using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Decal.Adapter;
using Newtonsoft.Json;

namespace MosswartMassacre
{
    /// <summary>
    /// Parses Asheron's Call combat chat messages into structured stats,
    /// matching every pattern tracked by Mag-Tools' Combat Tracker
    /// (StandardTracker + AetheriaTracker + CloakTracker).
    ///
    /// Maintains two CombatSessionState objects:
    ///   - _sessionState: cleared on login / manual reset
    ///   - Lifetime: accumulated on the backend by merging session deltas
    ///
    /// Every 10 seconds, sends a combat_stats snapshot to MosswartOverlord
    /// via WebSocket so the browser dashboard can display the same grid
    /// that Mag-Tools shows in-game.
    /// </summary>
    public class CombatStatsTracker : IDisposable
    {
        private const int SendIntervalMs = 10000;

        private readonly IPluginLogger _logger;
        private CombatSessionState _sessionState = new CombatSessionState();
        private System.Windows.Forms.Timer _sendTimer;
        private bool _dirty;
        private bool _disposed;

        public CombatStatsTracker(IPluginLogger logger)
        {
            _logger = logger;
            _sessionState.SessionStart = DateTime.UtcNow.ToString("o");
        }

        public void Start()
        {
            if (_sendTimer != null) return;
            _sendTimer = new System.Windows.Forms.Timer();
            _sendTimer.Interval = SendIntervalMs;
            _sendTimer.Tick += OnSendTick;
            _sendTimer.Start();
            _logger?.Log("[CombatStats] Tracker started (10s send interval)");
        }

        public void Stop()
        {
            if (_sendTimer != null)
            {
                _sendTimer.Stop();
                _sendTimer.Tick -= OnSendTick;
                _sendTimer.Dispose();
                _sendTimer = null;
            }
        }

        public void RestartSession()
        {
            _sessionState.Clear();
            _dirty = true;
        }

        // ═══════════════════════════════════════════════════════════════
        //  Public entry point — called from ChatEventRouter.OnChatText
        // ═══════════════════════════════════════════════════════════════

        private int _parseCount;

        public void ProcessChatLine(string text)
        {
            if (string.IsNullOrEmpty(text)) return;

            try
            {
                // DECAL ChatBoxMessage delivers raw text with HTML tags and
                // trailing newlines. Strip them so ^...$ regex anchors work.
                text = System.Text.RegularExpressions.Regex.Replace(text, "<[^>]+>", "");
                text = text.TrimEnd('\r', '\n', ' ');
                if (string.IsNullOrEmpty(text)) return;

                // Skip non-combat chat (tells, channels, etc.)
                if (IsNonCombatChat(text)) return;

                // Diagnostic: log first few parses to verify the pipeline is live
                if (_parseCount < 3)
                {
                    _logger?.Log($"[CombatStats] ProcessChatLine invoked (#{_parseCount}): \"{text.Substring(0, Math.Min(text.Length, 80))}\"");
                    _parseCount++;
                }

                // Detect prefix flags (Mag-Tools does this via text.Contains)
                bool isCrit = text.Contains("Critical hit!");
                bool isOverpower = text.Contains("Overpower!");
                bool isSneakAttack = text.Contains("Sneak Attack!");
                bool isRecklessness = text.Contains("Recklessness!");

                string myName = SafeCharacterName();

                // ── Aetheria surges ──
                if (TryParseAetheriaSurge(text, myName))
                    return;

                // ── Cloak surges ──
                if (TryParseCloakSurge(text, myName))
                    return;

                // ── Evade / Resist (failed attacks) ──
                if (TryParseFailedAttack(text, myName))
                    return;

                // ── Kill messages ──
                if (TryParseKill(text, myName))
                    return;

                // ── Melee/Missile received ──
                if (TryMatchPatterns(_meleeMissileReceived, text, out string recvSource, out int recvDmg))
                {
                    RecordHit(recvSource, myName, AttackType.MeleeMissile, GetElementFromText(text),
                              recvDmg, isCrit, isOverpower, isSneakAttack, isRecklessness);
                    return;
                }

                // ── Melee/Missile given ──
                if (TryMatchPatterns(_meleeMissileGiven, text, out string givenTarget, out int givenDmg))
                {
                    RecordHit(myName, givenTarget, AttackType.MeleeMissile, GetElementFromText(text),
                              givenDmg, isCrit, isOverpower, isSneakAttack, isRecklessness);
                    return;
                }

                // ── Magic received ──
                if (TryMatchPatterns(_magicReceived, text, out string magRecvSource, out int magRecvDmg))
                {
                    RecordHit(magRecvSource, myName, AttackType.Magic, GetElementFromText(text),
                              magRecvDmg, isCrit, isOverpower, isSneakAttack, isRecklessness);
                    return;
                }

                // ── Magic given ──
                if (TryMatchPatterns(_magicGiven, text, out string magGivenTarget, out int magGivenDmg))
                {
                    RecordHit(myName, magGivenTarget, AttackType.Magic, GetElementFromText(text),
                              magGivenDmg, isCrit, isOverpower, isSneakAttack, isRecklessness);
                    return;
                }
            }
            catch (Exception ex)
            {
                _logger?.Log($"[CombatStats] Parse error: {ex.Message}");
            }
        }

