using RpgRoller.Contracts;
using RpgRoller.Domain;

namespace RpgRoller.Services;

public sealed class GameSkillService(GameStateStore stateStore, GamePersistenceService persistenceService)
{
    public ServiceResult<SkillGroupSummary> CreateSkillGroup(string sessionToken, Guid characterId, string name, string diceRollDefinition, int wildDice, bool allowFumble, int? fumbleRange = null)
    {
        if (string.IsNullOrWhiteSpace(name))
            return ServiceResult<SkillGroupSummary>.Failure("invalid_skill_group_name", "Skill group name is required.");

        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            if (user is null)
                return ServiceResult<SkillGroupSummary>.Failure("unauthorized", "You must be logged in.");

            if (!stateStore.CharactersById.TryGetValue(characterId, out var character))
                return ServiceResult<SkillGroupSummary>.Failure("character_not_found", "Character was not found.");

            if (!GameContextResolver.TryResolveCharacterCampaignLocked(stateStore, character, out var campaign, out var campaignError))
                return ServiceResult<SkillGroupSummary>.Failure(campaignError!.Code, campaignError.Message);

            if (!GameAuthorization.CanEditCharacter(user.Id, character, campaign))
                return ServiceResult<SkillGroupSummary>.Failure("forbidden", "Only the owner or GM can manage skill groups.");

            var prototypeValidation = SkillDefinitionValidator.Validate(campaign.Ruleset, diceRollDefinition, wildDice, allowFumble, fumbleRange);
            if (!prototypeValidation.Succeeded)
                return ServiceResult<SkillGroupSummary>.Failure(prototypeValidation.Error!.Code, prototypeValidation.Error.Message);

            var group = new SkillGroup
            {
                Id = Guid.NewGuid(),
                CharacterId = character.Id,
                Name = name.Trim(),
                DiceRollDefinition = prototypeValidation.Value.CanonicalExpression,
                WildDice = prototypeValidation.Value.WildDice,
                AllowFumble = prototypeValidation.Value.AllowFumble,
                FumbleRange = prototypeValidation.Value.FumbleRange
            };

            stateStore.SkillGroupsById[group.Id] = group;
            stateStore.TouchCharacterLocked(campaign.Id, character.Id);

            persistenceService.PersistStateLocked();
            return ServiceResult<SkillGroupSummary>.Success(GameDtoMapper.ToSkillGroupSummary(group));
        }
    }

    public ServiceResult<SkillGroupSummary> UpdateSkillGroup(string sessionToken, Guid skillGroupId, string name, string diceRollDefinition, int wildDice, bool allowFumble, int? fumbleRange = null)
    {
        if (string.IsNullOrWhiteSpace(name))
            return ServiceResult<SkillGroupSummary>.Failure("invalid_skill_group_name", "Skill group name is required.");

        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            if (user is null)
                return ServiceResult<SkillGroupSummary>.Failure("unauthorized", "You must be logged in.");

            if (!stateStore.SkillGroupsById.TryGetValue(skillGroupId, out var group))
                return ServiceResult<SkillGroupSummary>.Failure("skill_group_not_found", "Skill group was not found.");

            var character = stateStore.CharactersById[group.CharacterId];
            if (!GameContextResolver.TryResolveCharacterCampaignLocked(stateStore, character, out var campaign, out var campaignError))
                return ServiceResult<SkillGroupSummary>.Failure(campaignError!.Code, campaignError.Message);

            if (!GameAuthorization.CanEditCharacter(user.Id, character, campaign))
                return ServiceResult<SkillGroupSummary>.Failure("forbidden", "Only the owner or GM can manage skill groups.");

            var prototypeValidation = SkillDefinitionValidator.Validate(campaign.Ruleset, diceRollDefinition, wildDice, allowFumble, fumbleRange);
            if (!prototypeValidation.Succeeded)
                return ServiceResult<SkillGroupSummary>.Failure(prototypeValidation.Error!.Code, prototypeValidation.Error.Message);

            group.Name = name.Trim();
            group.DiceRollDefinition = prototypeValidation.Value.CanonicalExpression;
            group.WildDice = prototypeValidation.Value.WildDice;
            group.AllowFumble = prototypeValidation.Value.AllowFumble;
            group.FumbleRange = prototypeValidation.Value.FumbleRange;
            stateStore.TouchCharacterLocked(campaign.Id, character.Id);

            persistenceService.PersistStateLocked();
            return ServiceResult<SkillGroupSummary>.Success(GameDtoMapper.ToSkillGroupSummary(group));
        }
    }

    public ServiceResult<bool> DeleteSkillGroup(string sessionToken, Guid skillGroupId)
    {
        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            if (user is null)
                return ServiceResult<bool>.Failure("unauthorized", "You must be logged in.");

            if (!stateStore.SkillGroupsById.TryGetValue(skillGroupId, out var group))
                return ServiceResult<bool>.Failure("skill_group_not_found", "Skill group was not found.");

            var character = stateStore.CharactersById[group.CharacterId];
            if (!GameContextResolver.TryResolveCharacterCampaignLocked(stateStore, character, out var campaign, out var campaignError))
                return ServiceResult<bool>.Failure(campaignError!.Code, campaignError.Message);

            if (!GameAuthorization.CanEditCharacter(user.Id, character, campaign))
                return ServiceResult<bool>.Failure("forbidden", "Only the owner or GM can manage skill groups.");

            foreach (var skill in stateStore.SkillsById.Values.Where(skill => skill.SkillGroupId == group.Id))
                skill.SkillGroupId = null;

            stateStore.SkillGroupsById.Remove(group.Id);
            stateStore.TouchCharacterLocked(campaign.Id, character.Id);

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

    public ServiceResult<SkillSummary> CreateSkill(string sessionToken, Guid characterId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null, int? fumbleRange = null, bool rolemasterAutoRetry = false)
    {
        if (string.IsNullOrWhiteSpace(name))
            return ServiceResult<SkillSummary>.Failure("invalid_skill_name", "Skill name is required.");

        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            if (user is null)
                return ServiceResult<SkillSummary>.Failure("unauthorized", "You must be logged in.");

            if (!stateStore.CharactersById.TryGetValue(characterId, out var character))
                return ServiceResult<SkillSummary>.Failure("character_not_found", "Character was not found.");

            if (!GameContextResolver.TryResolveCharacterCampaignLocked(stateStore, character, out var campaign, out var campaignError))
                return ServiceResult<SkillSummary>.Failure(campaignError!.Code, campaignError.Message);

            if (!GameAuthorization.CanEditCharacter(user.Id, character, campaign))
                return ServiceResult<SkillSummary>.Failure("forbidden", "Only the owner or GM can edit skills.");

            var skillValidation = SkillDefinitionValidator.Validate(campaign.Ruleset, diceRollDefinition, wildDice, allowFumble, fumbleRange, rolemasterAutoRetry);
            if (!skillValidation.Succeeded)
                return ServiceResult<SkillSummary>.Failure(skillValidation.Error!.Code, skillValidation.Error.Message);

            var resolvedSkillGroupId = ResolveSkillGroupForSkillChangeLocked(skillGroupId, character.Id);
            if (!resolvedSkillGroupId.Succeeded)
                return ServiceResult<SkillSummary>.Failure(resolvedSkillGroupId.Error!.Code, resolvedSkillGroupId.Error.Message);

            var skill = new Skill
            {
                Id = Guid.NewGuid(),
                CharacterId = character.Id,
                SkillGroupId = resolvedSkillGroupId.Value,
                Name = name.Trim(),
                DiceRollDefinition = skillValidation.Value.CanonicalExpression,
                WildDice = skillValidation.Value.WildDice,
                AllowFumble = skillValidation.Value.AllowFumble,
                FumbleRange = skillValidation.Value.FumbleRange,
                RolemasterAutoRetry = skillValidation.Value.RolemasterAutoRetry
            };

            stateStore.SkillsById[skill.Id] = skill;
            stateStore.TouchCharacterLocked(campaign.Id, character.Id);

            persistenceService.PersistStateLocked();
            return ServiceResult<SkillSummary>.Success(GameDtoMapper.ToSkillSummary(skill));
        }
    }

    public ServiceResult<SkillSummary> UpdateSkill(string sessionToken, Guid skillId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null, int? fumbleRange = null, bool rolemasterAutoRetry = false)
    {
        if (string.IsNullOrWhiteSpace(name))
            return ServiceResult<SkillSummary>.Failure("invalid_skill_name", "Skill name is required.");

        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            if (user is null)
                return ServiceResult<SkillSummary>.Failure("unauthorized", "You must be logged in.");

            if (!stateStore.SkillsById.TryGetValue(skillId, out var skill))
                return ServiceResult<SkillSummary>.Failure("skill_not_found", "Skill was not found.");

            var character = stateStore.CharactersById[skill.CharacterId];
            if (!GameContextResolver.TryResolveCharacterCampaignLocked(stateStore, character, out var campaign, out var campaignError))
                return ServiceResult<SkillSummary>.Failure(campaignError!.Code, campaignError.Message);

            if (!GameAuthorization.CanEditCharacter(user.Id, character, campaign))
                return ServiceResult<SkillSummary>.Failure("forbidden", "Only the owner or GM can edit skills.");

            var skillValidation = SkillDefinitionValidator.Validate(campaign.Ruleset, diceRollDefinition, wildDice, allowFumble, fumbleRange, rolemasterAutoRetry);
            if (!skillValidation.Succeeded)
                return ServiceResult<SkillSummary>.Failure(skillValidation.Error!.Code, skillValidation.Error.Message);

            var resolvedSkillGroupId = ResolveSkillGroupForSkillChangeLocked(skillGroupId, character.Id);
            if (!resolvedSkillGroupId.Succeeded)
                return ServiceResult<SkillSummary>.Failure(resolvedSkillGroupId.Error!.Code, resolvedSkillGroupId.Error.Message);

            skill.Name = name.Trim();
            skill.DiceRollDefinition = skillValidation.Value.CanonicalExpression;
            skill.WildDice = skillValidation.Value.WildDice;
            skill.AllowFumble = skillValidation.Value.AllowFumble;
            skill.FumbleRange = skillValidation.Value.FumbleRange;
            skill.RolemasterAutoRetry = skillValidation.Value.RolemasterAutoRetry;
            skill.SkillGroupId = resolvedSkillGroupId.Value;
            stateStore.TouchCharacterLocked(campaign.Id, character.Id);

            persistenceService.PersistStateLocked();
            return ServiceResult<SkillSummary>.Success(GameDtoMapper.ToSkillSummary(skill));
        }
    }

    public ServiceResult<bool> DeleteSkill(string sessionToken, Guid skillId)
    {
        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            if (user is null)
                return ServiceResult<bool>.Failure("unauthorized", "You must be logged in.");

            if (!stateStore.SkillsById.TryGetValue(skillId, out var skill))
                return ServiceResult<bool>.Failure("skill_not_found", "Skill was not found.");

            var character = stateStore.CharactersById[skill.CharacterId];
            if (!GameContextResolver.TryResolveCharacterCampaignLocked(stateStore, character, out var campaign, out var campaignError))
                return ServiceResult<bool>.Failure(campaignError!.Code, campaignError.Message);

            if (!GameAuthorization.CanEditCharacter(user.Id, character, campaign))
                return ServiceResult<bool>.Failure("forbidden", "Only the owner or GM can edit skills.");

            stateStore.SkillsById.Remove(skill.Id);
            stateStore.TouchCharacterLocked(campaign.Id, character.Id);

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

    public ServiceResult<CharacterSheet> GetCharacterSheet(string sessionToken, Guid characterId)
    {
        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            if (user is null)
                return ServiceResult<CharacterSheet>.Failure("unauthorized", "You must be logged in.");

            if (!stateStore.CharactersById.TryGetValue(characterId, out var character))
                return ServiceResult<CharacterSheet>.Failure("character_not_found", "Character was not found.");

            if (!GameContextResolver.TryResolveCharacterCampaignLocked(stateStore, character, out var campaign, out var campaignError))
                return ServiceResult<CharacterSheet>.Failure(campaignError!.Code, campaignError.Message);

            if (!GameAuthorization.CanViewCampaign(stateStore, user.Id, campaign.Id))
                return ServiceResult<CharacterSheet>.Failure("forbidden", "You are not a participant in this campaign.");

            return ServiceResult<CharacterSheet>.Success(GameDtoMapper.ToCharacterSheet(stateStore, character.Id));
        }
    }

    private ServiceResult<Guid?> ResolveSkillGroupForSkillChangeLocked(Guid? requestedSkillGroupId, Guid characterId)
    {
        if (!requestedSkillGroupId.HasValue)
            return ServiceResult<Guid?>.Success(null);

        if (!stateStore.SkillGroupsById.TryGetValue(requestedSkillGroupId.Value, out var skillGroup))
            return ServiceResult<Guid?>.Failure("skill_group_not_found", "Skill group was not found.");

        if (skillGroup.CharacterId != characterId)
            return ServiceResult<Guid?>.Failure("invalid_skill_group", "Skill group must belong to the same character.");

        return ServiceResult<Guid?>.Success(skillGroup.Id);
    }
}