using RobotNet.MapShares.Dtos;
using RobotNet.MapShares.Enums;
using RobotNet.RobotManager.HubClients;
using RobotNet.Shares;

namespace RobotNet.RobotManager.Services;

public class MapManager(IServiceProvider ServiceProvider, LoggerController<MapManager> Logger) : BackgroundService
{
    private MapHubClient? MapHub;
    private readonly Dictionary<Guid, MapDataDto> MapData = [];

    public async Task<MessageResult<MapDataDto?>> GetMapData(Guid mapId)
    {
        try
        {
            if (MapData.TryGetValue(mapId, out MapDataDto? mapData) && mapData is not null)
            {
                if (!mapData.Active) return new(false, "Bản đồ chưa được active");
                return new(true) { Data = mapData };
            }
            if (MapHub is null || !MapHub.IsConnected)
                return new(false, "Chưa khởi tạo kết nối với quản lí bản đồ");
            var mapDataResult = await MapHub.GetMapData(mapId);
            if (mapDataResult is not null && mapDataResult.Data is not null)
            {
                if (!mapDataResult.IsSuccess) return new(false, mapDataResult.Message);
                MapData[mapId] = mapDataResult.Data;
                return new(true) { Data = mapDataResult.Data };
            }
            return new(false, "Không thể lấy dữ liệu bản đồ");
        }
        catch (Exception ex)
        {
            Logger.Warning($"GetMapData: Lấy dữ liệu bản đồ có lỗi xảy ra: {ex.Message}");
            return new(false, $"GetMapData: Lấy dữ liệu bản đồ có lỗi xảy ra: {ex.Message}");
        }
    }

    public async Task<MessageResult<MapInfoDto>> GetMapInfo(Guid mapId)
    {
        try
        {
            if (MapHub is null || !MapHub.IsConnected)
                return new(false, "Chưa khởi tạo kết nối với quản lí bản đồ");
            var mapInfoResult =await MapHub.GetMapInfoById(mapId);
            if (mapInfoResult is not null && mapInfoResult.Data is not null)
            {
                if (!mapInfoResult.IsSuccess) return new(false, mapInfoResult.Message);
                return new(true) { Data = mapInfoResult.Data };
            }
            return new(false, "Không thể lấy dữ liệu bản đồ");
        }
        catch (Exception ex)
        {
            Logger.Warning($"GetMapInfo: Lấy dữ liệu bản đồ có lỗi xảy ra: {ex.Message}");
            return new(false, $"GetMapInfo: Lấy dữ liệu bản đồ có lỗi xảy ra: {ex.Message}");
        }
    }

    public async Task<MessageResult<MapInfoDto>> GetMapInfo(string mapName)
    {
        try
        {
            if (MapHub is null || !MapHub.IsConnected)
                return new(false, "Chưa khởi tạo kết nối với quản lí bản đồ");
            var mapInfoResult = await MapHub.GetMapInfoByName(mapName);
            if (mapInfoResult is not null && mapInfoResult.Data is not null)
            {
                if (!mapInfoResult.IsSuccess) return new(false, mapInfoResult.Message);
                return new(true) { Data = mapInfoResult.Data };
            }
            return new(false, "Không thể lấy dữ liệu bản đồ");
        }
        catch (Exception ex)
        {
            Logger.Warning($"GetMapInfo: Lấy dữ liệu bản đồ có lỗi xảy ra: {ex.Message}");
            return new(false, $"GetMapInfo: Lấy dữ liệu bản đồ có lỗi xảy ra: {ex.Message}");
        }
    }