        // ═══════════════════════════════════════════════════════════════
        //  Recording helpers
        // ═══════════════════════════════════════════════════════════════

        private void RecordHit(string source, string target, AttackType at, DamageElement el,
                               int damage, bool isCrit, bool isOverpower, bool isSneakAttack, bool isRecklessness)
        {
            string myName = SafeCharacterName();
            bool weAreSource = source.Equals(myName, StringComparison.OrdinalIgnoreCase);
            string monsterName = weAreSource ? target : source;
            if (string.IsNullOrEmpty(monsterName)) return;

            Action<CombatSessionState> apply = state =>
            {
                var rec = state.GetOrCreateMonster(monsterName);
                var side = weAreSource ? rec.Offense : rec.Defense;
                var stats = rec.GetOrCreateStats(side, at, el);

                stats.TotalAttacks++;
                if (isCrit)
                {
                    stats.Crits++;
                    stats.TotalCritDamage += damage;
                    if (damage > stats.MaxCritDamage) stats.MaxCritDamage = damage;
                }
                else
                {
                    stats.TotalNormalDamage += damage;
                    if (damage > stats.MaxNormalDamage) stats.MaxNormalDamage = damage;
                }

                if (weAreSource)
                {
                    rec.DamageGiven += damage;
                    state.TotalDamageGiven += damage;
                }
                else
                {
                    rec.DamageReceived += damage;
                    state.TotalDamageReceived += damage;
                }
            };

            apply(_sessionState);

            _dirty = true;
        }

        private void RecordFailedAttack(string source, string target, AttackType at)
        {
            string myName = SafeCharacterName();
            bool weAreSource = source.Equals(myName, StringComparison.OrdinalIgnoreCase);
            string monsterName = weAreSource ? target : source;
            if (string.IsNullOrEmpty(monsterName)) return;

            Action<CombatSessionState> apply = state =>
            {
                var rec = state.GetOrCreateMonster(monsterName);
                var side = weAreSource ? rec.Offense : rec.Defense;
                var stats = rec.GetOrCreateStats(side, at, DamageElement.None);
                stats.TotalAttacks++;
                stats.FailedAttacks++;
            };

            apply(_sessionState);

            _dirty = true;
        }

        private void RecordKill(string targetName)
        {
            if (string.IsNullOrEmpty(targetName)) return;

            Action<CombatSessionState> apply = state =>
            {
                var rec = state.GetOrCreateMonster(targetName);
                rec.KillCount++;
                state.TotalKills++;
            };

            apply(_sessionState);

            _dirty = true;
        }

