using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using RobotNet.MapManager.Data;
using RobotNet.MapManager.Hubs;
using RobotNet.MapManager.Services;
using RobotNet.MapShares.Dtos;
using RobotNet.MapShares.Enums;
using RobotNet.Shares;

namespace RobotNet.MapManager.Controllers;

[Route("api/[controller]")]
[ApiController]
[Authorize]
public class MapsManagerController(MapEditorDbContext MapDb, MapEditorStorageRepository StorageRepo, IHubContext<MapHub> MapHub, LoggerController<MapsManagerController> Logger) : ControllerBase
{
    [HttpGet]
    public async Task<IEnumerable<MapInfoDto>> GetMapInfos([FromQuery(Name = "txtSearch")] string? txtSearch)
    {
        try
        {
            if (string.IsNullOrWhiteSpace(txtSearch))
            {
                return await (from map in MapDb.Maps
                              select new MapInfoDto()
                              {
                                  Id = map.Id,
                                  VersionId = map.VersionId,
                                  Name = map.Name,
                                  Description = map.Description,
                                  Active = map.Active,
                                  OriginX = map.OriginX,
                                  OriginY = map.OriginY,
                                  Width = Math.Round(map.ImageWidth * map.Resolution, 2),
                                  Height = Math.Round(map.ImageHeight * map.Resolution, 2),
                                  Resolution = map.Resolution,
                                  VDA5050 = map.VDA5050,
                              }).ToListAsync();
            }
            else
            {
                return await (from map in MapDb.Maps
                              where !string.IsNullOrEmpty(map.Name) && map.Name.Contains(txtSearch)
                              select new MapInfoDto()
                              {
                                  Id = map.Id,
                                  VersionId = map.VersionId,
                                  Name = map.Name,
                                  Description = map.Description,
                                  Active = map.Active,
                                  OriginX = map.OriginX,
                                  OriginY = map.OriginY,
                                  Width = Math.Round(map.ImageWidth * map.Resolution, 2),
                                  Height = Math.Round(map.ImageHeight * map.Resolution, 2),
                                  Resolution = map.Resolution,
                                  VDA5050 = map.VDA5050,
                              }).ToListAsync();
            }
        }
        catch (Exception ex)
        {
            Logger.Warning($"GetMapInfos: Hệ thống có lỗi xảy ra - {ex.Message}");
            return [];
        }
    }

    [HttpGet]
    [Route("{id}")]
    public async Task<MessageResult<MapInfoDto>> GetMapInfoId(Guid id)
    {
        var map = await MapDb.Maps.FirstOrDefaultAsync(m => m.Id == id);
        if (map == null) return new MessageResult<MapInfoDto>(false, $"Không tìm thấy map {id}");

        return new(true)
        {
            Data = new MapInfoDto()
            {
                Id = map.Id,
                Name = map.Name,
                Active = map.Active,
                OriginX = map.OriginX,
                OriginY = map.OriginY,
                Width = Math.Round(map.ImageWidth * map.Resolution, 2),
                Height = Math.Round(map.ImageHeight * map.Resolution, 2),
                Resolution = map.Resolution,
            },
        };
    }

    [HttpGet]
    [Route("info")]
    public async Task<MessageResult<MapInfoDto>> GetMapInfoName([FromQuery(Name = "name")] string name)
    {
        var map = await MapDb.Maps.FirstOrDefaultAsync(m => m.Name == name);
        if (map == null) return new MessageResult<MapInfoDto>(false, $"Không tìm thấy map {name}");

        return new(true)
        {
            Data = new MapInfoDto()
            {
                Id = map.Id,
                Name = map.Name,
                Active = map.Active,
                OriginX = map.OriginX,
                OriginY = map.OriginY,
                Width = Math.Round(map.ImageWidth * map.Resolution, 2),
                Height = Math.Round(map.ImageHeight * map.Resolution, 2),
                Resolution = map.Resolution,
            },
        };
    }

