using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Editor.GON.StageEditor;
using Intrepid.Gameplay.Journey;
using UnityEditor;
using UnityEngine;

namespace Editor.GON.WorldEditor
{
    public static class StageDefinitionSyncer
    {
        public const int ContractVersion = 1;

        public static StageDefinition CreateOrSyncStageDefinition(WorldLayoutSO layout, WorldStageAuthoring stage)
        {
            if (layout is null || stage is null)
            {
                return null;
            }

            layout.EnsureDefaults();
            stage.EnsureDefaults();

            var definition = stage.stageDefinition;
            if (definition is null)
            {
                definition = CreateStageDefinitionAsset(layout, stage);
                if (definition is null)
                {
                    return null;
                }

                stage.stageDefinition = definition;
                EditorUtility.SetDirty(layout);
            }

            SyncStageDefinition(layout, stage, definition);
            return definition;
        }

        public static void SyncStageDefinition(WorldLayoutSO layout, WorldStageAuthoring stage, StageDefinition definition)
        {
            if (layout is null || stage is null || definition is null)
            {
                return;
            }

            Undo.RecordObject(definition, "Sync stage definition world contract");
            definition.EnsureDefaults();
            definition.worldSync = BuildSyncData(layout, stage);
            EditorUtility.SetDirty(definition);
            AssetDatabase.SaveAssets();
        }

        public static StageWorldSyncData BuildSyncData(WorldLayoutSO layout, WorldStageAuthoring stage)
        {
            layout.EnsureDefaults();
            stage.EnsureDefaults();

            var data = new StageWorldSyncData
            {
                worldLayoutId = layout.id,
                worldStageId = stage.id,
                worldStageDisplayName = stage.displayName,
                worldStageKind = stage.stageKind,
                worldLayer = stage.worldLayer,
                worldTileSize = layout.worldTileSize,
                worldLayerHeight = layout.worldLayerHeight,
                syncedAt = DateTime.UtcNow.ToString("O"),
                contractVersion = ContractVersion
            };

            foreach (var tile in stage.occupiedTiles)
            {
                data.worldTiles.Add(tile);
            }

            foreach (var endpoint in GetStageEndpoints(layout, stage.id))
            {
                data.expectedEndpoints.Add(BuildEndpoint(layout, endpoint));
            }

            data.contractHash = ComputeContractHash(data);
            return data;
        }

        public static string ComputeExpectedContractHash(WorldLayoutSO layout, WorldStageAuthoring stage)
        {
            var data = BuildSyncData(layout, stage);
            return data.contractHash;
        }

        public static string ComputeContractHash(StageWorldSyncData data)
        {
            if (data is null)
            {
                return string.Empty;
            }

            data.EnsureDefaults();
            var builder = new StringBuilder();
            builder.Append("v=").Append(ContractVersion).Append('\n');
            builder.Append("layout=").Append(data.worldLayoutId).Append('\n');
            builder.Append("stage=").Append(data.worldStageId).Append('\n');
            builder.Append("name=").Append(data.worldStageDisplayName).Append('\n');
            builder.Append("kind=").Append((int)data.worldStageKind).Append('\n');
            builder.Append("layer=").Append(data.worldLayer).Append('\n');
            builder.Append("tileSize=").Append(data.worldTileSize.ToString("R", CultureInfo.InvariantCulture)).Append('\n');
            builder.Append("layerHeight=").Append(data.worldLayerHeight.ToString("R", CultureInfo.InvariantCulture)).Append('\n');

            var tiles = new List<JWorldTile>(data.worldTiles);
            tiles.Sort(CompareTiles);
            foreach (var tile in tiles)
            {
                builder.Append("tile=").Append(tile.x).Append(',').Append(tile.y).Append('\n');
            }

            var endpoints = new List<StageWorldSyncEndpoint>(data.expectedEndpoints);
            endpoints.Sort(CompareEndpoints);
            foreach (var endpoint in endpoints)
            {
                builder.Append("endpoint=")
                    .Append(endpoint.endpointId).Append('|')
                    .Append((int)endpoint.kind).Append('|')
                    .Append(endpoint.worldTile.x).Append(',').Append(endpoint.worldTile.y).Append('|')
                    .Append((int)endpoint.socketFace).Append('|')
                    .Append(endpoint.required ? "1" : "0").Append('|')
                    .Append(endpoint.connectionId).Append('|')
                    .Append(endpoint.connectedEndpointId).Append('|')
                    .Append(endpoint.debugLabel).Append('\n');
            }

            using var sha = SHA256.Create();
            var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
            var hash = new StringBuilder(bytes.Length * 2);
            foreach (var b in bytes)
            {
                hash.Append(b.ToString("x2"));
            }

            return hash.ToString();
        }

