using System.Net.Sockets;
using Microsoft.Extensions.Logging;

namespace Flyshot.Runtime.Fanuc.Protocol;

/// <summary>
/// 表示 FANUC TCP 10010 状态通道客户端的连接阶段。
/// </summary>
public enum FanucStateConnectionState
{
    /// <summary>
    /// 状态通道未连接。
    /// </summary>
    Disconnected,

    /// <summary>
    /// 状态通道正在建立连接。
    /// </summary>
    Connecting,

    /// <summary>
    /// 状态通道已连接并由后台循环接收状态帧。
    /// </summary>
    Connected,

    /// <summary>
    /// 状态通道在限定时间内没有收到完整状态帧。
    /// </summary>
    TimedOut,

    /// <summary>
    /// 状态通道正在按退避策略重新连接。
    /// </summary>
    Reconnecting,
}

/// <summary>
/// 定义 FANUC TCP 10010 状态通道的超时和重连参数。
/// </summary>
public sealed class FanucStateClientOptions
{
    /// <summary>
    /// 获取或设置接收一帧完整 90B 状态帧允许的最长时间。
    /// </summary>
    public TimeSpan FrameTimeout { get; init; } = TimeSpan.FromMilliseconds(250);

    /// <summary>
    /// 获取或设置初始重连等待时间。
    /// </summary>
    public TimeSpan ReconnectInitialDelay { get; init; } = TimeSpan.FromMilliseconds(100);

    /// <summary>
    /// 获取或设置重连等待时间的上限。
    /// </summary>
    public TimeSpan ReconnectMaxDelay { get; init; } = TimeSpan.FromSeconds(5);

    /// <summary>
    /// 获取或设置单次 TCP 建连允许的最长时间。
    /// </summary>
    public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(5);
}

/// <summary>
/// 表示 FANUC TCP 10010 状态通道客户端的当前诊断状态。
/// </summary>
public sealed class FanucStateClientStatus
{
    /// <summary>
    /// 初始化状态通道诊断状态。
    /// </summary>
    public FanucStateClientStatus(
        FanucStateConnectionState state,
        bool isFrameStale,
        DateTimeOffset? lastFrameAt,
        long reconnectAttemptCount,
        string? lastErrorMessage)
    {
        State = state;
        IsFrameStale = isFrameStale;
        LastFrameAt = lastFrameAt;
        ReconnectAttemptCount = reconnectAttemptCount;
        LastErrorMessage = lastErrorMessage;
    }

    /// <summary>
    /// 获取状态通道当前连接阶段。
    /// </summary>
    public FanucStateConnectionState State { get; }

    /// <summary>
    /// 获取最近缓存状态帧是否已经超过状态帧超时窗口。
    /// </summary>
    public bool IsFrameStale { get; }

    /// <summary>
    /// 获取最近一次成功解析状态帧的 UTC 时间。
    /// </summary>
    public DateTimeOffset? LastFrameAt { get; }

    /// <summary>
    /// 获取后台循环发起重连的累计次数。
    /// </summary>
    public long ReconnectAttemptCount { get; }

    /// <summary>
    /// 获取最近一次状态通道异常的诊断文本。
    /// </summary>
    public string? LastErrorMessage { get; }
}

/// <summary>
/// FANUC TCP 10010 状态通道客户端，持续接收状态帧并缓存最新快照。
/// </summary>
public sealed class FanucStateClient : IDisposable
{
    private readonly object _stateLock = new();
    private readonly FanucStateClientOptions _options;
    private readonly ILogger<FanucStateClient>? _logger;
    private TcpClient? _tcpClient;
    private NetworkStream? _stream;
    private CancellationTokenSource? _receiveCts;
    private Task? _receiveTask;
    private FanucStateFrame? _latestFrame;
    private FanucStateConnectionState _connectionState = FanucStateConnectionState.Disconnected;
    private DateTimeOffset? _lastConnectedAt;
    private DateTimeOffset? _lastFrameAt;
    private long _reconnectAttemptCount;
    private string? _lastErrorMessage;
    private bool _disposed;

