using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RobotNet.MapManager.Data;
using RobotNet.MapManager.Services;
using RobotNet.MapShares;
using RobotNet.MapShares.Dtos;
using RobotNet.Shares;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

namespace RobotNet.MapManager.Controllers;

[Route("api/[controller]")]
[ApiController]
[Authorize]
public class MapExportController(MapEditorDbContext MapDb, MapEditorStorageRepository StorageRepo, IHttpClientFactory HttpClientFactory, LoggerController<MapExportController> Logger) : ControllerBase
{
    private readonly byte[] Key = Encoding.UTF8.GetBytes("2512199802031998");
    private readonly byte[] IV = Encoding.UTF8.GetBytes("2512199802031998");

    [HttpGet]
    [Route("encrypt/{Id}")]
    public async Task<IActionResult> EncryptMap(Guid Id)
    {
        try
        {
            var map = await MapDb.Maps.FirstOrDefaultAsync(m => m.Id == Id);
            if (map is null) return NotFound($"Map {Id} không tồn tại");

            var (usingLocal, url) = StorageRepo.GetUrl("MapImages", $"{Id}");
            byte[] imageData = await GetImageDataAsync(usingLocal, url);

            var elementModels = MapDb.ElementModels.Where(n => n.MapId == Id);
            List<ElementModelExportDto> ElementModelExport = [];
            foreach (var elementModel in elementModels)
            {
                var getImageOpen = StorageRepo.GetUrl("ElementOpenModels", elementModel.Id.ToString());
                byte[] imageElementModelOpenData = await GetImageDataAsync(getImageOpen.usingLocal, getImageOpen.url);

                var getImageClose = StorageRepo.GetUrl("ElementCloseModels", elementModel.Id.ToString());
                byte[] imageElementModelCloseData = await GetImageDataAsync(getImageClose.usingLocal, getImageClose.url);

                ElementModelExport.Add(new ElementModelExportDto()
                {
                    Id = elementModel.Id,
                    Height = elementModel.Height,
                    Width = elementModel.Width,
                    Image1Height = elementModel.Image1Height,
                    Image2Height = elementModel.Image2Height,
                    Image1Width = elementModel.Image1Width,
                    Image2Width = elementModel.Image2Width,
                    Name = elementModel.Name,
                    Content = elementModel.Content,
                    ImageOpenData = imageElementModelOpenData,
                    ImageCloseData = imageElementModelCloseData,
                });
            }
            var nodes = MapDb.Nodes.Where(n => n.MapId == Id);
            var edges = MapDb.Edges.Where(n => n.MapId == Id);
            var zones = MapDb.Zones.Where(n => n.MapId == Id);
            var actions = MapDb.Actions.Where(n => n.MapId == Id);
            var elements = MapDb.Elements.Where(n => n.MapId == Id);

            var mapDto = new MapExportDto()
            {
                Id = Id,
                Name = map.Name,
                Description = map.Description,
                Info = new()
                {
                    OriginX = map.OriginX,
                    OriginY = map.OriginY,
                    Resolution = map.Resolution,
                    ViewX = map.ViewX,
                    ViewY = map.ViewY,
                    ViewWidth = map.ViewWidth,
                    ViewHeight = map.ViewHeight,
                    VDA5050 = map.VDA5050,
                },
                Setting = new()
                {
                    NodeNameAutoGenerate = map.NodeNameAutoGenerate,
                    NodeNameTemplate = map.NodeNameTemplateDefault,
                    NodeAllowedDeviationXy = map.EdgeAllowedDeviationXyDefault,
                    NodeAllowedDeviationTheta = map.EdgeAllowedDeviationThetaDefault,

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

                    ZoneMinSquare = map.ZoneMinSquareDefault,
                },
                Data = new()
                {
                    NodeCount = map.NodeCount,
                    Nodes = [.. nodes.Select(n => new NodeDto()
                    {
                        Id = n.Id,
                        Name = n.Name,
                        X = n.X,
                        Y = n.Y,
                        Theta = n.Theta,
                        AllowedDeviationXy = n.AllowedDeviationXy,
                        AllowedDeviationTheta = n.AllowedDeviationTheta,
                        Actions = n.Actions,
                    })],
                    Edges = [.. edges.Select(e => new EdgeDto()
                    {
                        ControlPoint1X = e.ControlPoint1X,
                        ControlPoint1Y = e.ControlPoint1Y,
                        ControlPoint2X = e.ControlPoint2X,
                        ControlPoint2Y = e.ControlPoint2Y,
                        TrajectoryDegree = e.TrajectoryDegree,
                        EndNodeId = e.EndNodeId,
                        StartNodeId = e.StartNodeId,
                        DirectionAllowed = e.DirectionAllowed,
                        RotationAllowed = e.RotationAllowed,
                        AllowedDeviationTheta = e.AllowedDeviationTheta,
                        AllowedDeviationXy = e.AllowedDeviationXy,
                        MaxHeight = e.MaxHeight,
                        MinHeight = e.MinHeight,
                        MaxSpeed = e.MaxSpeed,
                        MaxRotationSpeed = e.MaxRotationSpeed,
                        Actions = e.Actions,
                    })],
                    Zones = [.. zones.Select(z => new ZoneDto()
                    {
                        Type = z.Type,
                        X1 = z.X1,
                        Y1 = z.Y1,
                        X2 = z.X2,
                        Y2 = z.Y2,
                        X3 = z.X3,
                        Y3 = z.Y3,
                        X4 = z.X4,
                        Y4 = z.Y4,
                    })],
                    Actions = [.. actions.Select(a => new ActionDto()
                    {
                        Id = a.Id,
                        Name = a.Name,
                        Content = a.Content,
                    })],
                    ElementModels = [.. ElementModelExport],
                    Elements = [.. elements.Select(e => new ElementDto()
                    {
                        Name = e.Name,
                        IsOpen = e.IsOpen,
                        ModelId = e.ModelId,
                        NodeId = e.NodeId,
                        OffsetX = e.OffsetX,
                        OffsetY = e.OffsetY,
                        Content = e.Content,
                    })],
                    ImageData = imageData,
                }
            };

            var jsonData = JsonSerializer.Serialize(mapDto, JsonOptionExtends.Write);
            var data = EncryptDataAES(jsonData, Key, IV);
            return File(data, "application/octet-stream", $"{map.Name}.map");
        }
        catch (Exception ex)
        {
            Logger.Warning($"EncryptMap: Hệ thống có lỗi xảy ra - {ex.Message}");
            return NotFound("Hệ thống có lỗi xảy ra");
        }
    }

