using System;
using System.Collections.Generic;
using System.Linq;

namespace FishDice.LuckyDice
{
    public static class LuckyDiceConfigValidator
    {
        public static ConfigValidationResult Validate(
            IReadOnlyList<ComboRuleDefinition> rules,
            IReadOnlyList<LuckyDiceCandidateDefinition> candidates,
            IReadOnlyList<TargetModeDefinition> targetModes,
            IReadOnlyList<IComboMatcher> matchers,
            IReadOnlyList<IRollActionExecutor> actionExecutors)
        {
            var errors = new List<ConfigValidationError>();
            var matcherTypes = ValidateMatchers(errors, matchers);
            var actionTypes = ValidateActionExecutors(errors, actionExecutors);
            var ruleIds = new HashSet<string>();

            foreach (var rule in rules ?? Array.Empty<ComboRuleDefinition>())
            {
                if (rule == null)
                {
                    errors.Add(new ConfigValidationError("NullRule", "Rule cannot be null."));
                    continue;
                }

                if (string.IsNullOrEmpty(rule.RuleId))
                {
                    errors.Add(new ConfigValidationError("EmptyRuleId", "Rule id is required."));
                }
                else if (!ruleIds.Add(rule.RuleId))
                {
                    errors.Add(new ConfigValidationError("DuplicateRuleId", "Duplicate rule id: " + rule.RuleId));
                }

                if (!matcherTypes.Contains(rule.MatcherType))
                {
                    errors.Add(new ConfigValidationError("UnknownMatcher", "Unknown matcher: " + rule.MatcherType));
                }

                foreach (var action in rule.Actions ?? Array.Empty<RollActionDefinition>())
                {
                    if (action == null)
                    {
                        errors.Add(new ConfigValidationError("NullAction", "Action cannot be null."));
                        continue;
                    }

                    if (!actionTypes.Contains(action.ActionType))
                    {
                        errors.Add(new ConfigValidationError("UnknownAction", "Unknown action: " + action.ActionType));
                    }

                    ValidateActionParams(errors, action.Params);
                }
            }

            if ((candidates ?? Array.Empty<LuckyDiceCandidateDefinition>()).All(candidate => candidate == null || !candidate.IsDefault))
            {
                errors.Add(new ConfigValidationError("MissingDefaultCandidate", "Lucky Dice needs one default candidate."));
            }

            var targetModeKeys = new HashSet<string>();
            foreach (var targetMode in targetModes ?? Array.Empty<TargetModeDefinition>())
            {
                if (targetMode == null)
                {
                    errors.Add(new ConfigValidationError("NullTargetMode", "Target mode cannot be null."));
                    continue;
                }

                if (string.IsNullOrEmpty(targetMode.ResultKey))
                {
                    errors.Add(new ConfigValidationError("EmptyTargetModeResultKey", "Target mode result key is required."));
                }

                if (string.IsNullOrEmpty(targetMode.TargetKey))
                {
                    errors.Add(new ConfigValidationError("EmptyTargetModeTargetKey", "Target mode target key is required."));
                }

                if (string.IsNullOrEmpty(targetMode.ModeKey))
                {
                    errors.Add(new ConfigValidationError("EmptyTargetModeModeKey", "Target mode mode key is required."));
                }

                var targetModeKey = targetMode.ResultKey + "|" + targetMode.TargetKey;
                if (!targetModeKeys.Add(targetModeKey))
                {
                    errors.Add(new ConfigValidationError("DuplicateTargetMode", "Duplicate target mode: " + targetModeKey));
                }
            }

            var candidateKeys = new HashSet<string>();
            foreach (var candidate in candidates ?? Array.Empty<LuckyDiceCandidateDefinition>())
            {
                if (candidate == null)
                {
                    errors.Add(new ConfigValidationError("NullCandidate", "Candidate cannot be null."));
                    continue;
                }

                if (string.IsNullOrEmpty(candidate.ResultKey))
                {
                    errors.Add(new ConfigValidationError("EmptyCandidateResultKey", "Candidate result key is required."));
                }

                if (string.IsNullOrEmpty(candidate.TargetKey))
                {
                    errors.Add(new ConfigValidationError("EmptyCandidateTargetKey", "Candidate target key is required."));
                }

                var candidateKey = candidate.ResultKey + "|" + candidate.TargetKey;
                if (!candidateKeys.Add(candidateKey))
                {
                    errors.Add(new ConfigValidationError("DuplicateCandidate", "Duplicate candidate: " + candidateKey));
                }

                if (candidate.Weight < 0)
                {
                    errors.Add(new ConfigValidationError("NegativeCandidateWeight", "Candidate weight cannot be negative: " + candidate.ResultKey));
                }

                if (candidate.ResultSlotCount.HasValue && candidate.ResultSlotCount.Value <= 0)
                {
                    errors.Add(new ConfigValidationError("InvalidResultSlotCount", "Result slot count must be greater than zero: " + candidate.ResultKey));
                }

                if (candidate.IsDefault && !candidate.Enabled)
                {
                    errors.Add(new ConfigValidationError("DisabledDefaultCandidate", "Default candidate cannot be disabled: " + candidate.ResultKey));
                }

                var hasMode = (targetModes ?? Array.Empty<TargetModeDefinition>())
                    .Any(mode => mode != null && mode.ResultKey == candidate.ResultKey && mode.TargetKey == candidate.TargetKey);
                if (!hasMode)
                {
                    errors.Add(new ConfigValidationError("MissingTargetMode", "Missing target mode for result: " + candidate.ResultKey));
                }
            }

            return new ConfigValidationResult(errors);
        }