    [HttpPost]
    [Route("")]
    public async Task<MessageResult<MapInfoDto>> CreateMap([FromForm] MapCreateModel model, [FromForm(Name = "Image")] IFormFile imageUpload)
    {
        try
        {
            if (imageUpload is null) return new(false, "Dữ liệu không hợp lệ");
            if (imageUpload.ContentType != "image/png") return new(false, "Ảnh map chỉ hỗ trợ định dạng image/png");
            if (await MapDb.Maps.AnyAsync(map => map.Name == model.Name)) return new(false, "Tên của map đã tồn tại. Hãy đặt tên khác!");

            var image = SixLabors.ImageSharp.Image.Load(imageUpload.OpenReadStream());

            var entityMap = await MapDb.Maps.AddAsync(new Map()
            {
                Name = model.Name,
                OriginX = model.OriginX,
                OriginY = model.OriginY,
                Resolution = model.Resolution,
                ImageHeight = (ushort)image.Height,
                ImageWidth = (ushort)image.Width,
                Active = false,

                NodeCount = 0,
                NodeNameAutoGenerate = true,
                NodeNameTemplateDefault = "Node",
                NodeAllowedDeviationXyDefault = 0.1,
                NodeAllowedDeviationThetaDefault = 0,

                EdgeMinLengthDefault = 1,
                EdgeMaxHeightDefault = 1,
                EdgeMinHeightDefault = 0.1,
                EdgeStraightMaxSpeedDefault = 1,
                EdgeCurveMaxSpeedDefault = 0.3,
                EdgeMaxRotationSpeedDefault = 0.5,
                EdgeAllowedDeviationXyDefault = 0.1,
                EdgeAllowedDeviationThetaDefault = 0,
                EdgeRotationAllowedDefault = true,
                EdgeDirectionAllowedDefault = DirectionAllowed.Both,

                ZoneMinSquareDefault = 0.25,
            });

            await MapDb.SaveChangesAsync();

            var (isSuccess, message) = await StorageRepo.UploadAsync("MapImages", $"{entityMap.Entity.Id}", imageUpload.OpenReadStream(), imageUpload.Length, imageUpload.ContentType, CancellationToken.None);
            if (!isSuccess)
            {
                MapDb.Maps.Remove(entityMap.Entity);
                await MapDb.SaveChangesAsync();
                return new(false, message);
            }

            await MapDb.SaveChangesAsync();
            Logger.Info($"User {HttpContext.User.Identity?.Name} đã tạo bản đồ mới với tên: {model.Name}");

            return new(true)
            {
                Data = new MapInfoDto()
                {
                    Id = entityMap.Entity.Id,
                    Name = entityMap.Entity.Name,
                    Active = entityMap.Entity.Active,
                    OriginX = entityMap.Entity.OriginX,
                    OriginY = entityMap.Entity.OriginY,
                    Width = entityMap.Entity.ImageWidth * entityMap.Entity.Resolution,
                    Height = entityMap.Entity.ImageHeight * entityMap.Entity.Resolution,
                    Resolution = entityMap.Entity.Resolution,
                },
            };
        }
        catch (Exception ex)
        {
            Logger.Warning($"CreateMap: Hệ thống có lỗi xảy ra - {ex.Message}");
            return new(false, $"CreateMap: Hệ thống có lỗi xảy ra - {ex.Message}");
        }
    }

