using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Bson;

namespace StellaOps.Feedser.Source.Kev.Internal;

internal sealed record KevCursor(
    string? CatalogVersion,
    DateTimeOffset? CatalogReleased,
    IReadOnlyCollection<Guid> PendingDocuments,
    IReadOnlyCollection<Guid> PendingMappings)
{
    public static KevCursor Empty { get; } = new(null, null, Array.Empty<Guid>(), Array.Empty<Guid>());

    public BsonDocument ToBsonDocument()
    {
        var document = new BsonDocument
        {
            ["pendingDocuments"] = new BsonArray(PendingDocuments.Select(static id => id.ToString())),
            ["pendingMappings"] = new BsonArray(PendingMappings.Select(static id => id.ToString())),
        };

        if (!string.IsNullOrEmpty(CatalogVersion))
        {
            document["catalogVersion"] = CatalogVersion;
        }

        if (CatalogReleased.HasValue)
        {
            document["catalogReleased"] = CatalogReleased.Value.UtcDateTime;
        }

        return document;
    }

    public static KevCursor FromBson(BsonDocument? document)
    {
        if (document is null || document.ElementCount == 0)
        {
            return Empty;
        }

        var version = document.TryGetValue("catalogVersion", out var versionValue)
            ? versionValue.AsString
            : null;

        var released = document.TryGetValue("catalogReleased", out var releasedValue)
            ? ParseDate(releasedValue)
            : null;

        return new KevCursor(
            version,
            released,
            ReadGuidArray(document, "pendingDocuments"),
            ReadGuidArray(document, "pendingMappings"));
    }

    public KevCursor WithCatalogMetadata(string? version, DateTimeOffset? released)
        => this with
        {
            CatalogVersion = string.IsNullOrWhiteSpace(version) ? null : version.Trim(),
            CatalogReleased = released?.ToUniversalTime(),
        };

    public KevCursor WithPendingDocuments(IEnumerable<Guid> ids)
        => this with { PendingDocuments = ids?.Distinct().ToArray() ?? Array.Empty<Guid>() };

    public KevCursor WithPendingMappings(IEnumerable<Guid> ids)
        => this with { PendingMappings = ids?.Distinct().ToArray() ?? Array.Empty<Guid>() };

    private static DateTimeOffset? ParseDate(BsonValue value)
        => value.BsonType switch
        {
            BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
            BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
            _ => null,
        };

    private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
    {
        if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
        {
            return Array.Empty<Guid>();
        }

        var results = new List<Guid>(array.Count);
        foreach (var element in array)
        {
            if (element is null)
            {
                continue;
            }

            if (Guid.TryParse(element.ToString(), out var guid))
            {
                results.Add(guid);
            }
        }

        return results;
    }
}