        private static HashSet<string> ValidateMatchers(
            List<ConfigValidationError> errors,
            IReadOnlyList<IComboMatcher> matchers)
        {
            var matcherTypes = new HashSet<string>();
            foreach (var matcher in matchers ?? Array.Empty<IComboMatcher>())
            {
                if (matcher == null)
                {
                    errors.Add(new ConfigValidationError("NullMatcher", "Matcher cannot be null."));
                    continue;
                }

                if (string.IsNullOrEmpty(matcher.MatcherType))
                {
                    errors.Add(new ConfigValidationError("EmptyMatcherType", "Matcher type is required."));
                    continue;
                }

                if (!matcherTypes.Add(matcher.MatcherType))
                {
                    errors.Add(new ConfigValidationError("DuplicateMatcherType", "Duplicate matcher type: " + matcher.MatcherType));
                }
            }

            return matcherTypes;
        }

        private static HashSet<string> ValidateActionExecutors(
            List<ConfigValidationError> errors,
            IReadOnlyList<IRollActionExecutor> actionExecutors)
        {
            var actionTypes = new HashSet<string>();
            foreach (var executor in actionExecutors ?? Array.Empty<IRollActionExecutor>())
            {
                if (executor == null)
                {
                    errors.Add(new ConfigValidationError("NullActionExecutor", "Action executor cannot be null."));
                    continue;
                }

                if (string.IsNullOrEmpty(executor.ActionType))
                {
                    errors.Add(new ConfigValidationError("EmptyActionExecutorType", "Action executor type is required."));
                    continue;
                }

                if (!actionTypes.Add(executor.ActionType))
                {
                    errors.Add(new ConfigValidationError("DuplicateActionExecutorType", "Duplicate action executor type: " + executor.ActionType));
                }
            }

            return actionTypes;
        }

        private static void ValidateActionParams(
            List<ConfigValidationError> errors,
            IReadOnlyList<ActionParamDefinition> actionParams)
        {
            var paramKeys = new HashSet<string>();
            foreach (var param in actionParams ?? Array.Empty<ActionParamDefinition>())
            {
                if (param == null)
                {
                    errors.Add(new ConfigValidationError("NullActionParam", "Action param cannot be null."));
                    continue;
                }

                if (string.IsNullOrEmpty(param.Key))
                {
                    errors.Add(new ConfigValidationError("EmptyActionParamKey", "Action param key is required."));
                    continue;
                }

                if (!paramKeys.Add(param.Key))
                {
                    errors.Add(new ConfigValidationError("DuplicateActionParamKey", "Duplicate action param key: " + param.Key));
                }
            }
        }
    }

    public sealed class ConfigValidationResult
    {
        public ConfigValidationResult(IReadOnlyList<ConfigValidationError> errors)
        {
            Errors = errors == null ? Array.Empty<ConfigValidationError>() : errors.ToArray();
        }

        public IReadOnlyList<ConfigValidationError> Errors { get; }

        public bool IsValid => Errors.Count == 0;
    }

    public sealed class ConfigValidationError
    {
        public ConfigValidationError(string code, string message)
        {
            Code = code;
            Message = message;
        }

        public string Code { get; }

        public string Message { get; }
    }
}