    [HttpPut]
    [Route("{id}")]
    public async Task<MessageResult<MapInfoDto>> UpdateMap(Guid id, [FromBody] MapUpdateModel model)
    {
        try
        {
            if (id != model.Id) return new(false, "Dữ liệu gửi không chính xác");

            var map = await MapDb.Maps.FindAsync(id);
            if (map == null) return new(false, $"Không tồn tại map id = {id}");

            if (map.Name != model.Name && await MapDb.Maps.AnyAsync(m => m.Name == model.Name))
            {
                return new(false, "Tên của map đã tồn tại, Hãy đặt tên khác!");
            }

            if (model.Resolution <= 0)
            {
                return new(false, "Độ phân giải của bản đồ phải lớn hơn 0");
            }

            map.Name = model.Name;
            bool originChange = map.OriginX != model.OriginX || map.OriginY != model.OriginY;
            if (originChange)
            {
                map.OriginX = model.OriginX;
                map.OriginY = model.OriginY;
            }
            if (map.Resolution != model.Resolution)
            {
                var scale = model.Resolution / map.Resolution;
                map.Resolution = model.Resolution;
                if (originChange)
                {
                    map.OriginX *= scale;
                    map.OriginY *= scale;
                }

                var nodes = await MapDb.Nodes.Where(n => n.MapId == map.Id).ToListAsync();
                foreach (var node in nodes)
                {
                    node.X *= scale;
                    node.Y *= scale;
                }

                var edges = await MapDb.Edges.Where(e => e.MapId == map.Id).ToListAsync();
                foreach (var edge in edges)
                {
                    edge.ControlPoint1X *= scale;
                    edge.ControlPoint1Y *= scale;
                    edge.ControlPoint2X *= scale;
                    edge.ControlPoint2Y *= scale;
                }
            }

            await MapDb.SaveChangesAsync();
            Logger.Info($"User {HttpContext.User.Identity?.Name} đã cập nhật thông tin bản đồ : {model.Id} - {map.Name}");

            return new(true)
            {
                Data = new()
                {
                    Id = id,
                    Name = map.Name,
                    OriginX = map.OriginX,
                    OriginY = map.OriginY,
                    Resolution = model.Resolution,
                    Width = Math.Round(map.ImageWidth * map.Resolution, 2),
                    Height = Math.Round(map.ImageHeight * map.Resolution, 2),
                },
            };
        }
        catch (Exception ex)
        {
            Logger.Warning($"UpdateMap {id}: Hệ thống có lỗi xảy ra - {ex.Message}");
            return new(false, $"UpdateMap {id}: Hệ thống có lỗi xảy ra - {ex.Message}");
        }
    }

    [HttpDelete]
    [Route("{id}")]
    public async Task<MessageResult> DeleteMap(Guid id)
    {
        try
        {
            var map = await MapDb.Maps.FindAsync(id);
            if (map == null) return new(false, $"Không tồn tại map id = {id}");

            MapDb.Elements.RemoveRange(MapDb.Elements.Where(e => e.MapId == map.Id));
            MapDb.ElementModels.RemoveRange(MapDb.ElementModels.Where(em => em.MapId == map.Id));
            MapDb.Edges.RemoveRange(MapDb.Edges.Where(edge => edge.MapId == map.Id));
            MapDb.Nodes.RemoveRange(MapDb.Nodes.Where(node => node.MapId == map.Id));
            MapDb.Zones.RemoveRange(MapDb.Zones.Where(zone => zone.MapId == map.Id));
            MapDb.Actions.RemoveRange(MapDb.Actions.Where(action => action.MapId == map.Id));
            MapDb.Maps.Remove(map);
            await MapDb.SaveChangesAsync();

            await StorageRepo.DeleteAsync("MapImages", $"{id}", CancellationToken.None);

            Logger.Info($"User {HttpContext.User.Identity?.Name} đã xóa bản đồ {map.Name}");

            return new(true);
        }
        catch (Exception ex)
        {
            Logger.Warning($"DeleteMap {id}: Hệ thống có lỗi xảy ra - {ex.Message}");
            return new(false, $"DeleteMap {id}: Hệ thống có lỗi xảy ra - {ex.Message}");
        }
    }

    [HttpPut]
    [Route("active")]
    public async Task<MessageResult> ActiveToggle([FromBody] MapActiveModel model)
    {
        var map = await MapDb.Maps.FindAsync(model.Id);
        if (map == null) return new(false, $"Không tồn tại map id = {model.Id}");

        map.Active = model.Active;
        await MapDb.SaveChangesAsync();

        await MapHub.Clients.All.SendAsync("MapUpdated", model.Id);

        Logger.Info($"User {HttpContext.User.Identity?.Name} đã thay đổi trạng thái active bản đồ {map.Name}: {model.Active}");

        return new(true);
    }

