using System.Collections.Generic;
using Jovian.EncounterSystem;
using UnityEditor;
using UnityEngine;

namespace Jovian.EncounterSystem.Editor {
    public enum ValidationSeverity {
        Warning,
        Error
    }

    public class ValidationIssue {
        public Object asset;
        public Encounter encounter;
        public string path;
        public ValidationSeverity severity;
        public string message;
    }

    /// <summary>Project-wide scan of encounter tables and rewards. Runs on demand, no caching.</summary>
    public static class EncounterValidator {
        public static List<ValidationIssue> ValidateProject() {
            var issues = new List<ValidationIssue>();

            ValidateRegistries(issues);

            var tables = FindAssetsOfType<EncounterTable>();
            var idIndex = new Dictionary<string, (Encounter encounter, EncounterTable table)>();

            foreach(var table in tables) {
                ValidateTable(table, issues, idIndex);
            }

            foreach(var reward in FindAssetsOfType<Reward>()) {
                ValidateReward(reward, issues);
            }

            return issues;
        }

        private static void ValidateRegistries(List<ValidationIssue> issues) {
            var registries = FindAssetsOfType<EncounterRegistry>();
            if(registries.Count == 0) {
                issues.Add(new ValidationIssue {
                    asset = null,
                    path = "<project>",
                    severity = ValidationSeverity.Error,
                    message = "No EncounterRegistry asset exists. Create one via Jovian → Encounter System → Encounter Registry."
                });
                return;
            }

            foreach(var registry in registries) {
                if(registry.encounterCollections == null || registry.encounterCollections.Length == 0) {
                    issues.Add(new ValidationIssue {
                        asset = registry,
                        path = "encounterCollections",
                        severity = ValidationSeverity.Warning,
                        message = "Registry has no collections assigned."
                    });
                    continue;
                }

                for(int i = 0; i < registry.encounterCollections.Length; i++) {
                    if(registry.encounterCollections[i] == null) {
                        issues.Add(new ValidationIssue {
                            asset = registry,
                            path = $"encounterCollections[{i}]",
                            severity = ValidationSeverity.Warning,
                            message = "Null collection slot."
                        });
                    }
                }
            }
        }

        public static List<ValidationIssue> ValidateEncounter(EncounterTable table, int index) {
            var issues = new List<ValidationIssue>();
            if(table?.encounters == null || index < 0 || index >= table.encounters.Count) {
                return issues;
            }

            var encounter = table.encounters[index];
            ValidateEncounterEntry(table, index, encounter, issues, duplicateCheck: null);
            return issues;
        }

        private static void ValidateTable(EncounterTable table, List<ValidationIssue> issues, Dictionary<string, (Encounter, EncounterTable)> idIndex) {
            if(table?.encounters == null) {
                return;
            }

            for(int i = 0; i < table.encounters.Count; i++) {
                ValidateEncounterEntry(table, i, table.encounters[i], issues, idIndex);
            }
        }

        private static void ValidateEncounterEntry(EncounterTable table, int index, Encounter encounter, List<ValidationIssue> issues, Dictionary<string, (Encounter, EncounterTable)> duplicateCheck) {
            var pathPrefix = $"encounters[{index}]";

            if(encounter == null) {
                issues.Add(new ValidationIssue {
                    asset = table,
                    path = pathPrefix,
                    severity = ValidationSeverity.Error,
                    message = "Null encounter entry."
                });
                return;
            }

            var definition = encounter.EncounterDefinition;
            if(definition == null || string.IsNullOrEmpty(definition.internalId)) {
                issues.Add(new ValidationIssue {
                    asset = table,
                    encounter = encounter,
                    path = $"{pathPrefix}.EncounterDefinition.internalId",
                    severity = ValidationSeverity.Error,
                    message = "Missing internalId (cannot be referenced by EncounterLink)."
                });
            }
            else if(duplicateCheck != null) {
                if(duplicateCheck.TryGetValue(definition.internalId, out var prior)) {
                    issues.Add(new ValidationIssue {
                        asset = table,
                        encounter = encounter,
                        path = $"{pathPrefix}.EncounterDefinition.internalId",
                        severity = ValidationSeverity.Error,
                        message = $"Duplicate internalId '{definition.internalId}' (also in table '{prior.Item2.name}')."
                    });
                }
                else {
                    duplicateCheck[definition.internalId] = (encounter, table);
                }
            }

            if(encounter.EncounterDefinition?.Kind == null) {
                issues.Add(new ValidationIssue {
                    asset = table,
                    encounter = encounter,
                    path = $"{pathPrefix}.EncounterDefinition.Kind",
                    severity = ValidationSeverity.Warning,
                    message = "Encounter.Kind is null — pick a kind in the inspector."
                });
            }

            if(encounter.EncounterDefinition?.Kind is QuestKind questKind) {
                ValidateEncounterLink(table, pathPrefix + ".EncounterDefinition.Kind.nextEncounter", encounter, questKind.nextEncounter, issues);
            }

            ValidateDialogEvents(table, index, encounter, issues);
        }

