namespace BigRegister.Api.Data;

/// <summary>
/// Stored document: metadata + bytes. The demo holds bytes IN-MEMORY (reset on
/// restart) purely so re-opened wizards can preview/download what was uploaded — a
/// real backend persists them to blob storage keyed by DocumentId. Bytes are never
/// serialized into a JSON response; only the dedicated content endpoint streams them.
/// </summary>
public sealed record StoredDocument(
    string DocumentId, string LocalId, string CategoryId, string WizardId,
    string FileName, long SizeBytes, string ContentType, byte[] Content, string Owner, DateTimeOffset UploadedAt)
{
    public bool Linked { get; set; }
}

public sealed record AuditEntry(DateTimeOffset At, string Action, string DocumentId, string CategoryId, string Actor);

/// <summary>
/// In-memory document store + audit log (no DB). ponytail: one global lock — fine
/// for a single-process demo store; swap for per-key locks if it ever serves load.
/// The audit log holds metadata only (never file content or other PII).
/// </summary>
public static class DocumentStore
{
    /// The single seeded user (the demo has no real auth; ownership = this id).
    public const string DemoOwner = "19012345601";

    private static readonly Dictionary<string, StoredDocument> _docs = new();
    private static readonly List<AuditEntry> _audit = new();
    private static readonly object _gate = new();

    public static StoredDocument Add(string localId, string categoryId, string wizardId, string fileName, string contentType, byte[] content, string owner)
    {
        var doc = new StoredDocument(Guid.NewGuid().ToString(), localId, categoryId, wizardId, fileName, content.LongLength, contentType, content, owner, DateTimeOffset.UtcNow);
        lock (_gate) _docs[doc.DocumentId] = doc;
        Audit("upload", doc.DocumentId, categoryId, owner);
        return doc;
    }

    public static StoredDocument? Get(string documentId)
    {
        lock (_gate) return _docs.TryGetValue(documentId, out var d) ? d : null;
    }

    /// Status for the poll-on-return pattern: a known localId is "complete" (it
    /// arrived), an unknown one is still in flight / never started.
    public static IReadOnlyList<StoredDocument> ByLocalIds(IEnumerable<string> localIds)
    {
        var set = localIds.ToHashSet();
        lock (_gate) return _docs.Values.Where(d => set.Contains(d.LocalId)).ToList();
    }

    /// Mark digital documents as linked to a finalised submission (blocks user delete).
    public static void Link(IEnumerable<string> documentIds)
    {
        lock (_gate)
            foreach (var id in documentIds)
                if (_docs.TryGetValue(id, out var d)) d.Linked = true;
    }

    public enum DeleteResult { Ok, NotFound, Linked }

    /// User delete: owner-scoped; blocked once linked to a finalised submission.
    public static DeleteResult DeleteOwned(string documentId, string owner)
    {
        string categoryId;
        lock (_gate)
        {
            if (!_docs.TryGetValue(documentId, out var d) || d.Owner != owner) return DeleteResult.NotFound;
            if (d.Linked) return DeleteResult.Linked;
            categoryId = d.CategoryId;
            _docs.Remove(documentId);
        }
        Audit("delete-user", documentId, categoryId, owner);
        return DeleteResult.Ok;
    }

    /// Admin delete: bypasses ownership, unlinks, and (seam) flags the submission for
    /// review so a caseworker is notified. Returns false if the document is unknown.
    public static bool AdminDelete(string documentId, string actor)
    {
        string categoryId;
        lock (_gate)
        {
            if (!_docs.TryGetValue(documentId, out var d)) return false;
            categoryId = d.CategoryId;
            _docs.Remove(documentId);
        }
        Audit("delete-admin", documentId, categoryId, actor);
        return true;
    }

    public static void Audit(string action, string documentId, string categoryId, string actor)
    {
        lock (_gate) _audit.Add(new AuditEntry(DateTimeOffset.UtcNow, action, documentId, categoryId, actor));
    }

    public static IReadOnlyList<AuditEntry> AuditLog
    {
        get { lock (_gate) return _audit.ToList(); }
    }
}