        private void RecordAetheriaSurge(string targetName)
        {
            if (string.IsNullOrEmpty(targetName)) return;

            Action<CombatSessionState> apply = state =>
            {
                var rec = state.GetOrCreateMonster(targetName);
                rec.AetheriaSurges++;
                state.TotalAetheriaSurges++;
            };

            apply(_sessionState);

            _dirty = true;
        }

        private void RecordCloakSurge(string sourceName)
        {
            if (string.IsNullOrEmpty(sourceName)) return;

            Action<CombatSessionState> apply = state =>
            {
                var rec = state.GetOrCreateMonster(sourceName);
                rec.CloakSurges++;
                state.TotalCloakSurges++;
            };

            apply(_sessionState);

            _dirty = true;
        }

        // ═══════════════════════════════════════════════════════════════
        //  Regex pattern matching — mirrors Mag-Tools CombatMessages.cs
        // ═══════════════════════════════════════════════════════════════

        // ── Failed attacks (evade/resist) ──
        private static readonly Regex[] _failedAttacks = new[]
        {
            new Regex(@"^You evaded (?<name>.+)!$", RegexOptions.Compiled),
            new Regex(@"^(?<name>.+) evaded your attack\.$", RegexOptions.Compiled),
            new Regex(@"^You resist the spell cast by (?<name>.+)$", RegexOptions.Compiled),
            new Regex(@"^(?<name>.+) resists your spell$", RegexOptions.Compiled),
        };

        private bool TryParseFailedAttack(string text, string myName)
        {
            foreach (var rx in _failedAttacks)
            {
                var m = rx.Match(text);
                if (!m.Success) continue;
                string parsed = m.Groups["name"].Value;

                if (text.StartsWith("You evaded "))
                    RecordFailedAttack(parsed, myName, AttackType.MeleeMissile);
                else if (text.Contains(" evaded your attack"))
                    RecordFailedAttack(myName, parsed, AttackType.MeleeMissile);
                else if (text.StartsWith("You resist the spell cast by "))
                    RecordFailedAttack(parsed, myName, AttackType.Magic);
                else if (text.Contains(" resists your spell"))
                    RecordFailedAttack(myName, parsed, AttackType.Magic);

                return true;
            }
            return false;
        }

