using System;
using System.Collections.Generic;
using System.Linq;
using Net.Eagle0.Eagle.Common;
using Net.Eagle0.Eagle.Views;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using ProvinceId = System.Int32;
using FactionId = System.Int32;

namespace eagle {
    // Model and highlight state are updated by other controllers in Update. Run afterward so an
    // incoming model cannot overwrite the animated province colors before they are rendered.
    [DefaultExecutionOrder(100)]
    public class MapController : MonoBehaviour {
        public EagleGameController eagleGameController;

        public ProvinceColorManager provinceColorManager;
        public ProvinceBorderManager provinceBorderManager;
        public ProvinceWeatherController weatherController;
        public ProvinceFestivalController festivalController;
        public ProvinceEpidemicController epidemicController;
        public ProvinceBlizzardController blizzardController;
        public ProvinceDroughtController droughtController;
        public ProvinceFloodController floodController;
        public ProvinceBeastsController beastsController;
        public BattleInProgressController battleInProgressController;
        public MapPinchZoomHandler zoomHandler;

        public TMP_Text provinceNameText;
        public TMP_Text provinceOwnerText;
        public TMP_Text underProvinceNameText;
        public TMP_Text underProvinceOwnerText;

        public bool Enabled { get; set; }

        private void SetDefaultProvinceColor(ProvinceId provinceId) {
            // Model may be null during reconnection when UI is being reset
            if (Model?.Provinces == null || !Model.Provinces.ContainsKey(provinceId)) { return; }
            SetProvinceColor(provinceId, ColorForProvince(Model.Provinces[provinceId]));
        }

        public void ReloadFactionDisplaySetting() {
            RefreshHoveredProvinceDisplay(_previouslyHoveredProvinceId);
        }

        private void SetProvinceColor(ProvinceId provinceId, Color color) {
            if (provinceColorManager != null) {
                provinceColorManager.SetProvinceColor(provinceId, color);
            }
        }

        private ProvinceId? _selectedProvinceId;
        public ProvinceId? SelectedProvinceId {
            get => _selectedProvinceId;
            set {
                if (_selectedProvinceId is ProvinceId provinceId) {
                    SetDefaultProvinceColor(provinceId);
                }
                _selectedProvinceId = value;
                eagleGameController.ProvinceWasSelected(value);
                SetUpCenterText(value, Color.black);
            }
        }

        private List<ProvinceId> _targetedProvinces = new List<ProvinceId>();
        public List<ProvinceId> TargetedProvinces {
            get => _targetedProvinces;
            set {
                if (value == null) { value = new List<ProvinceId>(); }
                if (!Enumerable.SequenceEqual(_targetedProvinces, value)) {
                    foreach (ProvinceId pid in _targetedProvinces) { SetDefaultProvinceColor(pid); }

                    _targetedProvinces = new List<ProvinceId>(value);
                }
            }
        }

        // When true, MapController draws TargetedProvinces as a single grouped outline (internal
        // borders between members suppressed via ProvinceBorderManager's highlight-group lookup),
        // matching the look used by OverrideTargetedProvinces (notifications, popups). Set by the
        // caller alongside TargetedProvinces when the targets came from a faction selection.
        public bool TargetedProvincesAsGroup { get; set; }

        private List<ProvinceId> _overrideTargetedProvinces = new List<ProvinceId>();
        public IList<ProvinceId> OverrideTargetedProvinces {
            get => _overrideTargetedProvinces;
            set {
                if (value == null) { value = new List<ProvinceId>(); }
                if (!Enumerable.SequenceEqual(_overrideTargetedProvinces, value)) {
                    foreach (ProvinceId pid in _overrideTargetedProvinces) {
                        SetDefaultProvinceColor(pid);
                    }

                    _overrideTargetedProvinces = new List<ProvinceId>(value);
                }
            }
        }

        public Action<List<ProvinceId>> targetedProvincesChangedCallback;
        public Action<ProvinceId?> hoveredProvinceChangedCallback;
        private List<ProvinceId> _provincesWithCommands = new List<ProvinceId>();
        public List<ProvinceId> ProvincesWithCommands {
            get => _provincesWithCommands;
            set {
                if (!value.SequenceEqual(_provincesWithCommands)) {
                    foreach (var pid in _provincesWithCommands) { SetDefaultProvinceColor(pid); }
                    _provincesWithCommands = value;
                }
            }
        }

        private IGameModel _model;
        public IGameModel Model {
            get => _model;
            set {
                _model = value;

                if (value == null) { return; }

                foreach (var kv in _model.Provinces) { SetDefaultProvinceColor(kv.Value.Id); }

                foreach (var province in _model.Provinces.Values) {
                    SetDefaultProvinceColor(province.Id);
                }

                UpdateProvinceEffects(value);

                eagleGameController
                        .ProvinceWasSelected(_selectedProvinceId, selectRecommendedCommand: false);
            }
        }

