using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
using Inspectron.Epson.PrintServer.ConfigurationSources;
using Inspectron.Epson.Queue;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;

namespace Inspectron.Epson.PrintServer.JobSources;

public class SignalRPrintJobSource: IPrintJobSource
{
    private readonly EpsonPrintServiceConfiguration _groupConfiguration;
    private readonly ILogger _logger;
    private readonly HubConnection _connection;
    private readonly Channel<PrintJob> _printJobChannel = Channel.CreateUnbounded<PrintJob>();

    public SignalRPrintJobSource(EpsonPrintServiceConfiguration groupConfiguration, ILogger logger)
    {
        _groupConfiguration = groupConfiguration;
        _logger = logger;

        var hubUrl = $"{groupConfiguration.ApiUrl?.TrimEnd('/')}/hubs/printer-servers";
        _connection = new HubConnectionBuilder()
            .WithUrl(hubUrl)
            .WithAutomaticReconnect(new InfiniteRetryPolicy())
            .Build();

        // Register handlers BEFORE starting connection
        _connection.On<PrintJobFromSignalR>("PrintJob", OnPrintJobReceived);

        
        _connection.Reconnecting += OnReconnecting;
        _connection.Reconnected += OnReconnected;
        _connection.Closed += OnClosed;

        _ = InitializeConnectionAsync();
    }

    private async Task InitializeConnectionAsync()
    {
        while (true)
        {
            try
            {
                await _connection.StartAsync();
                await _connection.InvokeAsync("JoinGroup", _groupConfiguration.RestaurantId);
                _logger.LogInformation("SignalR connection started and joined group {GroupId}.", _groupConfiguration.RestaurantId);
                return;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to initialize SignalR connection. Retrying in 5 seconds...");
                await Task.Delay(5000);
            }
        }
    }

    private Task OnReconnecting(Exception? exception)
    {
        _logger.LogWarning(exception, "SignalR connection lost. Reconnecting...");
        return Task.CompletedTask;
    }

    private async Task OnReconnected(string? connectionId)
    {
        _logger.LogInformation("SignalR reconnected with connection ID: {ConnectionId}. Rejoining group...", connectionId);
        try
        {
            await _connection.InvokeAsync("JoinGroup", _groupConfiguration.RestaurantId);
            _logger.LogInformation("Rejoined SignalR group {GroupId} after reconnection.", _groupConfiguration.RestaurantId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to rejoin group after reconnection.");
        }
    }

    private async Task OnClosed(Exception? exception)
    {
        _logger.LogError(exception, "SignalR connection closed. Attempting manual restart...");
        
        // Manual reconnection loop if automatic reconnection exhausted (shouldn't happen with infinite retry)
        await Task.Delay(5000); // Wait before retry
        try
        {
            await _connection.StartAsync();
            await _connection.InvokeAsync("JoinGroup", _groupConfiguration.RestaurantId);
            _logger.LogInformation("Manually reconnected to SignalR.");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Manual reconnection failed. Will retry on next connection closed event.");
        }
    }

    private void OnPrintJobReceived(PrintJobFromSignalR args)
    {
        
        _logger.LogInformation("Received print job for working area: {PrintJob}", JsonSerializer.Serialize(args));
        _printJobChannel.Writer.TryWrite(new PrintJob
        {
            IP = args.PrinterIp,
            Document = args,
        });
    }

    public async Task<PrintJob> GetNextJobAsync(CancellationToken cancellationToken)
    {
        return await _printJobChannel.Reader.ReadAsync(cancellationToken);
    }

    public class PrintJobFromSignalR
    {
        [JsonPropertyName("printerIp")]
        public string PrinterIp { get; set; }

        [JsonPropertyName("logoUrl")]
        public string? LogoUrl { get; set; }

        [JsonPropertyName("receiptType")]
        public int ReceiptType { get; set; }

        [JsonPropertyName("content")]
        public string Content { get; set; }
    }

    private class InfiniteRetryPolicy : IRetryPolicy
    {
        public TimeSpan? NextRetryDelay(RetryContext retryContext)
        {
            // Exponential backoff with a cap at 30 seconds
            var delay = Math.Min(Math.Pow(2, retryContext.PreviousRetryCount), 30);
            return TimeSpan.FromSeconds(delay);
        }
    }
}