using CopilotCoordinator.Models;

namespace CopilotCoordinator.Services;

/// <summary>
/// Sequence execution engine. Ported from legacy SequenceService.cs.
/// Runs camera rotation sequences by sending CrossSwitch commands to bridges.
/// </summary>
public class SequenceRunner
{
    private readonly Dictionary<int, (CancellationTokenSource Cts, int SequenceId, DateTime StartedAt)> _running = new();
    private readonly SemaphoreSlim _lock = new(1, 1);
    private readonly WsBroadcaster _broadcaster;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _config;
    private readonly ILogger<SequenceRunner> _logger;

    // Sequence definitions loaded from config (in production, loaded from JSON file)
    private List<SequenceDefinition> _sequences = new();
    private List<SequenceCategory> _categories = new();

    public SequenceRunner(WsBroadcaster broadcaster, IHttpClientFactory httpClientFactory,
        IConfiguration config, ILogger<SequenceRunner> logger)
    {
        _broadcaster = broadcaster;
        _httpClientFactory = httpClientFactory;
        _config = config;
        _logger = logger;
    }

    public void LoadSequences(List<SequenceDefinition> sequences, List<SequenceCategory> categories)
    {
        _sequences = sequences;
        _categories = categories;
        _logger.LogInformation("Loaded {Count} sequences in {CatCount} categories", sequences.Count, categories.Count);
    }

    public IReadOnlyList<SequenceDefinition> GetSequences(int? categoryId = null)
    {
        return categoryId.HasValue
            ? _sequences.Where(s => s.CategoryId == categoryId.Value).ToList()
            : _sequences;
    }

    public IReadOnlyList<SequenceCategory> GetCategories() => _categories;

    public async Task<RunningSequence?> Start(int viewerId, int sequenceId)
    {
        var sequence = _sequences.FirstOrDefault(s => s.Id == sequenceId);
        if (sequence == null)
        {
            _logger.LogError("Sequence {SequenceId} not found", sequenceId);
            return null;
        }

        await _lock.WaitAsync();
        try
        {
            // Stop existing sequence on this viewer
            if (_running.TryGetValue(viewerId, out var existing))
            {
                existing.Cts.Cancel();
                _running.Remove(viewerId);
            }

            var cts = new CancellationTokenSource();
            var startedAt = DateTime.UtcNow;
            _running[viewerId] = (cts, sequenceId, startedAt);

            // Start the sequence task
            _ = Task.Run(() => RunSequenceLoop(viewerId, sequence, cts.Token), cts.Token);

            _logger.LogInformation("Started sequence {SequenceId} on viewer {ViewerId}", sequenceId, viewerId);

            var state = new RunningSequence(viewerId, sequenceId, startedAt, 0);
            await _broadcaster.Broadcast("sequence_started", state);
            return state;
        }
        finally { _lock.Release(); }
    }

    public async Task Stop(int viewerId)
    {
        await _lock.WaitAsync();
        try
        {
            if (_running.TryGetValue(viewerId, out var existing))
            {
                existing.Cts.Cancel();
                _running.Remove(viewerId);
                _logger.LogInformation("Stopped sequence on viewer {ViewerId}", viewerId);
            }
        }
        finally { _lock.Release(); }

        await _broadcaster.Broadcast("sequence_stopped", new { viewerId });
    }

    public async Task<IReadOnlyList<RunningSequence>> GetRunning()
    {
        await _lock.WaitAsync();
        try
        {
            return _running.Select(kvp => new RunningSequence(
                kvp.Key, kvp.Value.SequenceId, kvp.Value.StartedAt, 0
            )).ToList();
        }
        finally { _lock.Release(); }
    }

    /// <summary>
    /// Core sequence loop. Ported from legacy SequencePeriodTask.
    /// Cycles through cameras, sending CrossSwitch to the appropriate bridge.
    /// </summary>
    private async Task RunSequenceLoop(int viewerId, SequenceDefinition sequence, CancellationToken ct)
    {
        _logger.LogDebug("Sequence loop started: viewer {ViewerId}, sequence {SequenceId}", viewerId, sequence.Id);

        try
        {
            while (!ct.IsCancellationRequested)
            {
                for (int i = 0; i < sequence.Cameras.Count && !ct.IsCancellationRequested; i++)
                {
                    var camera = sequence.Cameras[i];

                    // Send CrossSwitch via bridge
                    await SendCrossSwitch(viewerId, camera);
                    _logger.LogDebug("Sequence {SeqId} viewer {ViewerId}: switched to camera {Camera}",
                        sequence.Id, viewerId, camera);

                    await Task.Delay(TimeSpan.FromSeconds(sequence.IntervalSeconds), ct);
                }
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Sequence loop cancelled: viewer {ViewerId}", viewerId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Sequence loop error: viewer {ViewerId}", viewerId);
        }
    }

    /// <summary>
    /// Send CrossSwitch command to the appropriate bridge.
    /// Determines bridge URL from camera ID ranges in configuration.
    /// </summary>
    private async Task SendCrossSwitch(int viewerId, int camera)
    {
        try
        {
            // Determine which bridge handles this camera based on configured ranges
            var bridgeUrl = ResolveBridgeUrl(camera);
            if (bridgeUrl == null)
            {
                _logger.LogWarning("No bridge found for camera {Camera}", camera);
                return;
            }

            var client = _httpClientFactory.CreateClient();
            client.Timeout = TimeSpan.FromSeconds(5);
            var response = await client.PostAsJsonAsync($"{bridgeUrl}/viewer/connect-live",
                new { Viewer = viewerId, Channel = camera });

            if (!response.IsSuccessStatusCode)
            {
                _logger.LogWarning("CrossSwitch failed: viewer {Viewer} camera {Camera} status {Status}",
                    viewerId, camera, response.StatusCode);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "CrossSwitch error: viewer {ViewerId} camera {Camera}", viewerId, camera);
        }
    }

    private string? ResolveBridgeUrl(int camera)
    {
        // Read bridge mappings from config: "Bridges" section with camera ranges
        var bridges = _config.GetSection("Bridges").GetChildren();
        foreach (var bridge in bridges)
        {
            var start = bridge.GetValue<int>("CameraRangeStart");
            var end = bridge.GetValue<int>("CameraRangeEnd");
            if (camera >= start && camera <= end)
                return bridge.GetValue<string>("Url");
        }
        // Fallback: first bridge
        return bridges.FirstOrDefault()?.GetValue<string>("Url");
    }
}