        public ProvinceIDLoader provinceIDLoader;

        private readonly Color _commandAvailableColor = Color.blue;
        private readonly Color _hoverColor = new Color(0, 0, 0.5f, 0.5f);

        private const float SelectedBorderMin = 2f, SelectedBorderMax = 6f,
                            SelectedBorderCycle = 1.0f;

        private static readonly Color OverrideStrobeColor = Color.black;
        private static readonly Color OverrideBorderColor = new Color(0.05f, 0.05f, 0.05f, 0.95f);
        private const float OverrideBorderMin = 3f, OverrideBorderMax = 8f,
                            OverrideBorderCycle = 0.25f;

        private static readonly Color TargetedStrobeColor = new Color(0.55f, 0.05f, 0.15f);
        private static readonly Color TargetedBorderColor = new Color(0.55f, 0.05f, 0.15f, 0.95f);
        private const float TargetedBorderMin = 3f, TargetedBorderMax = 8f,
                            TargetedBorderCycle = 0.25f;

        private const float CommandBorderMin = 1.5f, CommandBorderMax = 5f,
                            CommandBorderCycle = 0.5f;
        private static readonly Color CommandBorderColor = new Color(0.3f, 0.3f, 1f, 0.85f);

        private readonly HashSet<ProvinceId> _activeHighlights = new();

        private byte[] _mapIdBytes;
        ProvinceId? _previouslyHoveredProvinceId;

        private static readonly Color BlizzardTint = new Color(0.82f, 0.88f, 0.95f);

        private Color ColorForProvince(ProvinceView province) {
            var color =
                    PlayerColors.LightPlayerColor(province.RulingFactionId.GetValueOrDefault(0));
            if (province.RulingFactionId.HasValue && province.FullInfo != null &&
                province.FullInfo.Support != null && province.FullInfo.Support.Stat < 40) {
                color = PlayerColors.MutedPlayerColor(
                        province.RulingFactionId.GetValueOrDefault(0));
            }

            if (HasBlizzardEvent(province)) { color *= BlizzardTint; }

            return color;
        }

        private static float Luminance(Color c) {
            return 0.299f * c.r + 0.587f * c.g + 0.114f * c.b;
        }

        private static (Color strobe, Color border) ContrastingHighlightColors(Color baseColor) {
            if (Luminance(baseColor) > 0.5f) {
                return (Color.black, new Color(0.05f, 0.05f, 0.05f, 0.95f));
            }
            return (Color.white, new Color(1f, 1f, 1f, 0.95f));
        }

        private static bool HasBlizzardEvent(ProvinceView province) {
            foreach (var evt in province.KnownEvents) {
                if (evt.SealedValueCase == ProvinceEvent.SealedValueOneofCase.BlizzardEvent) {
                    return true;
                }
            }
            return false;
        }

        void Awake() { provinceIDLoader.OnMapLoaded += OnProvinceMapLoaded; }

        private void OnProvinceMapLoaded() {
            _mapIdBytes = provinceIDLoader.DecompressedBytes;

            // Re-apply province effects if model already arrived before centroids.
            // CDN-fetched centroids may arrive after the first Model set, so effect
            // controllers that depend on centroids need a second chance to spawn.
            if (_model != null) { UpdateProvinceEffects(_model); }
        }

        public void UpdateBattleEffects(IGameModel model) {
            battleInProgressController.UpdateBattleEffects(model);
        }

        private void UpdateProvinceEffects(IGameModel model) {
            weatherController.UpdateWeatherEffects(model);
            festivalController.UpdateFestivalEffects(model);
            epidemicController.UpdateEpidemicEffects(model);
            blizzardController.UpdateBlizzardEffects(model);
            droughtController.UpdateDroughtEffects(model);
            floodController.UpdateFloodEffects(model);
            beastsController.UpdateBeastsEffects(model);
            battleInProgressController.UpdateBattleEffects(model);
        }

        void Start() {
            Enabled = true;
            _previouslyHoveredProvinceId = null;
            PlaceClickedProvincePanelAboveMap();
        }

        private void PlaceClickedProvincePanelAboveMap() {
            var clickedProvincePanel = provinceNameText.transform.parent;
            clickedProvincePanel.SetAsLastSibling();

            foreach (var graphic in clickedProvincePanel.GetComponentsInChildren<Graphic>()) {
                graphic.raycastTarget = false;
            }
        }

