using RpgRoller.Contracts;
using RpgRoller.Domain;

namespace RpgRoller.Services;

public sealed class GameUserAdministrationService(GameStateStore stateStore, GamePersistenceService persistenceService)
{
    public ServiceResult<IReadOnlyList<string>> GetUsernames(string sessionToken)
    {
        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            if (user is null)
                return ServiceResult<IReadOnlyList<string>>.Failure("unauthorized", "You must be logged in.");

            var usernames = stateStore.UsersById.Values.Select(account => account.Username).OrderBy(username => username, StringComparer.OrdinalIgnoreCase).ToArray();

            return ServiceResult<IReadOnlyList<string>>.Success(usernames);
        }
    }

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

            if (!GameAuthorization.HasRole(user, UserRoles.Admin))
                return ServiceResult<IReadOnlyList<AdminUserSummary>>.Failure("forbidden", "Admin role is required.");

            var users = stateStore.UsersById.Values.OrderBy(account => account.Username, StringComparer.OrdinalIgnoreCase).Select(GameDtoMapper.ToAdminUserSummary).ToArray();

            return ServiceResult<IReadOnlyList<AdminUserSummary>>.Success(users);
        }
    }

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

            if (!GameAuthorization.HasRole(user, UserRoles.Admin))
                return ServiceResult<AdminUserSummary>.Failure("forbidden", "Admin role is required.");

            if (!stateStore.UsersById.TryGetValue(userId, out var targetUser))
                return ServiceResult<AdminUserSummary>.Failure("user_not_found", "User was not found.");

            var normalizedRoles = RoleSerializer.Normalize(roles);
            if (normalizedRoles.Any(role => !string.Equals(role, UserRoles.Admin, StringComparison.Ordinal)))
                return ServiceResult<AdminUserSummary>.Failure("invalid_role", "Unsupported role.");

            if (user.Id == targetUser.Id && !normalizedRoles.Contains(UserRoles.Admin, StringComparer.Ordinal))
                return ServiceResult<AdminUserSummary>.Failure("forbidden", "You cannot remove your own admin role.");

            targetUser.Roles = RoleSerializer.Serialize(normalizedRoles);
            persistenceService.PersistStateLocked();
            return ServiceResult<AdminUserSummary>.Success(GameDtoMapper.ToAdminUserSummary(targetUser));
        }
    }

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

            if (!GameAuthorization.HasRole(user, UserRoles.Admin))
                return ServiceResult<bool>.Failure("forbidden", "Admin role is required.");

            if (user.Id == userId)
                return ServiceResult<bool>.Failure("forbidden", "You cannot delete your own account.");

            if (!stateStore.UsersById.TryGetValue(userId, out var targetUser))
                return ServiceResult<bool>.Failure("user_not_found", "User was not found.");

            var gmCampaignIds = stateStore.CampaignsById.Values.Where(campaign => campaign.GmUserId == targetUser.Id).Select(campaign => campaign.Id).ToArray();
            var gmCampaignIdSet = gmCampaignIds.ToHashSet();
            var preservedCharacterIds = stateStore.CharactersById.Values.Where(character => character.CampaignId.HasValue && gmCampaignIdSet.Contains(character.CampaignId.Value)).Select(character => character.Id).ToHashSet();

            foreach (var campaignId in gmCampaignIds)
                DeleteCampaignLocked(campaignId);

            var ownedCharacterIds = stateStore.CharactersById.Values.Where(character => character.OwnerUserId == targetUser.Id && !preservedCharacterIds.Contains(character.Id)).Select(character => character.Id).ToArray();
            foreach (var characterId in ownedCharacterIds)
                DeleteCharacterLocked(characterId);

            stateStore.RollLog.RemoveAll(entry => entry.RollerUserId == targetUser.Id);

            var staleSessions = stateStore.SessionsByToken.Values.Where(session => session.UserId == targetUser.Id).Select(session => session.Token).ToArray();
            foreach (var token in staleSessions)
                stateStore.SessionsByToken.Remove(token);

            stateStore.UsersById.Remove(targetUser.Id);
            stateStore.UserIdsByUsername.Remove(targetUser.UsernameNormalized);

            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);
    }

    private void DeleteCharacterLocked(Guid characterId)
    {
        if (!stateStore.CharactersById.TryGetValue(characterId, out var character))
            return;

        var campaignId = character.CampaignId;
        stateStore.CharactersById.Remove(characterId);

        var skillGroupIds = stateStore.SkillGroupsById.Values.Where(group => group.CharacterId == characterId).Select(group => group.Id).ToHashSet();
        foreach (var skillGroupId in skillGroupIds)
            stateStore.SkillGroupsById.Remove(skillGroupId);

        var skillIds = stateStore.SkillsById.Values.Where(skill => skill.CharacterId == characterId).Select(skill => skill.Id).ToHashSet();
        foreach (var skillId in skillIds)
            stateStore.SkillsById.Remove(skillId);

        stateStore.RollLog.RemoveAll(entry => entry.CharacterId == characterId || skillIds.Contains(entry.SkillId));

        foreach (var account in stateStore.UsersById.Values.Where(account => account.ActiveCharacterId == characterId))
            account.ActiveCharacterId = null;

        stateStore.RemoveCharacterStateLocked(campaignId, characterId);
        stateStore.TouchRosterLocked(campaignId);
    }
}