using System.Collections.Concurrent;
using ClaudeDo.Data;
using Microsoft.EntityFrameworkCore;

namespace ClaudeDo.Worker.Tickets;

/// <summary>
/// Meldet den Task-Status ans verknüpfte Ticket zurück. Hängt an genau einem Punkt im System:
/// TaskStateService.NotifyAsync, dem einzigen Ort, an dem Statuswechsel zusammenlaufen.
///
/// Zwei Invarianten:
///  * Wirft nie. Ein Ticketsystem-Ausfall darf keinen Statuswechsel und keine Queue anhalten.
///  * Kostet nichts, wenn nichts eingerichtet ist — IsConfigured bricht vor jeder DB-Abfrage ab.
/// </summary>
public sealed class TicketStatusSync
{
    private readonly TicketSystemConfig _config;
    private readonly TicketClientFactory _clients;
    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
    private readonly ILogger<TicketStatusSync> _logger;

    // taskId → zuletzt geschriebener Ticket-Status. Verhindert den redundanten PATCH bei
    // Running → WaitingForReview (beide sind "InBearbeitung"). Bewusst nur im Prozessspeicher:
    // nach einem Neustart ist ein überzähliger PATCH harmlos.
    private readonly ConcurrentDictionary<string, int> _lastWritten = new();

    public TicketStatusSync(
        TicketSystemConfig config,
        TicketClientFactory clients,
        IDbContextFactory<ClaudeDoDbContext> dbFactory,
        ILogger<TicketStatusSync> logger)
    {
        _config = config;
        _clients = clients;
        _dbFactory = dbFactory;
        _logger = logger;
    }

    public async Task SyncAsync(string taskId, CancellationToken ct)
    {
        if (!_config.IsConfigured) return;

        try
        {
            await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
            var row = await ctx.Tasks.AsNoTracking()
                .Where(t => t.Id == taskId)
                .Select(t => new { t.Status, t.TicketRef })
                .FirstOrDefaultAsync(ct);

            if (row?.TicketRef is null) return;

            var ticketId = TicketStatusMap.ParseBandelTicketId(row.TicketRef);
            if (ticketId is null) return;

            var target = TicketStatusMap.ToTicketStatus(row.Status);
            if (target is null) return;

            if (_lastWritten.TryGetValue(taskId, out var previous) && previous == target.Value) return;

            var client = _clients.Create();
            if (client is null) return;

            await client.SetStatusAsync(ticketId.Value, target.Value, ct);
            _lastWritten[taskId] = target.Value;

            _logger.LogInformation(
                "Ticket {ticket_id} auf Status {ticket_status} gesetzt (Task {task_id})",
                ticketId.Value, target.Value, taskId);
        }
        catch (Exception ex)
        {
            // Serilog Warn landet über BroadcastLogSink im Footer-Log-Strip.
            _logger.LogWarning(ex,
                "Ticket-Status für Task {task_id} konnte nicht geschrieben werden: {reason}",
                taskId, ex.Message);
        }
    }
}