        // ── Kill messages — same patterns as KillTracker ──
        private static readonly Regex[] _killPatterns = new[]
        {
            new Regex(@"^You flatten (?<targetname>.+)'s body with the force of your assault!$", RegexOptions.Compiled),
            new Regex(@"^You bring (?<targetname>.+) to a fiery end!$", RegexOptions.Compiled),
            new Regex(@"^You beat (?<targetname>.+) to a lifeless pulp!$", RegexOptions.Compiled),
            new Regex(@"^You smite (?<targetname>.+) mightily!$", RegexOptions.Compiled),
            new Regex(@"^You obliterate (?<targetname>.+)!$", RegexOptions.Compiled),
            new Regex(@"^You run (?<targetname>.+) through!$", RegexOptions.Compiled),
            new Regex(@"^You reduce (?<targetname>.+) to a sizzling, oozing mass!$", RegexOptions.Compiled),
            new Regex(@"^You knock (?<targetname>.+) into next Morningthaw!$", RegexOptions.Compiled),
            new Regex(@"^You split (?<targetname>.+) apart!$", RegexOptions.Compiled),
            new Regex(@"^You cleave (?<targetname>.+) in twain!$", RegexOptions.Compiled),
            new Regex(@"^You slay (?<targetname>.+) viciously enough to impart death several times over!$", RegexOptions.Compiled),
            new Regex(@"^You reduce (?<targetname>.+) to a drained, twisted corpse!$", RegexOptions.Compiled),
            new Regex(@"^Your killing blow nearly turns (?<targetname>.+) inside-out!$", RegexOptions.Compiled),
            new Regex(@"^Your attack stops (?<targetname>.+) cold!$", RegexOptions.Compiled),
            new Regex(@"^Your lightning coruscates over (?<targetname>.+)'s mortal remains!$", RegexOptions.Compiled),
            new Regex(@"^Your assault sends (?<targetname>.+) to an icy death!$", RegexOptions.Compiled),
            new Regex(@"^You killed (?<targetname>.+)!$", RegexOptions.Compiled),
            new Regex(@"^The thunder of crushing (?<targetname>.+) is followed by the deafening silence of death!$", RegexOptions.Compiled),
            new Regex(@"^The deadly force of your attack is so strong that (?<targetname>.+)'s ancestors feel it!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+)'s seared corpse smolders before you!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) is reduced to cinders!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) is shattered by your assault!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) catches your attack, with dire consequences!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) is utterly destroyed by your attack!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) suffers a frozen fate!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+)'s perforated corpse falls before you!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) is fatally punctured!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+)'s death is preceded by a sharp, stabbing pain!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) is torn to ribbons by your assault!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) is liquified by your attack!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+)'s last strength dissolves before you!$", RegexOptions.Compiled),
            new Regex(@"^Electricity tears (?<targetname>.+) apart!$", RegexOptions.Compiled),
            new Regex(@"^Blistered by lightning, (?<targetname>.+) falls!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+)'s last strength withers before you!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) is dessicated by your attack!$", RegexOptions.Compiled),
            new Regex(@"^(?<targetname>.+) is incinerated by your assault!$", RegexOptions.Compiled),
        };

        private bool TryParseKill(string text, string myName)
        {
            foreach (var rx in _killPatterns)
            {
                var m = rx.Match(text);
                if (m.Success)
                {
                    RecordKill(m.Groups["targetname"].Value);
                    return true;
                }
            }
            return false;
        }