        private static void ValidateEncounterLink(EncounterTable owningTable, string path, Encounter encounter, EncounterLink link, List<ValidationIssue> issues) {
            // No id picked — terminal quest step / unset link. Not an error regardless of table value.
            if(string.IsNullOrEmpty(link.internalId)) {
                return;
            }

            if(link.table == null) {
                issues.Add(new ValidationIssue {
                    asset = owningTable,
                    encounter = encounter,
                    path = path,
                    severity = ValidationSeverity.Error,
                    message = "EncounterLink has internalId set but no table assigned."
                });
                return;
            }

            if(link.Resolve() == null) {
                issues.Add(new ValidationIssue {
                    asset = owningTable,
                    encounter = encounter,
                    path = path,
                    severity = ValidationSeverity.Error,
                    message = $"Broken EncounterLink — table '{link.table.name}' has no encounter with internalId '{link.internalId}'."
                });
            }
        }

        private static void ValidateDialogEvents(EncounterTable table, int index, Encounter encounter, List<ValidationIssue> issues) {
            var optionSet = encounter.EncounterDialogOptionSet;
            if(optionSet?.options == null) {
                return;
            }

            for(int o = 0; o < optionSet.options.Count; o++) {
                var option = optionSet.options[o];
                if(option == null) {
                    continue;
                }

                ValidateDialogLineRef(optionSet, encounter, $"options[{o}].text", option.text, issues);

                if(option.events == null) {
                    continue;
                }

                for(int e = 0; e < option.events.Count; e++) {
                    var evt = option.events[e];
                    if(evt == null) {
                        issues.Add(new ValidationIssue {
                            asset = optionSet,
                            encounter = encounter,
                            path = $"options[{o}].events[{e}]",
                            severity = ValidationSeverity.Warning,
                            message = "Null event entry (type may have been renamed/deleted)."
                        });
                        continue;
                    }

                    if(evt is GiveRewardEvent give && give.reward == null) {
                        issues.Add(new ValidationIssue {
                            asset = optionSet,
                            encounter = encounter,
                            path = $"options[{o}].events[{e}].reward",
                            severity = ValidationSeverity.Warning,
                            message = "GiveRewardEvent has no Reward asset assigned."
                        });
                    }
                }
            }
        }

        private static void ValidateDialogLineRef(EncounterDialogOptionSet optionSet, Encounter encounter, string path, DialogLineRef lineRef, List<ValidationIssue> issues) {
            var library = optionSet.library;
            var hasId = !string.IsNullOrEmpty(lineRef.id);
            var hasInline = !string.IsNullOrEmpty(lineRef.inlineText);

            if(!hasId && !hasInline) {
                issues.Add(new ValidationIssue {
                    asset = optionSet,
                    encounter = encounter,
                    path = path,
                    severity = ValidationSeverity.Warning,
                    message = "Dialog line is empty (no id and no inline text)."
                });
                return;
            }

            if(hasId && library == null) {
                issues.Add(new ValidationIssue {
                    asset = optionSet,
                    encounter = encounter,
                    path = path,
                    severity = ValidationSeverity.Error,
                    message = $"DialogLineRef references id '{lineRef.id}' but the option set has no library assigned."
                });
                return;
            }

            if(hasId && library != null && string.IsNullOrEmpty(library.Resolve(lineRef.id))) {
                issues.Add(new ValidationIssue {
                    asset = optionSet,
                    encounter = encounter,
                    path = path,
                    severity = ValidationSeverity.Error,
                    message = $"DialogLineRef id '{lineRef.id}' not found in library '{library.name}'."
                });
            }
        }

        private static void ValidateReward(Reward reward, List<ValidationIssue> issues) {
            if(reward == null) {
                return;
            }

            if(reward.kind == null) {
                issues.Add(new ValidationIssue {
                    asset = reward,
                    path = "kind",
                    severity = ValidationSeverity.Warning,
                    message = "Reward.kind is null — pick a reward kind in the inspector."
                });
            }

            if(string.IsNullOrEmpty(reward.id)) {
                issues.Add(new ValidationIssue {
                    asset = reward,
                    path = "id",
                    severity = ValidationSeverity.Warning,
                    message = "Reward has no id."
                });
            }
        }

        private static List<T> FindAssetsOfType<T>() where T : Object {
            var result = new List<T>();
            var guids = AssetDatabase.FindAssets("t:" + typeof(T).Name);
            foreach(var guid in guids) {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<T>(path);
                if(asset != null) {
                    result.Add(asset);
                }
            }
            return result;
        }
    }

    public static class EncounterValidatorMenu {
        [MenuItem("Jovian/Encounters/Validate All")]
        public static void ValidateAll() {
            var issues = EncounterValidator.ValidateProject();
            if(issues.Count == 0) {
                Debug.Log("[EncounterValidator] No issues found.");
                return;
            }

            int errors = 0;
            int warnings = 0;
            foreach(var issue in issues) {
                if(issue.severity == ValidationSeverity.Error) {
                    errors++;
                }
                else {
                    warnings++;
                }
            }

            Debug.Log($"[EncounterValidator] {issues.Count} issue(s) found — {errors} error(s), {warnings} warning(s). Click any log row to ping the offending asset.");

            foreach(var issue in issues) {
                var assetName = issue.asset != null ? issue.asset.name : "<null>";
                var message = $"[EncounterValidator] {assetName} · {issue.path} — {issue.message}";
                if(issue.severity == ValidationSeverity.Error) {
                    Debug.LogError(message, issue.asset);
                }
                else {
                    Debug.LogWarning(message, issue.asset);
                }
            }
        }
    }
}
