using System.Linq;
using MongoDB.Bson;

namespace StellaOps.Feedser.Source.Jvn.Internal;

internal sealed record JvnCursor(
    DateTimeOffset? WindowStart,
    DateTimeOffset? WindowEnd,
    DateTimeOffset? LastCompletedWindowEnd,
    IReadOnlyCollection<Guid> PendingDocuments,
    IReadOnlyCollection<Guid> PendingMappings)
{
    public static JvnCursor Empty { get; } = new(null, null, null, Array.Empty<Guid>(), Array.Empty<Guid>());

    public BsonDocument ToBsonDocument()
    {
        var document = new BsonDocument();

        if (WindowStart.HasValue)
        {
            document["windowStart"] = WindowStart.Value.UtcDateTime;
        }

        if (WindowEnd.HasValue)
        {
            document["windowEnd"] = WindowEnd.Value.UtcDateTime;
        }

        if (LastCompletedWindowEnd.HasValue)
        {
            document["lastCompletedWindowEnd"] = LastCompletedWindowEnd.Value.UtcDateTime;
        }

        document["pendingDocuments"] = new BsonArray(PendingDocuments.Select(static id => id.ToString()));
        document["pendingMappings"] = new BsonArray(PendingMappings.Select(static id => id.ToString()));
        return document;
    }

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

        DateTimeOffset? windowStart = TryGetDateTime(document, "windowStart");
        DateTimeOffset? windowEnd = TryGetDateTime(document, "windowEnd");
        DateTimeOffset? lastCompleted = TryGetDateTime(document, "lastCompletedWindowEnd");

        var pendingDocuments = ReadGuidArray(document, "pendingDocuments");
        var pendingMappings = ReadGuidArray(document, "pendingMappings");

        return new JvnCursor(windowStart, windowEnd, lastCompleted, pendingDocuments, pendingMappings);
    }

    public JvnCursor WithWindow(DateTimeOffset start, DateTimeOffset end)
        => this with { WindowStart = start, WindowEnd = end };

    public JvnCursor WithCompletedWindow(DateTimeOffset end)
        => this with { LastCompletedWindowEnd = end };

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

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

    private static DateTimeOffset? TryGetDateTime(BsonDocument document, string field)
    {
        if (!document.TryGetValue(field, out var value))
        {
            return null;
        }

        return 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 (element.BsonType == BsonType.String && Guid.TryParse(element.AsString, out var guid))
            {
                results.Add(guid);
            }
        }

        return results;
    }
}