        // ── Melee/Missile received ──
        private static readonly Regex[] _meleeMissileReceived = new[]
        {
            // Critical hit! Overpower! X smashes your leg for N points of bludgeoning damage!
            new Regex(@"^Critical hit! Overpower! (?<name>.+) [\w]+ your .+ for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // Critical hit! X scratches your leg for N points of slashing damage!
            new Regex(@"^Critical hit! (?<name>.+) [\w]+ your .+ for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // Overpower! X bashes your foot for N points of bludgeoning damage!
            new Regex(@"^Overpower! (?<name>.+) [\w]+ your .+ for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // X grazes your upper arm for N points of bludgeoning damage!
            new Regex(@"^(?<name>.+) [\w]+ your .+ for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
        };

        // ── Melee/Missile given ──
        private static readonly Regex[] _meleeMissileGiven = new[]
        {
            // Critical hit! Sneak Attack! Recklessness! You crush X for N points of bludgeoning damage!
            new Regex(@"^Critical hit!\s+Sneak Attack! Recklessness! You [\w]+ (?<name>.+) for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // Critical hit! Sneak Attack! You chill X for N points of cold damage!
            new Regex(@"^Critical hit!\s+Sneak Attack! You [\w]+ (?<name>.+) for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // Critical hit! You scorch X for N points of fire damage!
            new Regex(@"^Critical hit!\s+You [\w]+ (?<name>.+) for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // Sneak Attack! Recklessness! You scratch X for N points of slashing damage!
            new Regex(@"^Sneak Attack! Recklessness! You [\w]+ (?<name>.+) for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // Sneak Attack! You chill X for N points of cold damage!
            new Regex(@"^Sneak Attack! You [\w]+ (?<name>.+) for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // Recklessness! You scratch X for N points of slashing damage!
            new Regex(@"^Recklessness! You [\w]+ (?<name>.+) for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
            // You scorch X for N points of fire damage!
            new Regex(@"^You [\w]+ (?<name>.+) for (?<points>\d+) point.* of .+ damage", RegexOptions.Compiled),
        };

        // ── Magic received ──
        private static readonly Regex[] _magicReceived = new[]
        {
            // Critical hit! Overpower! X shocks you for N points with Incantation of Lightning Arc.
            new Regex(@"^Critical hit! Overpower! (?<name>.+) [\w]+ you for (?<points>\d+) point.* with .+$", RegexOptions.Compiled),
            // Critical hit! X smashes you for N points with Incantation of Shock Wave Streak.
            new Regex(@"^Critical hit! (?<name>.+) [\w]+ you for (?<points>\d+) point.* with .+$", RegexOptions.Compiled),
            // Overpower! X shocks you for N points with Incantation of Lightning Arc.
            new Regex(@"^Overpower! (?<name>.+) [\w]+ you for (?<points>\d+) point.* with .+$", RegexOptions.Compiled),
            // X scorches you for N points with Flame Arc VII.
            new Regex(@"^(?<name>.+) [\w]+ you for (?<points>\d+) point.* with .+$", RegexOptions.Compiled),
            // Magical energies lose N points of health due to X casting Vitality Siphon
            new Regex(@"^Magical energies lose (?<points>\d+) point.* of health due to (?<name>.+) casting .+$", RegexOptions.Compiled),
            // You lose N points of health due to X casting Drain Health Other V on you
            new Regex(@"^You lose (?<points>\d+) point.* of health due to (?<name>.+) casting .+$", RegexOptions.Compiled),
            // X casts Harm Other VI and drains N points ...
            new Regex(@"^(?<name>.+) casts .+ and drains (?<points>\d+) point.* .+$", RegexOptions.Compiled),
        };

        // ── Magic given ──
        private static readonly Regex[] _magicGiven = new[]
        {
            // Critical hit! You smash X for N points with Incantation of Shock Wave Streak.
            new Regex(@"^Critical hit! You [\w]+ (?<name>.+) for (?<points>\d+) point.* with .+$", RegexOptions.Compiled),
            // You nick X for N points with Incantation of Force Bolt.
            new Regex(@"^You [\w]+ (?<name>.+) for (?<points>\d+) point.* with .+$", RegexOptions.Compiled),
        };

        // ── Aetheria surges ──
        private static readonly Regex _aetheriaSurge =
            new Regex(@"^Aetheria surges on (?<name>.+) with the ", RegexOptions.Compiled);

        private bool TryParseAetheriaSurge(string text, string myName)
        {
            if (!text.StartsWith("Aetheria surges on ")) return false;
            var m = _aetheriaSurge.Match(text);
            if (!m.Success) return false;
            RecordAetheriaSurge(m.Groups["name"].Value);
            return true;
        }

        // ── Cloak surges ──
        private bool TryParseCloakSurge(string text, string myName)
        {
            // "Your cloak ..." messages indicate our cloak procc'd against an attacker.
            // We don't have the attacker name easily, so we track as a global surge count.
            if (text.StartsWith("Your cloak") || text.Contains("Shroud of Darkness"))
            {
                // For cloak surges we store against a synthetic "Cloak Surges" monster key
                // since MT tracks them as a total count, not per-monster.
                RecordCloakSurge("__cloak_surges__");
                return true;
            }
            return false;
        }

        // ── Generic pattern matcher (returns name + points) ──
        private static bool TryMatchPatterns(Regex[] patterns, string text,
                                              out string name, out int points)
        {
            foreach (var rx in patterns)
            {
                var m = rx.Match(text);
                if (m.Success)
                {
                    name = m.Groups["name"].Value;
                    int.TryParse(m.Groups["points"].Value, out points);
                    return true;
                }
            }
            name = null;
            points = 0;
            return false;
        }

        // ═══════════════════════════════════════════════════════════════
        //  Element detection — matches Mag-Tools StandardTracker logic
        // ═══════════════════════════════════════════════════════════════

        private static DamageElement GetElementFromText(string text)
        {
            // Slash
            if (text.Contains("slash") || text.Contains(" cut") || text.Contains(" scratch") || text.Contains(" mangle"))
                return DamageElement.Slash;
            // Pierce
            if (text.Contains("pierc") || text.Contains(" gore") || text.Contains(" impale") || text.Contains(" nick") || text.Contains(" stab"))
                return DamageElement.Pierce;
            // Bludgeon
            if (text.Contains("bludge") || text.Contains(" smash") || text.Contains(" bash") || text.Contains(" graze") || text.Contains(" crush"))
                return DamageElement.Bludgeon;
            // Fire
            if (text.Contains("fire") || text.Contains(" burn") || text.Contains(" singe") || text.Contains(" scorch") || text.Contains(" incinerate"))
                return DamageElement.Fire;
            // Cold
            if (text.Contains("cold") || text.Contains(" frost") || text.Contains(" chill") || text.Contains(" numb") || text.Contains(" freeze"))
                return DamageElement.Cold;
            // Acid
            if (text.Contains("acid") || text.Contains(" blister") || text.Contains(" sear") || text.Contains(" corrode") || text.Contains(" dissolve"))
                return DamageElement.Acid;
            // Electric
            if (text.Contains("electric") || text.Contains(" lightning") || text.Contains(" jolt") || text.Contains(" shock") || text.Contains(" spark") || text.Contains(" blast"))
                return DamageElement.Electric;
            // Nether → Typeless
            if (text.Contains(" eradicate") || text.Contains(" wither") || text.Contains(" scar") || text.Contains(" twist"))
                return DamageElement.Typeless;
            // Life magic drains → Typeless
            if (text.Contains(" deplete") || text.Contains(" siphon") || text.Contains(" exhaust") || text.Contains(" drain") || text.Contains(" lose ") || text.Contains(" health "))
                return DamageElement.Typeless;

            return DamageElement.Unknown;
        }

        // ═══════════════════════════════════════════════════════════════
        //  Chat filtering
        // ═══════════════════════════════════════════════════════════════

        private static bool IsNonCombatChat(string text)
        {
            // Quick filters to skip non-combat lines before regex work
            if (text.StartsWith("<Tell:") || text.StartsWith("[")) return true;
            if (text.StartsWith("You say,") || text.StartsWith("You tell ")) return true;
            if (text.Contains(" tells you,") || text.Contains(" says,")) return true;
            return false;
        }

        // ═══════════════════════════════════════════════════════════════
        //  Periodic send
        // ═══════════════════════════════════════════════════════════════

        private int _sendCount;

        private void OnSendTick(object sender, EventArgs e)
        {
            if (!_dirty) return;
            _dirty = false;
            _sendCount++;
            if (_sendCount <= 3)
                _logger?.Log($"[CombatStats] Sending snapshot #{_sendCount}: {_sessionState.TotalKills} kills, {_sessionState.TotalDamageGiven} dmg given, {_sessionState.Monsters.Count} monsters");

            try
            {
                var payload = new
                {
                    type = "combat_stats",
                    timestamp = DateTime.UtcNow.ToString("o"),
                    character_name = SafeCharacterName(),
                    session_id = WebSocket.SessionId ?? "",
                    session = _sessionState,
                    lifetime = (object)null,  // lifetime is accumulated on the backend
                };
                _ = WebSocket.SendCombatStatsAsync(payload);
            }
            catch (Exception ex)
            {
                _logger?.Log($"[CombatStats] Send error: {ex.Message}");
            }
        }

        // ═══════════════════════════════════════════════════════════════
        //  Helpers
        // ═══════════════════════════════════════════════════════════════

        private static string SafeCharacterName()
        {
            try { return CoreManager.Current.CharacterFilter?.Name ?? ""; }
            catch { return ""; }
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
            Stop();
        }
    }
}