    public NodeDto[] GetNegativeNodes(Guid mapId, Guid nodeId)
    {
        var mapData = Task.Run(async () => await GetMapData(mapId));
        mapData.Wait(); 
        if (mapData is null || !mapData.Result.IsSuccess || mapData.Result.Data is null) return [];
        var map = mapData.Result.Data;  
        var ListNodesNegative = new List<NodeDto>();
        var ListPaths = map.Edges.Where(p => p.EndNodeId == nodeId || p.StartNodeId == nodeId);
        foreach (var path in ListPaths)
        {
            if (path.StartNodeId == nodeId && (path.DirectionAllowed == DirectionAllowed.Both || path.DirectionAllowed == DirectionAllowed.Forward))
            {
                var nodeAdd = map.Nodes.FirstOrDefault(p => p.Id == path.EndNodeId);
                if (nodeAdd is not null) ListNodesNegative.Add(nodeAdd);
                continue;
            }
            if (path.EndNodeId == nodeId && (path.DirectionAllowed == DirectionAllowed.Both || path.DirectionAllowed == DirectionAllowed.Backward))
            {
                var nodeAdd = map.Nodes.FirstOrDefault(p => p.Id == path.StartNodeId);
                if (nodeAdd is not null) ListNodesNegative.Add(nodeAdd);
                continue;
            }
        }
        return [.. ListNodesNegative];
    }

    public EdgeDto[] GetEdges(Guid mapId, NodeDto[] nodes)
    {
        if (nodes.Length < 2) return [];
        if (MapData.TryGetValue(mapId, out MapDataDto? mapData) && mapData is not null)
        {
            List<EdgeDto> edges = [];
            for (int i = 0; i < nodes.Length - 1; i++)
            {
                var edge = mapData.Edges.FirstOrDefault(e => e.StartNodeId == nodes[i].Id && e.EndNodeId == nodes[i + 1].Id ||
                                                        e.EndNodeId == nodes[i].Id && e.StartNodeId == nodes[i + 1].Id);
                if (edge is null)
                {
                    if (i != 0) return [];
                    edges.Add(new EdgeDto()
                    {
                        Id = Guid.NewGuid(),
                        StartNodeId = nodes[i].Id,
                        EndNodeId = nodes[i + 1].Id,
                        DirectionAllowed = DirectionAllowed.Both,
                        TrajectoryDegree = TrajectoryDegree.One,
                    });
                    continue;
                }
                bool isReverse = nodes[i].Id != edge.StartNodeId && edge.TrajectoryDegree == TrajectoryDegree.Three;
                edges.Add(new()
                {
                    Id = edge.Id,
                    StartNodeId = nodes[i].Id,
                    EndNodeId = nodes[i + 1].Id,
                    DirectionAllowed = edge.DirectionAllowed,
                    TrajectoryDegree = edge.TrajectoryDegree,
                    ControlPoint1X = isReverse ? edge.ControlPoint2X : edge.ControlPoint1X,
                    ControlPoint1Y = isReverse ? edge.ControlPoint2Y : edge.ControlPoint1Y,
                    ControlPoint2X = isReverse ? edge.ControlPoint1X : edge.ControlPoint2X,
                    ControlPoint2Y = isReverse ? edge.ControlPoint1Y : edge.ControlPoint2Y
                });
            }
            return [.. edges];
        }
        return [];
    }

    public EdgeDto? GetEdge(Guid startNodeId, Guid endNodeId, Guid mapId)
    {
        if (MapData.TryGetValue(mapId, out MapDataDto? mapData) && mapData is not null)
        {
            var edge = mapData.Edges.FirstOrDefault(e => (e.StartNodeId == startNodeId && e.EndNodeId == endNodeId) || (e.StartNodeId == endNodeId && e.EndNodeId == startNodeId));
            if (edge is not null)
            {
                bool isReverse = startNodeId != edge.StartNodeId && edge.TrajectoryDegree == TrajectoryDegree.Three;
                return new EdgeDto
                {
                    Id = edge.Id,
                    StartNodeId = startNodeId,
                    EndNodeId = endNodeId,
                    TrajectoryDegree = edge.TrajectoryDegree,
                    ControlPoint1X = isReverse ? edge.ControlPoint2X : edge.ControlPoint1X,
                    ControlPoint1Y = isReverse ? edge.ControlPoint2Y : edge.ControlPoint1Y,
                    ControlPoint2X = isReverse ? edge.ControlPoint1X : edge.ControlPoint2X,
                    ControlPoint2Y = isReverse ? edge.ControlPoint1Y : edge.ControlPoint2Y
                };
            }
        }
        return null;
    }

