using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Intrepid.Gameplay.Journey;
using UnityEditor;

namespace Editor.GON.WorldEditor
{
    public static class JourneyCommitter
    {
        public static List<WorldValidationMessage> Commit(WorldLayoutSO layout, JGraphSO graph)
        {
            var messages = WorldLayoutValidator.Validate(layout);
            if (WorldLayoutValidator.HasErrors(messages) || graph is null)
            {
                if (graph is null)
                {
                    messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error, "No target JGraphSO selected."));
                }

                return messages;
            }

            layout.EnsureDefaults();
            graph.sourceWorldLayoutId = layout.id;
            graph.publishedAt = DateTime.UtcNow.ToString("O");
            graph.schemaVersion = 1;
            graph.stages = BuildStages(layout);
            graph.endpoints = BuildEndpoints(layout);
            graph.connections = BuildConnections(layout);
            graph.contentHash = ComputeContentHash(graph);
            graph.EnsureDefaults();

            EditorUtility.SetDirty(graph);
            AssetDatabase.SaveAssets();
            return messages;
        }

        private static List<JStage> BuildStages(WorldLayoutSO layout)
        {
            var stages = new List<JStage>();
            foreach (var source in layout.stages)
            {
                if (source is null)
                {
                    continue;
                }

                source.EnsureDefaults();
                var stage = new JStage
                {
                    id = source.id,
                    displayName = source.displayName,
                    worldLayer = source.worldLayer,
                    occupiedTiles = new List<JWorldTile>(source.occupiedTiles)
                };
                if (source.stageKind == WorldStageKind.SavePoint)
                {
                    stage.tags.Add(WorldLayoutSO.SavePointTag);
                }

                stages.Add(stage);
            }

            return stages;
        }

        private static List<JEndpoint> BuildEndpoints(WorldLayoutSO layout)
        {
            var endpoints = new List<JEndpoint>();
            foreach (var source in layout.endpoints)
            {
                if (source is null)
                {
                    continue;
                }

                endpoints.Add(new JEndpoint
                {
                    id = source.id,
                    stageId = source.stageId,
                    worldTile = source.worldTile,
                    kind = source.kind,
                    required = source.required,
                    socketFace = source.socketFace
                });
            }

            return endpoints;
        }

        private static List<JConnection> BuildConnections(WorldLayoutSO layout)
        {
            var connections = new List<JConnection>();
            foreach (var source in layout.connections)
            {
                if (source is null)
                {
                    continue;
                }

                connections.Add(new JConnection
                {
                    id = source.id,
                    endpointAId = source.endpointAId,
                    endpointBId = source.endpointBId,
                    directionality = source.directionality
                });
            }

            return connections;
        }

        private static string ComputeContentHash(JGraphSO graph)
        {
            var builder = new StringBuilder();
            builder.Append(graph.sourceWorldLayoutId).Append('|').Append(graph.schemaVersion).AppendLine();

            foreach (var stage in graph.stages)
            {
                builder.Append("S|").Append(stage.id).Append('|').Append(stage.displayName).Append('|')
                    .Append(stage.worldLayer).AppendLine();
                foreach (var tag in stage.tags)
                {
                    builder.Append("G|").Append(tag).AppendLine();
                }

                foreach (var tile in stage.occupiedTiles)
                {
                    builder.Append("T|").Append(tile.x).Append('|').Append(tile.y).AppendLine();
                }
            }

            foreach (var endpoint in graph.endpoints)
            {
                builder.Append("E|").Append(endpoint.id).Append('|').Append(endpoint.stageId).Append('|')
                    .Append(endpoint.worldTile.x).Append('|').Append(endpoint.worldTile.y).Append('|')
                    .Append(endpoint.kind).Append('|').Append(endpoint.required).Append('|')
                    .Append(endpoint.socketFace).AppendLine();
            }

            foreach (var connection in graph.connections)
            {
                builder.Append("C|").Append(connection.id).Append('|').Append(connection.endpointAId).Append('|')
                    .Append(connection.endpointBId).Append('|').Append(connection.directionality).AppendLine();
            }

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

            return result.ToString();
        }
    }
}
