using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace StellaOps.Feedser.Storage.Mongo.Exporting;

/// <summary>
/// Helper for exporters to read and persist their export metadata in Mongo-backed storage.
/// </summary>
public sealed class ExportStateManager
{
    private readonly IExportStateStore _store;
    private readonly TimeProvider _timeProvider;

    public ExportStateManager(IExportStateStore store, TimeProvider? timeProvider = null)
    {
        _store = store ?? throw new ArgumentNullException(nameof(store));
        _timeProvider = timeProvider ?? TimeProvider.System;
    }

    public Task<ExportStateRecord?> GetAsync(string exporterId, CancellationToken cancellationToken)
    {
        ArgumentException.ThrowIfNullOrEmpty(exporterId);
        return _store.FindAsync(exporterId, cancellationToken);
    }

    public async Task<ExportStateRecord> StoreFullExportAsync(
        string exporterId,
        string exportId,
        string exportDigest,
        string? cursor,
        string? targetRepository,
        string exporterVersion,
        bool resetBaseline,
        IReadOnlyList<ExportFileRecord> manifest,
        CancellationToken cancellationToken)
    {
        ArgumentException.ThrowIfNullOrEmpty(exporterId);
        ArgumentException.ThrowIfNullOrEmpty(exportId);
        ArgumentException.ThrowIfNullOrEmpty(exportDigest);
        ArgumentException.ThrowIfNullOrEmpty(exporterVersion);
        manifest ??= Array.Empty<ExportFileRecord>();

        var existing = await _store.FindAsync(exporterId, cancellationToken).ConfigureAwait(false);
        var now = _timeProvider.GetUtcNow();

        if (existing is null)
        {
            var resolvedRepository = string.IsNullOrWhiteSpace(targetRepository) ? null : targetRepository;
            return await _store.UpsertAsync(
                new ExportStateRecord(
                    exporterId,
                    BaseExportId: exportId,
                    BaseDigest: exportDigest,
                    LastFullDigest: exportDigest,
                    LastDeltaDigest: null,
                    ExportCursor: cursor ?? exportDigest,
                    TargetRepository: resolvedRepository,
                    ExporterVersion: exporterVersion,
                    UpdatedAt: now,
                    Files: manifest),
                cancellationToken).ConfigureAwait(false);
        }

        var repositorySpecified = !string.IsNullOrWhiteSpace(targetRepository);
        var resolvedRepo = repositorySpecified ? targetRepository : existing.TargetRepository;
        var repositoryChanged = repositorySpecified
            && !string.Equals(existing.TargetRepository, targetRepository, StringComparison.Ordinal);

        var shouldResetBaseline =
            resetBaseline
            || string.IsNullOrWhiteSpace(existing.BaseExportId)
            || string.IsNullOrWhiteSpace(existing.BaseDigest)
            || repositoryChanged;

        var updatedRecord = shouldResetBaseline
            ? existing with
            {
                BaseExportId = exportId,
                BaseDigest = exportDigest,
                LastFullDigest = exportDigest,
                LastDeltaDigest = null,
                ExportCursor = cursor ?? exportDigest,
                TargetRepository = resolvedRepo,
                ExporterVersion = exporterVersion,
                UpdatedAt = now,
                Files = manifest,
            }
            : existing with
            {
                LastFullDigest = exportDigest,
                LastDeltaDigest = null,
                ExportCursor = cursor ?? existing.ExportCursor,
                TargetRepository = resolvedRepo,
                ExporterVersion = exporterVersion,
                UpdatedAt = now,
                Files = manifest,
            };

        return await _store.UpsertAsync(updatedRecord, cancellationToken).ConfigureAwait(false);
    }

    public async Task<ExportStateRecord> StoreDeltaExportAsync(
        string exporterId,
        string deltaDigest,
        string? cursor,
        string exporterVersion,
        IReadOnlyList<ExportFileRecord> manifest,
        CancellationToken cancellationToken)
    {
        ArgumentException.ThrowIfNullOrEmpty(exporterId);
        ArgumentException.ThrowIfNullOrEmpty(deltaDigest);
        ArgumentException.ThrowIfNullOrEmpty(exporterVersion);
        manifest ??= Array.Empty<ExportFileRecord>();

        var existing = await _store.FindAsync(exporterId, cancellationToken).ConfigureAwait(false);
        if (existing is null)
        {
            throw new InvalidOperationException($"Full export state missing for '{exporterId}'.");
        }

        var now = _timeProvider.GetUtcNow();
        var record = existing with
        {
            LastDeltaDigest = deltaDigest,
            ExportCursor = cursor ?? existing.ExportCursor,
            ExporterVersion = exporterVersion,
            UpdatedAt = now,
            Files = manifest,
        };

        return await _store.UpsertAsync(record, cancellationToken).ConfigureAwait(false);
    }
}