    [HttpPut]
    [Route("image/{id}")]
    public async Task<MessageResult> UpdateImage(Guid id, [FromForm(Name = "image")] IFormFile image)
    {
        try
        {
            var map = await MapDb.Maps.FindAsync(id);
            if (map == null) return new(false, $"Không tồn tại map id = {id}");

            var imageStream = image.OpenReadStream();
            var imageUpdate = SixLabors.ImageSharp.Image.Load(imageStream);
            map.ImageWidth = (ushort)imageUpdate.Width;
            map.ImageHeight = (ushort)imageUpdate.Height;
            await MapDb.SaveChangesAsync();
            var (isSuccess, message) = await StorageRepo.UploadAsync("MapImages", $"{id}", image.OpenReadStream(), image.Length, image.ContentType, CancellationToken.None);

            Logger.Info($"User {HttpContext.User.Identity?.Name} đã thay đổi ảnh của bản đồ {map.Name}");

            return new(true);
        }
        catch (Exception ex)
        {
            Logger.Warning($"UpdateImage {id}: Hệ thống có lỗi xảy ra - {ex.Message}");
            return new(false, $"UpdateImage {id}: Hệ thống có lỗi xảy ra - {ex.Message}");
        }
    }

    [HttpPost]
    [Route("import")]
    public async Task<MessageResult<MapInfoDto>> ImportNewMap([FromBody] MapExportDto model)
    {
        if (model == null || model.Data == null || model.Data.ImageData == null || model.Data.ImageData.Length == 0)
        {
            return new(false, "Dữ liệu đầu vào không hợp lệ");
        }

        using var transaction = await MapDb.Database.BeginTransactionAsync();
        try
        {
            if (await MapDb.Maps.AnyAsync(map => map.Name == model.Name)) return new(false, "Tên của map đã tồn tại, Hãy đặt tên khác!");

            using var stream = new MemoryStream(model.Data.ImageData);
            var imageFile = new FormFile(stream, 0, model.Data.ImageData.Length, "", $"{model.Name}.png")
            {
                Headers = new HeaderDictionary(),
                ContentType = "image/png",
            };
            var image = SixLabors.ImageSharp.Image.Load(imageFile.OpenReadStream());

            var entityMap = await MapDb.Maps.AddAsync(new Map()
            {
                Name = model.Name,
                Description = model.Description,
                OriginX = model.Info.OriginX,
                OriginY = model.Info.OriginY,
                Resolution = model.Info.Resolution,
                ImageHeight = (ushort)image.Height,
                ImageWidth = (ushort)image.Width,
                Active = false,
                ViewX = model.Info.ViewX,
                ViewY = model.Info.ViewY,
                ViewWidth = model.Info.ViewWidth,
                ViewHeight = model.Info.ViewHeight,
                VDA5050 = model.Info.VDA5050,

                NodeCount = model.Data.NodeCount,
                NodeNameAutoGenerate = model.Setting.NodeNameAutoGenerate,
                NodeNameTemplateDefault = model.Setting.NodeNameTemplate,
                NodeAllowedDeviationXyDefault = model.Setting.NodeAllowedDeviationXy,
                NodeAllowedDeviationThetaDefault = model.Setting.NodeAllowedDeviationTheta,

                EdgeMinLengthDefault = model.Setting.EdgeMinLength,
                EdgeMaxHeightDefault = model.Setting.EdgeMaxHeight,
                EdgeMinHeightDefault = model.Setting.EdgeMinHeight,
                EdgeStraightMaxSpeedDefault = model.Setting.EdgeStraightMaxSpeed,
                EdgeCurveMaxSpeedDefault = model.Setting.EdgeCurveMaxSpeed,
                EdgeMaxRotationSpeedDefault = model.Setting.EdgeMaxRotationSpeed,
                EdgeAllowedDeviationXyDefault = model.Setting.EdgeAllowedDeviationXy,
                EdgeAllowedDeviationThetaDefault = model.Setting.EdgeAllowedDeviationTheta,
                EdgeRotationAllowedDefault = model.Setting.EdgeRotationAllowed,
                EdgeDirectionAllowedDefault = model.Setting.EdgeDirectionAllowed,

                ZoneMinSquareDefault = model.Setting.ZoneMinSquare,
            });

            var (isSuccess, message) = await StorageRepo.UploadAsync("MapImages", $"{entityMap.Entity.Id}", imageFile.OpenReadStream(), imageFile.Length, imageFile.ContentType, CancellationToken.None);
            if (!isSuccess)
            {
                await transaction.RollbackAsync();
                return new(false, message);
            }

            Dictionary<Guid, Guid> actionSwap = [];
            foreach (var action in model.Data.Actions)
            {
                var actionDb = await MapDb.Actions.AddAsync(new Data.Action()
                {
                    MapId = entityMap.Entity.Id,
                    Name = action.Name,
                    Content = action.Content,
                });
                actionSwap.Add(action.Id, actionDb.Entity.Id);
            }

            Dictionary<Guid, Guid> nodeSwap = [];
            foreach (var node in model.Data.Nodes)
            {
                var actions = System.Text.Json.JsonSerializer.Deserialize<Guid[]>(node.Actions ?? "");
                List<Guid> newActions = [];
                if (actions is not null && actions.Length > 0)
                {
                    foreach (var actionId in actions)
                    {
                        if (actionSwap.TryGetValue(actionId, out Guid newActionId) && newActionId != Guid.Empty) newActions.Add(newActionId);
                    }
                }
                var nodeDb = await MapDb.Nodes.AddAsync(new Node()
                {
                    Name = node.Name,
                    MapId = entityMap.Entity.Id,
                    X = node.X,
                    Y = node.Y,
                    Theta = node.Theta,
                    AllowedDeviationXy = node.AllowedDeviationXy,
                    AllowedDeviationTheta = node.AllowedDeviationTheta,
                    Actions = System.Text.Json.JsonSerializer.Serialize(newActions),
                });
                nodeSwap.Add(node.Id, nodeDb.Entity.Id);
            }

            var Edges = model.Data.Edges.Select(e => new Edge()
            {
                MapId = entityMap.Entity.Id,
                StartNodeId = nodeSwap[e.StartNodeId],
                EndNodeId = nodeSwap[e.EndNodeId],
                ControlPoint1X = e.ControlPoint1X,
                ControlPoint1Y = e.ControlPoint1Y,
                ControlPoint2X = e.ControlPoint2X,
                ControlPoint2Y = e.ControlPoint2Y,
                TrajectoryDegree = e.TrajectoryDegree,
                MaxHeight = e.MaxHeight,
                MinHeight = e.MinHeight,
                MaxSpeed = e.MaxSpeed,
                MaxRotationSpeed = e.MaxRotationSpeed,
                AllowedDeviationXy = e.AllowedDeviationXy,
                AllowedDeviationTheta = e.AllowedDeviationTheta,
                DirectionAllowed = e.DirectionAllowed,
                RotationAllowed = e.RotationAllowed,
                Actions = e.Actions,
            }).ToList();

            var Zones = model.Data.Zones.Select(z => new Zone()
            {
                MapId = entityMap.Entity.Id,
                Type = z.Type,
                X1 = z.X1,
                X2 = z.X2,
                Y1 = z.Y1,
                Y2 = z.Y2,
                X3 = z.X3,
                X4 = z.X4,
                Y3 = z.Y3,
                Y4 = z.Y4,
            }).ToList();

            Dictionary<Guid, Guid> elementModelSwap = [];
            foreach (var elementModel in model.Data.ElementModels)
            {
                var elementModelDb = await MapDb.ElementModels.AddAsync(new ElementModel()
                {
                    MapId = entityMap.Entity.Id,
                    Name = elementModel.Name,
                    Height = elementModel.Height,
                    Width = elementModel.Width,
                    Image1Height = elementModel.Image1Height,
                    Image1Width = elementModel.Image1Width,
                    Image2Height = elementModel.Image2Height,
                    Image2Width = elementModel.Image2Width,
                    Content = elementModel.Content,
                });
                elementModelSwap.Add(elementModel.Id, elementModelDb.Entity.Id);

                using var openStream = new MemoryStream(elementModel.ImageOpenData);
                var imageOpenFile = new FormFile(openStream, 0, elementModel.ImageOpenData.Length, "", $"{elementModel.Name}O.png")
                {
                    Headers = new HeaderDictionary(),
                    ContentType = "image/png",
                };
                await StorageRepo.UploadAsync("ElementOpenModels", $"{elementModelDb.Entity.Id}", imageOpenFile.OpenReadStream(), imageOpenFile.Length, imageOpenFile.ContentType, CancellationToken.None);

                using var closeStream = new MemoryStream(elementModel.ImageCloseData);
                var imageCloseFile = new FormFile(closeStream, 0, elementModel.ImageCloseData.Length, "", $"{elementModel.Name}C.png")
                {
                    Headers = new HeaderDictionary(),
                    ContentType = "image/png",
                };
                await StorageRepo.UploadAsync("ElementCloseModels", $"{elementModelDb.Entity.Id}", imageCloseFile.OpenReadStream(), imageCloseFile.Length, imageCloseFile.ContentType, CancellationToken.None);
            }

            var Elements = model.Data.Elements.Select(e => new Data.Element()
            {
                MapId = entityMap.Entity.Id,
                Name = e.Name,
                IsOpen = e.IsOpen,
                ModelId = elementModelSwap[e.ModelId],
                Content = e.Content,
                NodeId = nodeSwap[e.NodeId],
                OffsetX = e.OffsetX,
                OffsetY = e.OffsetY,
            }).ToList();

            if (Edges.Count > 0) await MapDb.Edges.AddRangeAsync(Edges);
            if (Zones.Count > 0) await MapDb.Zones.AddRangeAsync(Zones);
            if (Elements.Count > 0) await MapDb.Elements.AddRangeAsync(Elements);

            await MapDb.SaveChangesAsync();
            await transaction.CommitAsync();

            return new(true)
            {
                Data = new MapInfoDto()
                {
                    Id = entityMap.Entity.Id,
                    Name = entityMap.Entity.Name,
                    Active = entityMap.Entity.Active,
                    OriginX = entityMap.Entity.OriginX,
                    OriginY = entityMap.Entity.OriginY,
                    Width = entityMap.Entity.ImageWidth * entityMap.Entity.Resolution,
                    Height = entityMap.Entity.ImageHeight * entityMap.Entity.Resolution,
                    Resolution = entityMap.Entity.Resolution,
                }
            };
        }
        catch (IOException ex)
        {
            await transaction.RollbackAsync();
            Logger.Warning($"ImportNewMap: Lỗi khi xử lý hình ảnh: {ex.Message}");
            return new(false, $"ImportNewMap: Lỗi khi xử lý hình ảnh: {ex.Message}");
        }
        catch (DbUpdateException ex)
        {
            await transaction.RollbackAsync();
            Logger.Warning($"ImportNewMap: Lỗi khi lưu vào database: {ex.Message}");
            return new(false, $"ImportNewMap: Lỗi khi lưu vào database: {ex.Message}");
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync();
            Logger.Warning($"ImportNewMap: Hệ thống có lỗi xảy ra - {ex.Message}");
            return new(false, $"ImportNewMap: Hệ thống có lỗi xảy ra - {ex.Message}");
        }
    }
}