    /// <summary>
    /// 使用默认状态通道参数初始化客户端。
    /// </summary>
    public FanucStateClient()
        : this(new FanucStateClientOptions(), null)
    {
    }

    /// <summary>
    /// 使用指定状态通道参数初始化客户端。
    /// </summary>
    /// <param name="options">超时和重连参数。</param>
    public FanucStateClient(FanucStateClientOptions options)
        : this(options, null)
    {
    }

    /// <summary>
    /// 使用指定状态通道参数和日志记录器初始化客户端。
    /// </summary>
    /// <param name="options">超时和重连参数。</param>
    /// <param name="logger">日志记录器；允许 null。</param>
    public FanucStateClient(FanucStateClientOptions options, ILogger<FanucStateClient>? logger)
    {
        ArgumentNullException.ThrowIfNull(options);
        ValidateOptions(options);
        _options = options;
        _logger = logger;
    }

    /// <summary>
    /// 获取当前是否已建立连接。
    /// </summary>
    public bool IsConnected => GetStatus().State == FanucStateConnectionState.Connected;

    /// <summary>
    /// 建立到 FANUC 控制柜 TCP 10010 状态通道的连接并启动后台接收循环。
    /// </summary>
    /// <param name="ip">控制柜 IP 地址。</param>
    /// <param name="port">状态通道端口，默认 10010。</param>
    /// <param name="cancellationToken">取消令牌。</param>
    public async Task ConnectAsync(string ip, int port = 10010, CancellationToken cancellationToken = default)
    {
        ObjectDisposedException.ThrowIf(_disposed, this);

        if (string.IsNullOrWhiteSpace(ip))
        {
            throw new ArgumentException("IP 不能为空。", nameof(ip));
        }

        if (_receiveTask is not null)
        {
            throw new InvalidOperationException("状态通道已经连接，请先 Disconnect。");
        }

        _logger?.LogInformation("StateClient ConnectAsync: {Ip}:{Port}", ip, port);

        _receiveCts = new CancellationTokenSource();
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _receiveCts.Token);

        lock (_stateLock)
        {
            _connectionState = FanucStateConnectionState.Connecting;
            _latestFrame = null;
            _lastConnectedAt = null;
            _lastFrameAt = null;
            _reconnectAttemptCount = 0;
            _lastErrorMessage = null;
        }

        try
        {
            await OpenConnectionAsync(ip, port, linkedCts.Token).ConfigureAwait(false);
        }
        catch (Exception exception)
        {
            _logger?.LogError(exception, "StateClient 连接失败: {Ip}:{Port}", ip, port);
            CloseCurrentConnection();
            lock (_stateLock)
            {
                _connectionState = FanucStateConnectionState.Disconnected;
            }

            _receiveCts.Dispose();
            _receiveCts = null;
            throw;
        }

        _receiveTask = Task.Run(
            () => ReceiveAndReconnectLoopAsync(ip, port, _receiveCts.Token),
            _receiveCts.Token);

