using RpgRoller.Contracts;
using RpgRoller.Domain;

namespace RpgRoller.Services;

public sealed class GameCampaignService(GameStateStore stateStore, GamePersistenceService persistenceService)
{
    public ServiceResult<CampaignSummary> CreateCampaign(string sessionToken, string name, string rulesetId)
    {
        if (string.IsNullOrWhiteSpace(name))
            return ServiceResult<CampaignSummary>.Failure("invalid_campaign_name", "Campaign name is required.");

        var ruleset = DiceRules.TryParseRulesetId(rulesetId);
        if (ruleset is null)
            return ServiceResult<CampaignSummary>.Failure("invalid_ruleset", "Unknown ruleset.");

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

            var campaign = new Campaign
            {
                Id = Guid.NewGuid(),
                GmUserId = user.Id,
                Name = name.Trim(),
                Ruleset = ruleset.Value,
                Version = 1
            };

            stateStore.CampaignsById[campaign.Id] = campaign;
            persistenceService.PersistStateLocked();
            return ServiceResult<CampaignSummary>.Success(GameDtoMapper.ToCampaignSummary(stateStore, campaign));
        }
    }

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

            var results = stateStore.CampaignsById.Values.Where(campaign => GameAuthorization.CanViewCampaign(stateStore, user.Id, campaign.Id)).OrderBy(campaign => campaign.Name, StringComparer.OrdinalIgnoreCase).Select(campaign => GameDtoMapper.ToCampaignSummary(stateStore, campaign)).ToArray();

            return ServiceResult<IReadOnlyList<CampaignSummary>>.Success(results);
        }
    }

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

            var options = stateStore.CampaignsById.Values.OrderBy(campaign => campaign.Name, StringComparer.OrdinalIgnoreCase).Select(GameDtoMapper.ToCampaignOption).ToArray();

            return ServiceResult<IReadOnlyList<CampaignOption>>.Success(options);
        }
    }

    public ServiceResult<CampaignRoster> GetCampaign(string sessionToken, Guid campaignId)
    {
        lock (stateStore.Gate)
        {
            var context = GameContextResolver.ResolveCampaignContextLocked(stateStore, sessionToken, campaignId);
            if (!context.Succeeded)
                return ServiceResult<CampaignRoster>.Failure(context.Error!.Code, context.Error.Message);

            var (_, campaign) = context.Value;
            return ServiceResult<CampaignRoster>.Success(GameDtoMapper.ToCampaignRoster(stateStore, campaign));
        }
    }

    public ServiceResult<bool> DeleteCampaign(string sessionToken, Guid campaignId)
    {
        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.CampaignsById.TryGetValue(campaignId, out var campaign))
                return ServiceResult<bool>.Failure("campaign_not_found", "Campaign was not found.");

            if (campaign.GmUserId != user.Id && !GameAuthorization.HasRole(user, UserRoles.Admin))
                return ServiceResult<bool>.Failure("forbidden", "Only the campaign owner or admin can delete this campaign.");

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

    private void DeleteCampaignLocked(Guid campaignId)
    {
        if (!stateStore.CampaignsById.Remove(campaignId))
            return;

        var affectedCharacterIds = stateStore.CharactersById.Values.Where(character => character.CampaignId == campaignId).Select(character => character.Id).ToArray();
        foreach (var characterId in affectedCharacterIds)
            stateStore.CharactersById[characterId].CampaignId = null;

        stateStore.RollLog.RemoveAll(entry => entry.CampaignId == campaignId);
        stateStore.CampaignStateById.Remove(campaignId);
    }
}