using MongoDB.Bson;

namespace StellaOps.Feedser.Source.Vndr.Cisco.Internal;

internal sealed record CiscoCursor(
    DateTimeOffset? LastModified,
    string? LastAdvisoryId,
    IReadOnlyCollection<Guid> PendingDocuments,
    IReadOnlyCollection<Guid> PendingMappings)
{
    private static readonly IReadOnlyCollection<Guid> EmptyGuidCollection = Array.Empty<Guid>();

    public static CiscoCursor Empty { get; } = new(null, null, EmptyGuidCollection, EmptyGuidCollection);

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

        if (LastModified.HasValue)
        {
            document["lastModified"] = LastModified.Value.UtcDateTime;
        }

        if (!string.IsNullOrWhiteSpace(LastAdvisoryId))
        {
            document["lastAdvisoryId"] = LastAdvisoryId;
        }

        return document;
    }

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

        DateTimeOffset? lastModified = null;
        if (document.TryGetValue("lastModified", out var lastModifiedValue))
        {
            lastModified = lastModifiedValue.BsonType switch
            {
                BsonType.DateTime => DateTime.SpecifyKind(lastModifiedValue.ToUniversalTime(), DateTimeKind.Utc),
                BsonType.String when DateTimeOffset.TryParse(lastModifiedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
                _ => null,
            };
        }

        string? lastAdvisoryId = null;
        if (document.TryGetValue("lastAdvisoryId", out var idValue) && idValue.BsonType == BsonType.String)
        {
            var value = idValue.AsString.Trim();
            if (value.Length > 0)
            {
                lastAdvisoryId = value;
            }
        }

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

        return new CiscoCursor(lastModified, lastAdvisoryId, pendingDocuments, pendingMappings);
    }

    public CiscoCursor WithCheckpoint(DateTimeOffset lastModified, string advisoryId)
        => this with
        {
            LastModified = lastModified.ToUniversalTime(),
            LastAdvisoryId = string.IsNullOrWhiteSpace(advisoryId) ? null : advisoryId.Trim(),
        };

    public CiscoCursor WithPendingDocuments(IEnumerable<Guid>? documents)
        => this with { PendingDocuments = documents?.Distinct().ToArray() ?? EmptyGuidCollection };

    public CiscoCursor WithPendingMappings(IEnumerable<Guid>? mappings)
        => this with { PendingMappings = mappings?.Distinct().ToArray() ?? EmptyGuidCollection };

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

        var results = new List<Guid>(array.Count);
        foreach (var element in array)
        {
            if (Guid.TryParse(element.ToString(), out var guid))
            {
                results.Add(guid);
            }
        }

        return results;
    }
}