        int? ProvinceFromPoint(Vector2 point) {
            if (_mapIdBytes == null) { return null; }
            var mousePosition = new Vector2(point.x, Screen.height - point.y);
            var mapRect = gameObject.GetComponentInParent<PanelPositions>().MapRect;

            int mapWidth = provinceIDLoader.mapWidth;
            int mapHeight = provinceIDLoader.mapHeight;

            var relativeX = (int)((mousePosition.x - mapRect.x) * mapWidth / mapRect.width);
            var relativeY =
                    (int)(mapHeight - (mousePosition.y - mapRect.y) * mapHeight / mapRect.height);

            if (relativeX < 0 || relativeX >= mapWidth || relativeY < 0 || relativeY >= mapHeight) {
                return null;
            }

            var provinceId = _mapIdBytes[relativeY * mapWidth + relativeX];
            if (provinceId == 0 || provinceId == 0xFF) { return null; }
            return provinceId;
        }

        private void RefreshHoveredProvinceDisplay(ProvinceId? maybeHoveredProvinceId) {
            if (maybeHoveredProvinceId is ProvinceId hoveredProvinceId) {
                SetUpCenterText(hoveredProvinceId, _hoverColor);
            } else {
                SetUpCenterText(SelectedProvinceId, Color.black);
            }
        }

        void Update() {
            if (Model == null || Model.Provinces == null || Model.Provinces.Count == 0) { return; }

            // Clear previous frame's border highlights
            foreach (var pid in _activeHighlights) {
                provinceBorderManager.ClearProvinceBorder(pid);
            }
            _activeHighlights.Clear();

            var maybeHoveredProvinceId = ProvinceFromPoint(GameInput.PointerPosition());
            if (Model != null && maybeHoveredProvinceId != _previouslyHoveredProvinceId) {
                _previouslyHoveredProvinceId = maybeHoveredProvinceId;
                hoveredProvinceChangedCallback.Invoke(maybeHoveredProvinceId);
                RefreshHoveredProvinceDisplay(maybeHoveredProvinceId);
            }

            if (OverrideTargetedProvinces.Any()) {
                if (SelectedProvinceId is ProvinceId sPid) { SetDefaultProvinceColor(sPid); }
                foreach (ProvinceId tPid in TargetedProvinces) { SetDefaultProvinceColor(tPid); }
                foreach (var commandProvince in ProvincesWithCommands) {
                    SetDefaultProvinceColor(commandProvince);
                }

                provinceBorderManager.SetHighlightGroup(OverrideTargetedProvinces);

                foreach (var pid in OverrideTargetedProvinces) {
                    FlashProvince(pid, OverrideStrobeColor, OverrideBorderCycle);
                    HighlightProvinceBorder(
                            pid,
                            OverrideBorderColor,
                            OverrideBorderMin,
                            OverrideBorderMax,
                            OverrideBorderCycle);
                }
            } else {
                if (TargetedProvincesAsGroup) {
                    provinceBorderManager.SetHighlightGroup(TargetedProvinces);
                } else {
                    provinceBorderManager.ClearHighlightGroup();
                }
                if (SelectedProvinceId is ProvinceId sPid) {
                    var sBase = ColorForProvince(Model.Provinces[sPid]);
                    var (sStrobe, sBorder) = ContrastingHighlightColors(sBase);
                    FlashProvince(sPid, sStrobe, SelectedBorderCycle);
                    HighlightProvinceBorder(
                            sPid,
                            sBorder,
                            SelectedBorderMin,
                            SelectedBorderMax,
                            SelectedBorderCycle);
                }
                foreach (ProvinceId tPid in TargetedProvinces) {
                    FlashProvince(tPid, TargetedStrobeColor, TargetedBorderCycle);
                    HighlightProvinceBorder(
                            tPid,
                            TargetedBorderColor,
                            TargetedBorderMin,
                            TargetedBorderMax,
                            TargetedBorderCycle);
                }
                foreach (var commandProvince in ProvincesWithCommands) {
                    FlashProvince(commandProvince, _commandAvailableColor, CommandBorderCycle);
                    HighlightProvinceBorder(
                            commandProvince,
                            CommandBorderColor,
                            CommandBorderMin,
                            CommandBorderMax,
                            CommandBorderCycle);
                }
            }
        }

        private void FlashProvince(ProvinceId provinceId, Color flashColor, float duration) {
            SetProvinceColor(
                    provinceId,
                    Color.Lerp(
                            ColorForProvince(Model.Provinces[provinceId]),
                            flashColor,
                            Mathf.PingPong(Time.time, duration)));
        }

        private void HighlightProvinceBorder(
                ProvinceId provinceId,
                Color borderColor,
                float minWidth,
                float maxWidth,
                float cycleDuration) {
            float t = Mathf.PingPong(Time.time, cycleDuration) / cycleDuration;
            float width = Mathf.Lerp(minWidth, maxWidth, t);
            provinceBorderManager.SetProvinceBorder(provinceId, borderColor, width);
            _activeHighlights.Add(provinceId);
        }

