using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using System.Security.Claims;
using Microsoft.AspNetCore.SignalR;
using DotNext;
using System.Collections.Concurrent;
using TypedSignalR.Client;
using Tapper;

namespace server.Hubs;

[Hub]
public interface IJtagHub
{
    Task<bool> SetBoundaryScanFreq(int freq);
    Task<bool> StartBoundaryScan(int freq = 100);
    Task<bool> StopBoundaryScan();
}

[Receiver]
public interface IJtagReceiver
{
    Task OnReceiveBoundaryScanData(Dictionary<string, bool> msg);
}

[Authorize]
[EnableCors("SignalR")]
public class JtagHub : Hub<IJtagReceiver>, IJtagHub
{
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    private readonly IHubContext<JtagHub, IJtagReceiver> _hubContext;
    private readonly Database.UserManager _userManager;

    private static ConcurrentDictionary<string, int> FreqTable = new();
    private static ConcurrentDictionary<string, CancellationTokenSource> CancellationTokenSourceTable = new();

    public JtagHub(IHubContext<JtagHub, IJtagReceiver> hubContext, Database.UserManager userManager)
    {
        _hubContext = hubContext;
        _userManager = userManager;
    }

    private Optional<Peripherals.JtagClient.Jtag> GetJtagClient(string userName)
    {
        try
        {
            var board = _userManager.GetBoardByUserName(userName);
            if (!board.IsSuccessful)
            {
                logger.Error($"Find Board {board.Value.Value.ID} failed because {board.Error}");
                return new(null);
            }
            if (!board.Value.HasValue)
            {
                logger.Error($"Board {board.Value.Value.ID} not found");
                return new(null);
            }

            var jtag = new Peripherals.JtagClient.Jtag(board.Value.Value.IpAddr, board.Value.Value.Port);
            return new(jtag);
        }
        catch (Exception error)
        {
            logger.Error(error);
            return new(null);
        }
    }

    public async Task<bool> SetBoundaryScanFreq(int freq)
    {
        try
        {
            var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
            if (userName is null)
            {
                logger.Error("Can't get user info");
                return false;
            }

            FreqTable.AddOrUpdate(userName, freq, (key, value) => freq);
            return true;
        }
        catch (Exception error)
        {
            logger.Error(error);
            return false;
        }
    }

    public async Task<bool> StartBoundaryScan(int freq = 100)
    {
        try
        {
            var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
            if (userName is null)
            {
                logger.Error("No Such User");
                return false;
            }

            SetBoundaryScanFreq(freq);
            var cts = new CancellationTokenSource();
            CancellationTokenSourceTable.AddOrUpdate(userName, cts, (key, value) => cts);

            _ = Task.Run(
                    () => BoundaryScanLogicPorts(Context.ConnectionId, userName, cts.Token),
                    cts.Token)
                .ContinueWith((task) =>
                {
                    if (task.IsFaulted)
                    {
                        // 遍历所有异常
                        foreach (var ex in task.Exception.InnerExceptions)
                        {
                            if (ex is OperationCanceledException)
                            {
                                logger.Info($"Boundary scan operation cancelled for user {userName}");
                            }
                            else
                            {
                                logger.Error($"Boundary scan operation failed for user {userName}: {ex}");
                            }
                        }
                    }
                    else if (task.IsCanceled)
                    {
                        logger.Info($"Boundary scan operation cancelled for user {userName}");
                    }
                    else
                    {
                        logger.Info($"Boundary scan completed successfully for user {userName}");
                    }
                });

            logger.Info($"Boundary scan started for user {userName}");
            return true;
        }
        catch (Exception error)
        {
            logger.Error(error);
            return false;
        }
    }

    public async Task<bool> StopBoundaryScan()
    {
        var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
        if (userName is null)
        {
            logger.Error("No Such User");
            return false;
        }

        if (!CancellationTokenSourceTable.TryGetValue(userName, out var cts))
        {
            return false;
        }

        cts.Cancel();
        cts.Token.WaitHandle.WaitOne();

        logger.Info($"Boundary scan stopped for user {userName}");
        return true;
    }

    private async Task BoundaryScanLogicPorts(string connectionID, string userName, CancellationToken cancellationToken)
    {
        var jtagCtrl = GetJtagClient(userName).OrThrow(() => new InvalidOperationException("JTAG client not found"));
        var cntFail = 0;

        while (true && cntFail < 5)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var ret = await jtagCtrl.BoundaryScanLogicalPorts();
            if (!ret.IsSuccessful)
            {
                logger.Error($"User {userName} boundary scan failed for device {jtagCtrl.address}: {ret.Error}");
                cntFail++;
                continue;
            }

            await _hubContext.Clients.Client(connectionID).OnReceiveBoundaryScanData(ret.Value);
            // logger.Info($"User {userName} successfully completed boundary scan for device {jtagCtrl.address}");

            await Task.Delay(FreqTable.TryGetValue(userName, out var freq) ? 1000 / freq : 1000 / 100, cancellationToken);
        }

        if (cntFail >= 5)
        {
            logger.Error($"User {userName} boundary scan failed for device {jtagCtrl.address} after 5 attempts");
            throw new InvalidOperationException("Boundary scan failed");
        }
    }

}