        _logger?.LogInformation("StateClient 已连接并启动接收循环: {Ip}:{Port}", ip, port);
    }

    /// <summary>
    /// 断开状态通道并停止后台接收循环。
    /// </summary>
    public void Disconnect()
    {
        ObjectDisposedException.ThrowIf(_disposed, this);

        _logger?.LogInformation("StateClient Disconnect");
        Shutdown(clearLatestFrame: true);
    }

    /// <summary>
    /// 获取最近一次解析的状态帧；若尚未收到任何帧则返回 null。
    /// </summary>
    /// <returns>最新状态帧或 null。</returns>
    public FanucStateFrame? GetLatestFrame()
    {
        ObjectDisposedException.ThrowIf(_disposed, this);

        lock (_stateLock)
        {
            return _latestFrame;
        }
    }

    /// <summary>
    /// 获取状态通道当前诊断状态。
    /// </summary>
    /// <returns>状态通道诊断快照。</returns>
    public FanucStateClientStatus GetStatus()
    {
        ObjectDisposedException.ThrowIf(_disposed, this);

        lock (_stateLock)
        {
            return new FanucStateClientStatus(
                _connectionState,
                IsFrameStaleLocked(DateTimeOffset.UtcNow),
                _lastFrameAt,
                _reconnectAttemptCount,
                _lastErrorMessage);
        }
    }

    /// <summary>
    /// 释放客户端资源。
    /// </summary>
    public void Dispose()
    {
        if (_disposed)
        {
            return;
        }

        _disposed = true;
        Shutdown(clearLatestFrame: true);
    }

    /// <summary>
    /// 后台循环：持续接收状态帧；断线、超时或坏帧后进入退避重连。
    /// </summary>
    private async Task ReceiveAndReconnectLoopAsync(string ip, int port, CancellationToken cancellationToken)
    {
        var reconnectDelay = _options.ReconnectInitialDelay;
        _logger?.LogInformation("StateClient 接收循环启动: {Ip}:{Port}", ip, port);

        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                await ReceiveCurrentConnectionAsync(cancellationToken).ConfigureAwait(false);
                reconnectDelay = _options.ReconnectInitialDelay;
            }
            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
            {
                _logger?.LogInformation("StateClient 接收循环正常取消");
                return;
            }
            catch (TimeoutException ex)
            {
                _logger?.LogWarning(ex, "StateClient 接收超时");
                MarkReceiveFailure(FanucStateConnectionState.TimedOut, ex.Message);
            }
            catch (Exception ex) when (ex is IOException or InvalidDataException or SocketException or ObjectDisposedException)
            {
                _logger?.LogWarning(ex, "StateClient 连接异常，准备重连");
                MarkReceiveFailure(FanucStateConnectionState.Reconnecting, ex.Message);
            }

            CloseCurrentConnection();

            if (cancellationToken.IsCancellationRequested)
            {
                return;
            }

            reconnectDelay = await ReconnectWithBackoffAsync(ip, port, reconnectDelay, cancellationToken).ConfigureAwait(false);
        }
    }

    /// <summary>
    /// 从当前连接中持续读取状态帧，直到连接异常或被取消。
    /// </summary>
    private async Task ReceiveCurrentConnectionAsync(CancellationToken cancellationToken)
    {
        NetworkStream stream;
        lock (_stateLock)
        {
            stream = _stream ?? throw new IOException("状态通道未连接。");
        }

        var buffer = new byte[FanucStateProtocol.StateFrameLength];
        long frameCount = 0;
        FanucStateFrame? lastLoggedFrame = null;

        while (!cancellationToken.IsCancellationRequested)
        {
            await ReadExactAsync(stream, buffer, cancellationToken).ConfigureAwait(false);

            var frame = FanucStateProtocol.ParseFrame(buffer);
            lock (_stateLock)
            {
                _latestFrame = frame;
                _lastFrameAt = DateTimeOffset.UtcNow;
                _connectionState = FanucStateConnectionState.Connected;
                _lastErrorMessage = null;
            }

            frameCount++;

            // 仅在状态变化或首次接收时记录 Info，避免高频日志。
            if (lastLoggedFrame is null
                || lastLoggedFrame.CartesianPose[0] != frame.CartesianPose[0]
                || !lastLoggedFrame.RawTailWords.SequenceEqual(frame.RawTailWords))
            {
                _logger?.LogInformation(
                    "StateClient 收到状态帧: pose=[{X:F1}, {Y:F1}, {Z:F1}], tail=[{Tail}]",
                    frame.CartesianPose[0],
                    frame.CartesianPose[1],
                    frame.CartesianPose[2],
                    string.Join(", ", frame.RawTailWords));
                lastLoggedFrame = frame;
            }
            else if (frameCount % 1000 == 0)
            {
                _logger?.LogDebug("StateClient 已接收 {Count} 个状态帧", frameCount);
            }
        }
    }

    /// <summary>
    /// 从流中精确读取固定长度字节，超过帧超时窗口则抛出超时异常。
    /// </summary>
    private async Task ReadExactAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken)
    {
        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        timeoutCts.CancelAfter(_options.FrameTimeout);

        var totalRead = 0;
        try
        {
            while (totalRead < buffer.Length)
            {
                var read = await stream.ReadAsync(
                    buffer.AsMemory(totalRead, buffer.Length - totalRead),
                    timeoutCts.Token).ConfigureAwait(false);

                if (read == 0)
                {
                    throw new IOException("状态通道已断开，读取到 EOF。");
                }

                totalRead += read;
            }
        }
        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
        {
            throw new TimeoutException("状态通道接收超时，未在限定时间内收到完整 90B 状态帧。");
        }
    }

    /// <summary>
    /// 打开 TCP 状态通道并更新连接状态。
    /// </summary>
    private async Task OpenConnectionAsync(string ip, int port, CancellationToken cancellationToken)
    {
        var tcpClient = new TcpClient { NoDelay = true };
        try
        {
            _logger?.LogInformation("StateClient 正在连接 {Ip}:{Port}...", ip, port);

            using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            timeoutCts.CancelAfter(_options.ConnectTimeout);
            await tcpClient.ConnectAsync(ip, port, timeoutCts.Token).ConfigureAwait(false);

            lock (_stateLock)
            {
                _tcpClient = tcpClient;
                _stream = tcpClient.GetStream();
                _lastConnectedAt = DateTimeOffset.UtcNow;
                _connectionState = FanucStateConnectionState.Connected;
            }

            _logger?.LogInformation("StateClient 已连接到 {Ip}:{Port}", ip, port);
        }
        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
        {
            _logger?.LogWarning("StateClient 连接 {Ip}:{Port} 超时", ip, port);
            tcpClient.Dispose();
            throw new TimeoutException("状态通道建连超时。");
        }
        catch (Exception ex)
        {
            _logger?.LogWarning(ex, "StateClient 连接 {Ip}:{Port} 失败", ip, port);
            tcpClient.Dispose();
            throw;
        }
    }

    /// <summary>
    /// 按退避策略循环尝试重新连接，并返回下一次异常后的退避时间。
    /// </summary>
    private async Task<TimeSpan> ReconnectWithBackoffAsync(
        string ip,
        int port,
        TimeSpan reconnectDelay,
        CancellationToken cancellationToken)
    {
        var nextDelay = reconnectDelay;

        while (!cancellationToken.IsCancellationRequested)
        {
            lock (_stateLock)
            {
                _connectionState = FanucStateConnectionState.Reconnecting;
            }

            _logger?.LogInformation(
                "StateClient 将在 {Delay}ms 后尝试重连 {Ip}:{Port}...",
                nextDelay.TotalMilliseconds,
                ip,
                port);

            await Task.Delay(nextDelay, cancellationToken).ConfigureAwait(false);

            lock (_stateLock)
            {
                _reconnectAttemptCount++;
            }

            try
            {
                await OpenConnectionAsync(ip, port, cancellationToken).ConfigureAwait(false);
                _logger?.LogInformation(
                    "StateClient 重连成功: {Ip}:{Port}, 累计重连次数={Count}",
                    ip,
                    port,
                    _reconnectAttemptCount);
                return _options.ReconnectInitialDelay;
            }
            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
            {
                throw;
            }
            catch (Exception ex) when (ex is SocketException or IOException or TimeoutException)
            {
                CloseCurrentConnection();
                lock (_stateLock)
                {
                    _connectionState = FanucStateConnectionState.Reconnecting;
                    _lastErrorMessage = ex.Message;
                }

                _logger?.LogWarning(
                    ex,
                    "StateClient 重连失败: {Ip}:{Port}, 下次等待={NextDelay}ms",
                    ip,
                    port,
                    nextDelay.TotalMilliseconds * 2);

                nextDelay = IncreaseReconnectDelay(nextDelay);
            }
        }

        return nextDelay;
    }

    /// <summary>
    /// 关闭当前 TCP 连接，不清除最新状态帧，供重连路径保留诊断数据。
    /// </summary>
    private void CloseCurrentConnection()
    {
        NetworkStream? stream;
        TcpClient? tcpClient;
        lock (_stateLock)
        {
            stream = _stream;
            tcpClient = _tcpClient;
            _stream = null;
            _tcpClient = null;
        }

        stream?.Dispose();
        tcpClient?.Dispose();
    }

    /// <summary>
    /// 记录接收异常并更新状态通道连接阶段。
    /// </summary>
    private void MarkReceiveFailure(FanucStateConnectionState state, string message)
    {
        lock (_stateLock)
        {
            _connectionState = state;
            _lastErrorMessage = message;
        }

        _logger?.LogWarning("StateClient 接收失败: state={State}, message={Message}", state, message);
    }

    /// <summary>
    /// 关闭后台循环和 socket 资源。
    /// </summary>
    private void Shutdown(bool clearLatestFrame)
    {
        _receiveCts?.Cancel();
        CloseCurrentConnection();

        try
        {
            _receiveTask?.Wait(TimeSpan.FromSeconds(2));
        }
        catch (AggregateException)
        {
            // 后台循环可能因取消而抛出 OperationCanceledException，忽略即可。
        }

        _receiveTask = null;
        _receiveCts?.Dispose();
        _receiveCts = null;

        lock (_stateLock)
        {
            _connectionState = FanucStateConnectionState.Disconnected;
            _lastConnectedAt = null;
            _lastErrorMessage = null;
            _reconnectAttemptCount = 0;
            if (clearLatestFrame)
            {
                _latestFrame = null;
                _lastFrameAt = null;
            }
        }
    }

    /// <summary>
    /// 判断缓存帧是否已经不能代表当前控制柜状态。
    /// </summary>
    private bool IsFrameStaleLocked(DateTimeOffset now)
    {
        if (_latestFrame is null)
        {
            return _connectionState is FanucStateConnectionState.TimedOut or FanucStateConnectionState.Reconnecting
                || _reconnectAttemptCount > 0
                || (_lastConnectedAt.HasValue && now - _lastConnectedAt.Value > _options.FrameTimeout);
        }

        return _lastFrameAt.HasValue && now - _lastFrameAt.Value > _options.FrameTimeout;
    }

    /// <summary>
    /// 计算下一轮重连等待时间。
    /// </summary>
    private TimeSpan IncreaseReconnectDelay(TimeSpan currentDelay)
    {
        var doubledMilliseconds = Math.Max(currentDelay.TotalMilliseconds * 2.0, _options.ReconnectInitialDelay.TotalMilliseconds);
        var cappedMilliseconds = Math.Min(doubledMilliseconds, _options.ReconnectMaxDelay.TotalMilliseconds);
        return TimeSpan.FromMilliseconds(cappedMilliseconds);
    }

    /// <summary>
    /// 校验状态通道参数，避免后台循环使用无效时间窗口。
    /// </summary>
    private static void ValidateOptions(FanucStateClientOptions options)
    {
        ValidatePositive(options.FrameTimeout, nameof(options.FrameTimeout));
        ValidatePositive(options.ReconnectInitialDelay, nameof(options.ReconnectInitialDelay));
        ValidatePositive(options.ReconnectMaxDelay, nameof(options.ReconnectMaxDelay));
        ValidatePositive(options.ConnectTimeout, nameof(options.ConnectTimeout));

        if (options.ReconnectMaxDelay < options.ReconnectInitialDelay)
        {
            throw new ArgumentOutOfRangeException(nameof(options), "最大重连等待时间不能小于初始重连等待时间。");
        }
    }

    /// <summary>
    /// 校验时间参数必须为正值。
    /// </summary>
    private static void ValidatePositive(TimeSpan value, string parameterName)
    {
        if (value <= TimeSpan.Zero)
        {
            throw new ArgumentOutOfRangeException(parameterName, "时间参数必须大于 0。");
        }
    }
}