    public NodeDto[][] GetNegativePaths(Guid mapId, NodeDto node, double distance)
    {
        var negativePaths = new Dictionary<Guid, NodeDto[]>();
        var currentPath = new List<NodeDto> { node };
        var visitedNodes = new Dictionary<Guid, double> { { node.Id, 0 } };

        void DFS(NodeDto currentNode, double currentDistance, NodeDto[] path)
        {
            var negatvieNodes = GetNegativeNodes(mapId, currentNode.Id);
            foreach (var negativeNode in negatvieNodes)
            {
                if (IsHasElement(mapId, negativeNode.Id)) continue;
                NodeDto[] newPath = [.. path, negativeNode];
                var newDistance = currentDistance + Math.Sqrt(Math.Pow(currentNode.X - negativeNode.X, 2) + Math.Pow(currentNode.Y - negativeNode.Y, 2));
                if (visitedNodes.TryGetValue(negativeNode.Id, out double oldDistance))
                {
                    if (oldDistance > newDistance)
                    {
                        visitedNodes.Remove(negativeNode.Id);
                        visitedNodes.Add(negativeNode.Id, newDistance);
                        negativePaths.Remove(negativeNode.Id);
                        if (newDistance >= distance) negativePaths.Add(negativeNode.Id, [.. newPath]);
                        else DFS(negativeNode, newDistance, newPath);
                    }
                }
                else
                {
                    visitedNodes.Add(negativeNode.Id, newDistance);
                    if (newDistance >= distance) negativePaths.Add(negativeNode.Id, [.. newPath]);
                    else DFS(negativeNode, newDistance, newPath);
                }
            }
        }
        DFS(node, 0, [.. currentPath]);
        //foreach (var key in negativePaths.Keys.ToList())
        //{
        //    var path = negativePaths[key];
        //    if (path != null && path.Length > 0) negativePaths[key] = [..path.Skip(1)];
        //    else negativePaths.Remove(key);
        //}
        return [.. negativePaths.Values];
    }

    private bool IsHasElement(Guid mapId, Guid nodeId)
    {
        var mapData = Task.Run(async () => await GetMapData(mapId));
        mapData.Wait();
        if (mapData is null || !mapData.Result.IsSuccess || mapData.Result.Data is null) return false;
        var map = mapData.Result.Data;
        return map.Elements.Any(e => e.NodeId == nodeId);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await Task.Yield();

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                using var scope = ServiceProvider.CreateAsyncScope();
                MapHub = scope.ServiceProvider.GetRequiredService<MapHubClient>();

                MapHub.MapUpdated += MapHub_Updated;
                await MapHub.StartAsync();
                break;
            }

            catch (Exception ex)
            {
                Logger.Warning($"Map Manager Execute: Khởi tạo kết nối với MapManager có lỗi xảy ra: {ex.Message}");
                await Task.Delay(2000, stoppingToken);
            }
        }
    }

    private void MapHub_Updated(Guid mapId)
    {
        Logger.Info($"Map update active: {mapId}");
        if (MapHub is null || !MapHub.IsConnected) return;
        var mapDataResult = MapHub.GetMapData(mapId).Result;
        if (mapDataResult is not null && mapDataResult.Data is not null)
        {
            if (!mapDataResult.IsSuccess) return;
            MapData[mapId] = mapDataResult.Data;
        }
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        if (MapHub is not null && MapHub.IsConnected) await MapHub.StopAsync();
        await base.StopAsync(cancellationToken);
    }
}
