using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using common;
using Net.Eagle0.Eagle.Common;
using UnityEngine;

namespace eagle {
    /// <summary>
    /// Loads the compressed rawGray province ID map as a GPU texture.
    /// Each pixel's red channel contains the province ID (0-255).
    /// ID 0 and 255 represent ocean/non-province areas.
    ///
    /// Loading is deferred until Initialize() is called with server-provided
    /// EagleMapInfo, which supplies the map name, dimensions, and SHA256 hash.
    /// The map data is fetched from CDN via ResourceFetcher.
    /// </summary>
    public class ProvinceIDLoader : MonoBehaviour {
        [Header("Output Materials")]
        public Material provinceMapMaterial;
        public Material weatherMaterial;

        [HideInInspector]
        public int mapWidth;
        [HideInInspector]
        public int mapHeight;

        private Texture2D _provinceIDTexture;
        private byte[] _decompressedBytes;
        private string _expectedSha256;
        private string _expectedCentroidsSha256;
        private string _centroidsJsonText;
        private bool _mapReady;
        private bool _centroidsReady;

        public Texture2D ProvinceIDTexture => _provinceIDTexture;
        public string CentroidsJsonText => _centroidsJsonText;

        /// <summary>
        /// The decompressed province ID bytes. Can be used by other components
        /// (e.g., MapController for hit testing) to avoid double-decompression.
        /// </summary>
        public byte[] DecompressedBytes => _decompressedBytes;

        /// <summary>
        /// Fired after the map has been successfully loaded and the texture assigned.
        /// Subscribers (MapController, ProvinceBorderDistanceGenerator, etc.) should
        /// wait for this before accessing DecompressedBytes or map dimensions.
        /// </summary>
        public event Action OnMapLoaded;

        /// <summary>
        /// Initializes the province ID map using server-provided map metadata.
        /// Sets dimensions immediately, then fetches the map data from CDN
        /// using the SHA256 hash as the content-addressable filename.
        /// OnMapLoaded fires when the data arrives and is validated.
        /// </summary>
        public void Initialize(EagleMapInfo eagleMap) {
            mapWidth = eagleMap.MapWidth;
            mapHeight = eagleMap.MapHeight;
            _expectedSha256 = eagleMap.Sha256;
            _expectedCentroidsSha256 = eagleMap.CentroidsSha256;

            ResourceFetcher.mapFetcher.FetchBytes(eagleMap.Sha256, OnMapBytesReceived);
            ResourceFetcher.mapFetcher.FetchBytes(
                    eagleMap.CentroidsSha256,
                    OnCentroidsBytesReceived);
        }

        private void OnMapBytesReceived(byte[] compressedBytes) {
            _decompressedBytes = Decompress(compressedBytes);

            int expectedSize = mapWidth * mapHeight;
            if (_decompressedBytes.Length != expectedSize) {
                Debug.LogError(
                        $"ProvinceIDLoader: Decompressed size {_decompressedBytes.Length} " +
                        $"does not match expected {expectedSize} ({mapWidth}x{mapHeight})");
                return;
            }

            if (!string.IsNullOrEmpty(_expectedSha256)) {
                var actualHash = ComputeSha256(_decompressedBytes);
                if (!string.Equals(
                            actualHash,
                            _expectedSha256,
                            StringComparison.OrdinalIgnoreCase)) {
                    Debug.LogError(
                            $"ProvinceIDLoader: SHA256 mismatch. " +
                            $"Expected: {_expectedSha256}, Actual: {actualHash}");
                    return;
                }
            }

            _provinceIDTexture = new Texture2D(mapWidth, mapHeight, TextureFormat.R8, false);
            _provinceIDTexture.filterMode = FilterMode.Point;
            _provinceIDTexture.wrapMode = TextureWrapMode.Clamp;
            _provinceIDTexture.LoadRawTextureData(_decompressedBytes);
            _provinceIDTexture.Apply();

            if (provinceMapMaterial != null) {
                provinceMapMaterial.SetTexture("_ProvinceIDMap", _provinceIDTexture);
            }
            if (weatherMaterial != null) {
                weatherMaterial.SetTexture("_ProvinceIDMap", _provinceIDTexture);
            }

            _mapReady = true;
            if (_centroidsReady) { OnMapLoaded?.Invoke(); }
        }

        private void OnCentroidsBytesReceived(byte[] bytes) {
            if (!string.IsNullOrEmpty(_expectedCentroidsSha256)) {
                var actualHash = ComputeSha256(bytes);
                if (!string.Equals(
                            actualHash,
                            _expectedCentroidsSha256,
                            StringComparison.OrdinalIgnoreCase)) {
                    Debug.LogError(
                            $"ProvinceIDLoader: Centroids SHA256 mismatch. " +
                            $"Expected: {_expectedCentroidsSha256}, Actual: {actualHash}");
                    return;
                }
            }

            _centroidsJsonText = System.Text.Encoding.UTF8.GetString(bytes);
            _centroidsReady = true;
            if (_mapReady) { OnMapLoaded?.Invoke(); }
        }

        private static string ComputeSha256(byte[] data) {
            using var sha256 = SHA256.Create();
            var hash = sha256.ComputeHash(data);
            return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        }

        private static byte[] Decompress(byte[] gzipData) {
            using var input = new MemoryStream(gzipData);
            using var gzip = new GZipStream(input, CompressionMode.Decompress);
            using var output = new MemoryStream();
            gzip.CopyTo(output);
            return output.ToArray();
        }

        void OnDestroy() {
            if (_provinceIDTexture != null) { Destroy(_provinceIDTexture); }
        }
    }
}
