using System.Text.RegularExpressions;
using RpgRoller.Domain;

namespace RpgRoller.Services;

public static partial class DiceRules
{
    public static RulesetKind? TryParseRulesetId(string rulesetId)
    {
        if (string.Equals(rulesetId, "d6", StringComparison.OrdinalIgnoreCase))
            return RulesetKind.D6;

        if (string.Equals(rulesetId, "dnd5e", StringComparison.OrdinalIgnoreCase))
            return RulesetKind.Dnd5e;

        if (string.Equals(rulesetId, "rolemaster", StringComparison.OrdinalIgnoreCase))
            return RulesetKind.Rolemaster;

        return null;
    }

    public static string ToRulesetId(RulesetKind ruleset)
    {
        return ruleset switch
        {
            RulesetKind.D6         => "d6",
            RulesetKind.Dnd5e      => "dnd5e",
            RulesetKind.Rolemaster => "rolemaster",
            _                      => throw new ArgumentOutOfRangeException(nameof(ruleset), ruleset, "Unknown ruleset.")
        };
    }

    public static ServiceResult<DiceExpression> ParseExpression(RulesetKind ruleset, string expression)
    {
        if (string.IsNullOrWhiteSpace(expression))
            return ServiceResult<DiceExpression>.Failure("invalid_expression", "Dice expression is required.");

        var trimmed = expression.Trim();
        return ruleset switch
        {
            RulesetKind.D6         => ParseD6(trimmed),
            RulesetKind.Dnd5e      => ParseDnd5e(trimmed),
            RulesetKind.Rolemaster => ParseRolemaster(trimmed),
            _                      => ServiceResult<DiceExpression>.Failure("invalid_ruleset", "Unknown ruleset.")
        };
    }

    private static ServiceResult<DiceExpression> ParseD6(string expression)
    {
        var match = D6Regex().Match(expression);
        if (!match.Success)
            return ServiceResult<DiceExpression>.Failure("invalid_expression", "Expected d6 format like 5D+4.");

        var diceCount = int.Parse(match.Groups["count"].Value);
        var modifier = ParseModifier(match.Groups["modifier"].Value);
        var validation = ValidateDiceParts(diceCount, 6, modifier);
        if (!validation.Succeeded)
            return ServiceResult<DiceExpression>.Failure(validation.Error!.Code, validation.Error.Message);

        return ServiceResult<DiceExpression>.Success(new(diceCount, 6, modifier, $"{diceCount}D{FormatModifier(modifier)}"));
    }

    private static ServiceResult<DiceExpression> ParseDnd5e(string expression)
    {
        var match = Dnd5eRegex().Match(expression);
        if (!match.Success)
            return ServiceResult<DiceExpression>.Failure("invalid_expression", "Expected dnd5e format like 2d12+2.");

        var diceCount = int.Parse(match.Groups["count"].Value);
        var sides = int.Parse(match.Groups["sides"].Value);
        var modifier = ParseModifier(match.Groups["modifier"].Value);
        var validation = ValidateDiceParts(diceCount, sides, modifier);
        if (!validation.Succeeded)
            return ServiceResult<DiceExpression>.Failure(validation.Error!.Code, validation.Error.Message);

        return ServiceResult<DiceExpression>.Success(new(diceCount, sides, modifier, $"{diceCount}d{sides}{FormatModifier(modifier)}"));
    }

    private static ServiceResult<DiceExpression> ParseRolemaster(string expression)
    {
        var match = RolemasterRegex().Match(expression);
        if (!match.Success)
            return ServiceResult<DiceExpression>.Failure("invalid_expression", "Expected Rolemaster format like d10+4, 15d10, d100-15, or d100!+85.");

        var countValue = match.Groups["count"].Value;
        var diceCount = string.IsNullOrEmpty(countValue) ? 1 : int.Parse(countValue);
        var sides = int.Parse(match.Groups["sides"].Value);
        var modifier = ParseModifier(match.Groups["modifier"].Value);
        var validation = ValidateDiceParts(diceCount, sides, modifier, -MaxModifier);
        if (!validation.Succeeded)
            return ServiceResult<DiceExpression>.Failure(validation.Error!.Code, validation.Error.Message);

        var isOpenEnded = match.Groups["openEnded"].Success;
        if (isOpenEnded && (diceCount != 1 || sides != 100))
            return ServiceResult<DiceExpression>.Failure("invalid_expression", "Open-ended Rolemaster rolls must use d100! with an implicit or explicit dice count of 1.");

        var countPrefix = diceCount == 1 ? string.Empty : diceCount.ToString();
        var canonical = $"{countPrefix}d{sides}{(isOpenEnded ? "!" : string.Empty)}{FormatModifier(modifier)}";
        var kind = isOpenEnded ? DiceExpressionKind.RolemasterOpenEndedPercentile : DiceExpressionKind.Standard;
        return ServiceResult<DiceExpression>.Success(new(diceCount, sides, modifier, canonical, kind));
    }

    private static ServiceResult<bool> ValidateDiceParts(int diceCount, int sides, int modifier, int minModifier = 0, int maxModifier = MaxModifier)
    {
        if (diceCount < 1 || diceCount > MaxDiceCount)
            return ServiceResult<bool>.Failure("invalid_expression", $"Dice count must be between 1 and {MaxDiceCount}.");

        if (sides < 2 || sides > MaxSides)
            return ServiceResult<bool>.Failure("invalid_expression", $"Dice sides must be between 2 and {MaxSides}.");

        if (modifier < minModifier || modifier > maxModifier)
            return ServiceResult<bool>.Failure("invalid_expression", $"Modifier must be between {minModifier} and {maxModifier}.");

        return ServiceResult<bool>.Success(true);
    }

    private static int ParseModifier(string value)
    {
        return string.IsNullOrEmpty(value) ? 0 : int.Parse(value);
    }

    private static string FormatModifier(int modifier)
    {
        return modifier switch
        {
            > 0 => $"+{modifier}",
            < 0 => modifier.ToString(),
            _   => string.Empty
        };
    }

    [GeneratedRegex("^(?<count>\\d+)D(?:\\+(?<modifier>\\d+))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
    private static partial Regex D6Regex();

    [GeneratedRegex("^(?<count>\\d+)d(?<sides>\\d+)(?:\\+(?<modifier>\\d+))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
    private static partial Regex Dnd5eRegex();

    [GeneratedRegex("^(?<count>\\d+)?d(?<sides>\\d+)(?<openEnded>!)?(?<modifier>[+-]\\d+)?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
    private static partial Regex RolemasterRegex();

    private const int MaxDiceCount = 50;
    private const int MaxSides = 1000;
    private const int MaxModifier = 1000;

    public static readonly IReadOnlyList<(RulesetKind Kind, string Id, string Name, string DiceSyntax)> SupportedRulesets =
    [
        (RulesetKind.D6, "d6", "D6 System", "countD(+modifier), e.g. 5D+4"),
        (RulesetKind.Dnd5e, "dnd5e", "D&D 5e", "countdSides(+modifier), e.g. 2d12+2"),
        (RulesetKind.Rolemaster, "rolemaster", "Rolemaster", "countdSides(+/-modifier), e.g. d10, 15d10, d100-15, d100!+85")
    ];
}