using System;
using System.Collections.Generic;
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace StellaOps.Policy;

public sealed class PolicyPreviewService
{
    private readonly PolicySnapshotStore _snapshotStore;
    private readonly ILogger<PolicyPreviewService> _logger;

    public PolicyPreviewService(PolicySnapshotStore snapshotStore, ILogger<PolicyPreviewService> logger)
    {
        _snapshotStore = snapshotStore ?? throw new ArgumentNullException(nameof(snapshotStore));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<PolicyPreviewResponse> PreviewAsync(PolicyPreviewRequest request, CancellationToken cancellationToken = default)
    {
        if (request is null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        var (snapshot, bindingIssues) = await ResolveSnapshotAsync(request, cancellationToken).ConfigureAwait(false);
        if (snapshot is null)
        {
            _logger.LogWarning("Policy preview failed: snapshot unavailable or validation errors. Issues={Count}", bindingIssues.Length);
            return new PolicyPreviewResponse(false, string.Empty, null, bindingIssues, ImmutableArray<PolicyVerdictDiff>.Empty, 0);
        }

        var projected = Evaluate(snapshot.Document, snapshot.ScoringConfig, request.Findings);
        var baseline = BuildBaseline(request.BaselineVerdicts, projected, snapshot.ScoringConfig);
        var diffs = BuildDiffs(baseline, projected);
        var changed = diffs.Count(static diff => diff.Changed);

        _logger.LogDebug("Policy preview computed for {ImageDigest}. Changed={Changed}", request.ImageDigest, changed);

        return new PolicyPreviewResponse(true, snapshot.Digest, snapshot.RevisionId, bindingIssues, diffs, changed);
    }

    private async Task<(PolicySnapshot? Snapshot, ImmutableArray<PolicyIssue> Issues)> ResolveSnapshotAsync(PolicyPreviewRequest request, CancellationToken cancellationToken)
    {
        if (request.ProposedPolicy is not null)
        {
            var binding = PolicyBinder.Bind(request.ProposedPolicy.Content, request.ProposedPolicy.Format);
            if (!binding.Success)
            {
                return (null, binding.Issues);
            }

            var digest = PolicyDigest.Compute(binding.Document);
            var snapshot = new PolicySnapshot(
                request.SnapshotOverride?.RevisionNumber + 1 ?? 0,
                request.SnapshotOverride?.RevisionId ?? "preview",
                digest,
                DateTimeOffset.UtcNow,
                request.ProposedPolicy.Actor,
                request.ProposedPolicy.Format,
                binding.Document,
                binding.Issues,
                PolicyScoringConfig.Default);

            return (snapshot, binding.Issues);
        }

        if (request.SnapshotOverride is not null)
        {
            return (request.SnapshotOverride, ImmutableArray<PolicyIssue>.Empty);
        }

        var latest = await _snapshotStore.GetLatestAsync(cancellationToken).ConfigureAwait(false);
        if (latest is not null)
        {
            return (latest, ImmutableArray<PolicyIssue>.Empty);
        }

        return (null, ImmutableArray.Create(PolicyIssue.Error("policy.preview.snapshot_missing", "No policy snapshot is available for preview.", "$")));
    }

    private static ImmutableArray<PolicyVerdict> Evaluate(PolicyDocument document, PolicyScoringConfig scoringConfig, ImmutableArray<PolicyFinding> findings)
    {
        if (findings.IsDefaultOrEmpty)
        {
            return ImmutableArray<PolicyVerdict>.Empty;
        }

        var results = ImmutableArray.CreateBuilder<PolicyVerdict>(findings.Length);
        foreach (var finding in findings)
        {
            var verdict = PolicyEvaluation.EvaluateFinding(document, scoringConfig, finding);
            results.Add(verdict);
        }

        return results.ToImmutable();
    }

    private static ImmutableDictionary<string, PolicyVerdict> BuildBaseline(ImmutableArray<PolicyVerdict> baseline, ImmutableArray<PolicyVerdict> projected, PolicyScoringConfig scoringConfig)
    {
        var builder = ImmutableDictionary.CreateBuilder<string, PolicyVerdict>(StringComparer.Ordinal);
        if (!baseline.IsDefaultOrEmpty)
        {
            foreach (var verdict in baseline)
            {
                if (!string.IsNullOrEmpty(verdict.FindingId) && !builder.ContainsKey(verdict.FindingId))
                {
                    builder.Add(verdict.FindingId, verdict);
                }
            }
        }

        foreach (var verdict in projected)
        {
            if (!builder.ContainsKey(verdict.FindingId))
            {
                builder.Add(verdict.FindingId, PolicyVerdict.CreateBaseline(verdict.FindingId, scoringConfig));
            }
        }

        return builder.ToImmutable();
    }

    private static ImmutableArray<PolicyVerdictDiff> BuildDiffs(ImmutableDictionary<string, PolicyVerdict> baseline, ImmutableArray<PolicyVerdict> projected)
    {
        var diffs = ImmutableArray.CreateBuilder<PolicyVerdictDiff>(projected.Length);
        foreach (var verdict in projected.OrderBy(static v => v.FindingId, StringComparer.Ordinal))
        {
            var baseVerdict = baseline.TryGetValue(verdict.FindingId, out var existing)
                ? existing
                : new PolicyVerdict(verdict.FindingId, PolicyVerdictStatus.Pass);

            diffs.Add(new PolicyVerdictDiff(baseVerdict, verdict));
        }

        return diffs.ToImmutable();
    }
}