        public void OnLeftClick(Vector2 position) {
            SelectedProvinceId = ProvinceFromPoint(position);
        }

        public void OnRightClick(Vector2 position) {
            if (ProvinceFromPoint(position) is ProvinceId pid) {
                TargetedProvinces = new List<ProvinceId> { pid };
            } else {
                TargetedProvinces.Clear();
            }
            targetedProvincesChangedCallback.Invoke(TargetedProvinces);
        }

        // ========== Province Highlight Proxy ==========

        private readonly Dictionary<int, Vector4> _provinceBoundsCache =
                new Dictionary<int, Vector4>();
        private RectTransform _provinceHighlightProxy;

        /// <summary>
        /// Creates a RectTransform child of the map panel that overlays the given province.
        /// Used by the dialogue system to highlight provinces on the strategic map.
        /// Returns null if the province is not found.
        /// </summary>
        public RectTransform GetProvinceHighlightProxy(string provinceName) {
            if (Model == null) return null;

            var province = Model.Provinces.Values.FirstOrDefault(p => p.Name == provinceName);
            if (province == null) return null;

            if (!_provinceBoundsCache.TryGetValue(province.Id, out var bounds)) {
                bounds = ComputeProvinceBounds(province.Id);
                _provinceBoundsCache[province.Id] = bounds;
            }

            ClearProvinceHighlightProxy();

            int w = provinceIDLoader.mapWidth;
            int h = provinceIDLoader.mapHeight;

            var panelPositions = gameObject.GetComponentInParent<PanelPositions>();
            var proxyGO = new GameObject("DialogueProvinceHighlight");
            proxyGO.AddComponent<CanvasRenderer>();
            proxyGO.transform.SetParent(panelPositions.mapPanelTransform, false);
            _provinceHighlightProxy = proxyGO.AddComponent<RectTransform>();

            // Anchors in RectTransform space: (0,0) = bottom-left, (1,1) = top-right.
            // Province ID byte array: y=0 = bottom of map (matches RectTransform origin).
            _provinceHighlightProxy.anchorMin =
                    new Vector2(bounds.x / w, bounds.y / h);  // (minX, minY)
            _provinceHighlightProxy.anchorMax =
                    new Vector2(bounds.z / w, bounds.w / h);  // (maxX, maxY)
            _provinceHighlightProxy.offsetMin = Vector2.zero;
            _provinceHighlightProxy.offsetMax = Vector2.zero;

            return _provinceHighlightProxy;
        }

        public void ClearProvinceHighlightProxy() {
            if (_provinceHighlightProxy != null) {
                Destroy(_provinceHighlightProxy.gameObject);
                _provinceHighlightProxy = null;
            }
        }

        /// <summary>
        /// Scans the province ID bytes to find the bounding box of a province.
        /// Returns Vector4(minX, minY, maxX, maxY) in map pixel coordinates.
        /// </summary>
        private Vector4 ComputeProvinceBounds(int provinceId) {
            int w = provinceIDLoader.mapWidth;
            int h = provinceIDLoader.mapHeight;
            int minX = w, minY = h, maxX = 0, maxY = 0;

            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    if (_mapIdBytes[y * w + x] == provinceId) {
                        if (x < minX) minX = x;
                        if (x > maxX) maxX = x;
                        if (y < minY) minY = y;
                        if (y > maxY) maxY = y;
                    }
                }
            }

            return new Vector4(minX, minY, maxX, maxY);
        }

        void SetUpCenterText(ProvinceId? maybeProvinceId, Color color) {
            provinceNameText.color = color;
            provinceOwnerText.color = color;
            if (underProvinceNameText != null) underProvinceNameText.color = color;
            if (underProvinceOwnerText != null) underProvinceOwnerText.color = color;

            if (maybeProvinceId is ProvinceId pid) {
                var province = Model.Provinces[pid];
                provinceNameText.text = province.Name;
                if (underProvinceNameText != null) underProvinceNameText.text = province.Name;

                if (province.RulingFactionId is FactionId fid) {
                    var factionName = Model.FactionName(fid);
                    provinceOwnerText.text = factionName;
                    if (underProvinceOwnerText != null) underProvinceOwnerText.text = factionName;
                } else {
                    provinceOwnerText.text = "";
                    if (underProvinceOwnerText != null) underProvinceOwnerText.text = "";
                }
            } else {
                provinceNameText.text = "";
                provinceOwnerText.text = "";
                if (underProvinceNameText != null) underProvinceNameText.text = "";
                if (underProvinceOwnerText != null) underProvinceOwnerText.text = "";
            }
        }
    }
}