        private static StageDefinition CreateStageDefinitionAsset(WorldLayoutSO layout, WorldStageAuthoring stage)
        {
            var layoutPath = AssetDatabase.GetAssetPath(layout);
            if (string.IsNullOrEmpty(layoutPath))
            {
                Debug.LogError("Cannot create StageDefinition because the WorldLayoutSO is not saved as an asset.");
                return null;
            }

            var layoutDirectory = Path.GetDirectoryName(layoutPath)?.Replace('\\', '/');
            if (string.IsNullOrEmpty(layoutDirectory))
            {
                return null;
            }

            var stagesFolder = layoutDirectory + "/Stages";
            if (!AssetDatabase.IsValidFolder(stagesFolder))
            {
                AssetDatabase.CreateFolder(layoutDirectory, "Stages");
            }

            var fileName = MakeSafeFileName(string.IsNullOrWhiteSpace(stage.displayName) ? stage.id : stage.displayName);
            var path = AssetDatabase.GenerateUniqueAssetPath(stagesFolder + "/" + fileName + ".stage-definition.asset");

            var definition = ScriptableObject.CreateInstance<StageDefinition>();
            definition.Id = Path.GetFileNameWithoutExtension(path).ToLowerInvariant();
            definition.EnsureDefaults();
            AssetDatabase.CreateAsset(definition, path);
            return definition;
        }

        private static StageWorldSyncEndpoint BuildEndpoint(WorldLayoutSO layout, WorldEndpointAuthoring endpoint)
        {
            var connection = FindFirstConnection(layout, endpoint.id);
            var connectedEndpointId = connection is null
                ? string.Empty
                : connection.endpointAId == endpoint.id
                    ? connection.endpointBId
                    : connection.endpointAId;

            return new StageWorldSyncEndpoint
            {
                endpointId = endpoint.id,
                kind = endpoint.kind,
                worldTile = endpoint.worldTile,
                socketFace = endpoint.socketFace,
                required = endpoint.required,
                connectionId = connection?.id ?? string.Empty,
                connectedEndpointId = connectedEndpointId,
                debugLabel = endpoint.kind + " " + endpoint.id
            };
        }

        private static List<WorldEndpointAuthoring> GetStageEndpoints(WorldLayoutSO layout, string stageId)
        {
            var endpoints = new List<WorldEndpointAuthoring>();
            foreach (var endpoint in layout.endpoints)
            {
                if (endpoint != null && endpoint.stageId == stageId)
                {
                    endpoints.Add(endpoint);
                }
            }

            endpoints.Sort((a, b) => string.CompareOrdinal(a.id, b.id));
            return endpoints;
        }

        private static WorldConnectionAuthoring FindFirstConnection(WorldLayoutSO layout, string endpointId)
        {
            foreach (var connection in layout.connections)
            {
                if (connection != null &&
                    (connection.endpointAId == endpointId || connection.endpointBId == endpointId))
                {
                    return connection;
                }
            }

            return null;
        }

        private static int CompareTiles(JWorldTile a, JWorldTile b)
        {
            var x = a.x.CompareTo(b.x);
            return x != 0 ? x : a.y.CompareTo(b.y);
        }

        private static int CompareEndpoints(StageWorldSyncEndpoint a, StageWorldSyncEndpoint b)
        {
            return string.CompareOrdinal(a?.endpointId, b?.endpointId);
        }

        private static string MakeSafeFileName(string value)
        {
            var safe = new StringBuilder();
            foreach (var c in value)
            {
                safe.Append(char.IsLetterOrDigit(c) || c == '-' || c == '_' ? c : '-');
            }

            var result = safe.ToString().Trim('-');
            return string.IsNullOrEmpty(result) ? "StageDefinition" : result;
        }
    }
}
