using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Predictalytics.Application.Interfaces;
using Predictalytics.Infrastructure.Configuration;

namespace Predictalytics.Infrastructure.Services;

public class EgressPoolService : IEgressPoolService, IDisposable
{
    private class ChannelState
    {
        public EgressChannelOptions Options { get; }
        public HttpMessageInvoker Invoker { get; }
        public SocketsHttpHandler Handler { get; }
        public int ConsecutiveFailures { get; set; }
        public DateTime? CooldownUntil { get; set; }

        public ChannelState(EgressChannelOptions options, HttpMessageInvoker invoker, SocketsHttpHandler handler)
        {
            Options = options;
            Invoker = invoker;
            Handler = handler;
        }
    }

    private readonly List<ChannelState> _channels = new();
    private readonly ConcurrentDictionary<string, ChannelState> _channelMap = new();
    private readonly ILogger<EgressPoolService> _logger;
    private int _roundRobinIndex = -1;

    public EgressPoolService(IOptions<EgressOptions> options, ILogger<EgressPoolService> logger)
    {
        _logger = logger;
        InitializeChannels(options.Value);
    }

    private void InitializeChannels(EgressOptions options)
    {
        if (options?.Channels == null || options.Channels.Count == 0)
        {
            _logger.LogInformation("🔌 EgressPoolService: No egress channels configured. Using default direct connection.");
            return;
        }

        foreach (var config in options.Channels)
        {
            try
            {
                var handler = new SocketsHttpHandler
                {
                    PooledConnectionLifetime = TimeSpan.FromMinutes(2),
                    KeepAlivePingDelay = TimeSpan.FromSeconds(15),
                    KeepAlivePingTimeout = TimeSpan.FromSeconds(5)
                };

                if (config.Type == EgressChannelType.Proxy)
                {
                    handler.Proxy = new WebProxy(config.Value);
                    handler.UseProxy = true;
                    _logger.LogInformation("🔌 Registered Proxy Channel '{Id}' -> {Proxy}", config.Id, config.Value);
                }
                else if (config.Type == EgressChannelType.SourceIp)
                {
                    var ip = IPAddress.Parse(config.Value);
                    handler.ConnectCallback = async (context, cancellationToken) =>
                    {
                        var socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                        socket.Bind(new IPEndPoint(ip, 0));
                        try
                        {
                            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);
                            return new NetworkStream(socket, ownsSocket: true);
                        }
                        catch
                        {
                            socket.Dispose();
                            throw;
                        }
                    };
                    _logger.LogInformation("🔌 Registered SourceIp Channel '{Id}' -> {Ip}", config.Id, config.Value);
                }
                else
                {
                    _logger.LogWarning("🔌 EgressPoolService: Unknown channel type '{Type}' for channel '{Id}'. Skipping.", config.Type, config.Id);
                    continue;
                }

                var invoker = new HttpMessageInvoker(handler, disposeHandler: true);
                var state = new ChannelState(config, invoker, handler);
                _channels.Add(state);
                _channelMap[config.Id] = state;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "🔌 EgressPoolService: Failed to initialize channel '{Id}' ({Type}) with value '{Value}'", config.Id, config.Type, config.Value);
            }
        }
    }

    public string? GetNextChannelId()
    {
        if (_channels.Count == 0) return null;

        lock (_channels)
        {
            var now = DateTime.UtcNow;
            // Filter out channels currently in cooldown
            var available = _channels
                .Where(c => !c.CooldownUntil.HasValue || c.CooldownUntil.Value < now)
                .ToList();

            if (available.Count == 0)
            {
                _logger.LogWarning("🔌 All egress channels are currently in cooldown! Falling back to default direct connection.");
                return null;
            }

            _roundRobinIndex = (_roundRobinIndex + 1) % available.Count;
            return available[_roundRobinIndex].Options.Id;
        }
    }

    public HttpMessageInvoker? GetInvoker(string channelId)
    {
        return _channelMap.TryGetValue(channelId, out var state) ? state.Invoker : null;
    }

    public void ReportFailure(string channelId, Exception exception)
    {
        if (!_channelMap.TryGetValue(channelId, out var state)) return;

        lock (state)
        {
            state.ConsecutiveFailures++;
            _logger.LogWarning("🔌 Channel '{Id}' reported failure ({Count}/3): {Message}", channelId, state.ConsecutiveFailures, exception.Message);

            if (state.ConsecutiveFailures >= 3)
            {
                var cooldown = TimeSpan.FromSeconds(30);
                state.CooldownUntil = DateTime.UtcNow.Add(cooldown);
                _logger.LogError("🔌 Channel '{Id}' has failed 3 times consecutively. Entering cooldown until {Time}", channelId, state.CooldownUntil);
            }
        }
    }

    public void ReportSuccess(string channelId)
    {
        if (!_channelMap.TryGetValue(channelId, out var state)) return;

        lock (state)
        {
            if (state.ConsecutiveFailures > 0 || state.CooldownUntil.HasValue)
            {
                state.ConsecutiveFailures = 0;
                state.CooldownUntil = null;
                _logger.LogInformation("🔌 Channel '{Id}' recovered successfully.", channelId);
            }
        }
    }

    public void Dispose()
    {
        foreach (var state in _channels)
        {
            state.Invoker.Dispose();
        }
        _channels.Clear();
        _channelMap.Clear();
    }
}
