using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace StellaOps.Policy;

public sealed class PolicySnapshotStore
{
    private readonly IPolicySnapshotRepository _snapshotRepository;
    private readonly IPolicyAuditRepository _auditRepository;
    private readonly TimeProvider _timeProvider;
    private readonly ILogger<PolicySnapshotStore> _logger;
    private readonly SemaphoreSlim _mutex = new(1, 1);

    public PolicySnapshotStore(
        IPolicySnapshotRepository snapshotRepository,
        IPolicyAuditRepository auditRepository,
        TimeProvider? timeProvider,
        ILogger<PolicySnapshotStore> logger)
    {
        _snapshotRepository = snapshotRepository ?? throw new ArgumentNullException(nameof(snapshotRepository));
        _auditRepository = auditRepository ?? throw new ArgumentNullException(nameof(auditRepository));
        _timeProvider = timeProvider ?? TimeProvider.System;
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<PolicySnapshotSaveResult> SaveAsync(PolicySnapshotContent content, CancellationToken cancellationToken = default)
    {
        if (content is null)
        {
            throw new ArgumentNullException(nameof(content));
        }

        var bindingResult = PolicyBinder.Bind(content.Content, content.Format);
        if (!bindingResult.Success)
        {
            _logger.LogWarning("Policy snapshot rejected due to validation errors (Format: {Format})", content.Format);
            return new PolicySnapshotSaveResult(false, false, string.Empty, null, bindingResult);
        }

        var digest = PolicyDigest.Compute(bindingResult.Document);

        await _mutex.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var latest = await _snapshotRepository.GetLatestAsync(cancellationToken).ConfigureAwait(false);
            if (latest is not null && string.Equals(latest.Digest, digest, StringComparison.Ordinal))
            {
                _logger.LogInformation("Policy snapshot unchanged; digest {Digest} matches revision {RevisionId}", digest, latest.RevisionId);
                return new PolicySnapshotSaveResult(true, false, digest, latest, bindingResult);
            }

            var revisionNumber = (latest?.RevisionNumber ?? 0) + 1;
            var revisionId = $"rev-{revisionNumber}";
            var createdAt = _timeProvider.GetUtcNow();

            var scoringConfig = PolicyScoringConfig.Default;

            var snapshot = new PolicySnapshot(
                revisionNumber,
                revisionId,
                digest,
                createdAt,
                content.Actor,
                content.Format,
                bindingResult.Document,
                bindingResult.Issues,
                scoringConfig);

            await _snapshotRepository.AddAsync(snapshot, cancellationToken).ConfigureAwait(false);

            var auditMessage = content.Description ?? "Policy snapshot created";
            var auditEntry = new PolicyAuditEntry(
                Guid.NewGuid(),
                createdAt,
                "snapshot.created",
                revisionId,
                digest,
                content.Actor,
                auditMessage);

            await _auditRepository.AddAsync(auditEntry, cancellationToken).ConfigureAwait(false);

            _logger.LogInformation(
                "Policy snapshot saved. Revision {RevisionId}, digest {Digest}, issues {IssueCount}",
                revisionId,
                digest,
                bindingResult.Issues.Length);

            return new PolicySnapshotSaveResult(true, true, digest, snapshot, bindingResult);
        }
        finally
        {
            _mutex.Release();
        }
    }

    public Task<PolicySnapshot?> GetLatestAsync(CancellationToken cancellationToken = default)
        => _snapshotRepository.GetLatestAsync(cancellationToken);
}
