using System.Collections.Concurrent;
using System.Collections.Generic;
using StellaOps.Scanner.WebService.Domain;
using StellaOps.Scanner.WebService.Utilities;

namespace StellaOps.Scanner.WebService.Services;

public sealed class InMemoryScanCoordinator : IScanCoordinator
{
    private sealed record ScanEntry(ScanSnapshot Snapshot);

    private readonly ConcurrentDictionary<string, ScanEntry> scans = new(StringComparer.OrdinalIgnoreCase);
    private readonly TimeProvider timeProvider;
    private readonly IScanProgressPublisher progressPublisher;

    public InMemoryScanCoordinator(TimeProvider timeProvider, IScanProgressPublisher progressPublisher)
    {
        this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
        this.progressPublisher = progressPublisher ?? throw new ArgumentNullException(nameof(progressPublisher));
    }

    public ValueTask<ScanSubmissionResult> SubmitAsync(ScanSubmission submission, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(submission);

        var normalizedTarget = submission.Target.Normalize();
        var metadata = submission.Metadata ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        var scanId = ScanIdGenerator.Create(normalizedTarget, submission.Force, submission.ClientRequestId, metadata);
        var now = timeProvider.GetUtcNow();

        var eventData = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
        {
            ["force"] = submission.Force,
        };
        foreach (var pair in metadata)
        {
            eventData[$"meta.{pair.Key}"] = pair.Value;
        }

        ScanEntry entry = scans.AddOrUpdate(
            scanId.Value,
            _ => new ScanEntry(new ScanSnapshot(
                scanId,
                normalizedTarget,
                ScanStatus.Pending,
                now,
                now,
                null)),
            (_, existing) =>
            {
                if (submission.Force)
                {
                    var snapshot = existing.Snapshot with
                    {
                        Status = ScanStatus.Pending,
                        UpdatedAt = now,
                        FailureReason = null
                    };
                    return new ScanEntry(snapshot);
                }

                return existing;
            });

        var created = entry.Snapshot.CreatedAt == now;
        var state = entry.Snapshot.Status.ToString();
        progressPublisher.Publish(scanId, state, created ? "queued" : "requeued", eventData);
        return ValueTask.FromResult(new ScanSubmissionResult(entry.Snapshot, created));
    }

    public ValueTask<ScanSnapshot?> GetAsync(ScanId scanId, CancellationToken cancellationToken)
    {
        if (scans.TryGetValue(scanId.Value, out var entry))
        {
            return ValueTask.FromResult<ScanSnapshot?>(entry.Snapshot);
        }

        return ValueTask.FromResult<ScanSnapshot?>(null);
    }
}