    [HttpPost]
    [Route("decrypt")]
    public async Task<MessageResult<MapExportDto>> DecryptMap([FromForm(Name = "importmap")] IFormFile file)
    {
        try
        {
            if (file == null || file.Length == 0) return new(false, "File không hợp lệ");
            if (!file.FileName.EndsWith(".map", StringComparison.OrdinalIgnoreCase)) return new(false, "Định dạng file không hợp lệ, yêu cầu file .map");
            using var memoryStream = new MemoryStream();
            await file.CopyToAsync(memoryStream);
            byte[] fileBytes = memoryStream.ToArray();

            var jsonData = DecryptDataAES(fileBytes, Key, IV);
            var mapData = JsonSerializer.Deserialize<MapExportDto>(jsonData, JsonOptionExtends.Read);
            if (mapData is null) return new(false, "Dữ liệu không hợp lệ");
            else return new(true) { Data = mapData };
        }
        catch (Exception ex)
        {
            Logger.Warning($"EncryptMap: Hệ thống có lỗi xảy ra - {ex.Message}");
            return new(false, $"EncryptMap: Hệ thống có lỗi xảy ra - {ex.Message}");
        }
    }

    private static byte[] EncryptDataAES(string data, byte[] key, byte[] iv)
    {
        using Aes aesAlg = Aes.Create();
        aesAlg.Key = key;
        aesAlg.IV = iv;

        ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

        using MemoryStream msEncrypt = new();
        using CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write);
        using (StreamWriter swEncrypt = new(csEncrypt))
        {
            swEncrypt.Write(data);
        }
        return msEncrypt.ToArray();
    }

    private static string DecryptDataAES(byte[] data, byte[] key, byte[] iv)
    {
        using Aes aesAlg = Aes.Create();
        aesAlg.Key = key;
        aesAlg.IV = iv;

        ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

        using MemoryStream msDecrypt = new(data);
        using CryptoStream csDecrypt = new(msDecrypt, decryptor, CryptoStreamMode.Read);
        using StreamReader srDecrypt = new(csDecrypt);
        return srDecrypt.ReadToEnd();
    }

    public async Task<byte[]> GetImageDataAsync(bool usingLocal, string url)
    {
        if (!usingLocal)
        {
            var http = HttpClientFactory.CreateClient();
            var response = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
            if (!response.IsSuccessStatusCode) return [];
            return await response.Content.ReadAsByteArrayAsync();
        }
        else
        {
            if (System.IO.File.Exists(url)) return System.IO.File.ReadAllBytes(url);
            return [];
        }
    }
}
