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

namespace FishDice.LuckyDice
{
    public sealed class LuckyDiceRollFlowService
    {
        private readonly LuckyDiceDefaultConfig _config;
        private readonly DiceRollService _rollService;
        private readonly ComboRuleMatcher _ruleMatcher;
        private readonly RollActionDispatcher _actionDispatcher;
        private readonly LuckyDiceFlowService _luckyDiceFlowService;
        private readonly TargetModeResolver _targetModeResolver;

        public LuckyDiceRollFlowService(
            LuckyDiceDefaultConfig config,
            DiceRollService rollService,
            ComboRuleMatcher ruleMatcher,
            RollActionDispatcher actionDispatcher,
            LuckyDiceFlowService luckyDiceFlowService,
            TargetModeResolver targetModeResolver)
        {
            _config = config ?? throw new ArgumentNullException(nameof(config));
            _rollService = rollService ?? throw new ArgumentNullException(nameof(rollService));
            _ruleMatcher = ruleMatcher ?? throw new ArgumentNullException(nameof(ruleMatcher));
            _actionDispatcher = actionDispatcher ?? throw new ArgumentNullException(nameof(actionDispatcher));
            _luckyDiceFlowService = luckyDiceFlowService ?? throw new ArgumentNullException(nameof(luckyDiceFlowService));
            _targetModeResolver = targetModeResolver ?? throw new ArgumentNullException(nameof(targetModeResolver));
        }

        public RollFlowResult Execute(RollFlowRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (string.IsNullOrWhiteSpace(request.RollSessionId))
            {
                throw new ArgumentException("Roll session id is required.", nameof(request));
            }

            if (string.IsNullOrWhiteSpace(request.DiceSetId))
            {
                throw new ArgumentException("Dice set id is required.", nameof(request));
            }

            if (string.IsNullOrWhiteSpace(request.TriggerSource))
            {
                throw new ArgumentException("Trigger source is required.", nameof(request));
            }

            var trace = new RollTrace(request.RollSessionId);
            var rollResult = _rollService.Roll(
                _config.GetDiceSet(request.DiceSetId),
                request.RollSessionId,
                request.TriggerSource);
            var facts = ComboFactBuilder.Build(rollResult);
            trace.RecordComboFacts(facts);

            var match = _ruleMatcher.Match(facts, _config.ComboRules);
            trace.RecordRuleMatch(match);

            var actionContext = new RollActionContext(request.RollSessionId, facts.NormalizedKey, trace);
            var actionResults = _actionDispatcher.Execute(
                actionContext,
                match.Matches.SelectMany(rule => rule.Actions).ToArray());
            var effects = actionResults.SelectMany(result => result.Effects).ToList();

            LuckyDiceSelectionResult luckyDiceResult = null;
            TargetModeEntry targetMode = null;
            if (effects.OfType<LuckyDiceRequestedEffect>().Any())
            {
                luckyDiceResult = _luckyDiceFlowService.Select(
                    _config.LuckyDiceCandidates,
                    request,
                    _config.GetDiceSet(LuckyDiceKeys.LuckyDiceSetId));
                trace.RecordLuckyDiceSelection(luckyDiceResult);

                var targetModeResult = _targetModeResolver.Resolve(
                    luckyDiceResult.Selected,
                    request,
                    rollResult.Multiplier,
                    _config.TargetModeDefinitions);

                if (targetModeResult.Success)
                {
                    targetMode = targetModeResult.Entry;
                    trace.RecordTargetMode(targetMode);
                    effects.Add(new EnterTargetModeEffect(targetMode.TargetKey, targetMode.ModeKey));
                }
                else
                {
                    trace.RecordTargetModeFailure(targetModeResult.FailureReason);
                    effects.Add(new TargetModeFailedEffect(targetModeResult.FailureReason, targetModeResult.FallbackMode));
                }
            }

            return new RollFlowResult(
                request.RollSessionId,
                rollResult,
                facts,
                match.Matches,
                actionResults,
                effects,
                luckyDiceResult,
                targetMode,
                trace);
        }
    }
}
