using Microsoft.AspNetCore.Identity;
using RpgRoller.Contracts;
using RpgRoller.Domain;

namespace RpgRoller.Services;

public sealed class GameAuthService(GameStateStore stateStore, IPasswordHasher<UserAccount> passwordHasher, GamePersistenceService persistenceService)
{
    public ServiceResult<UserSummary> Register(string username, string password, string displayName)
    {
        if (string.IsNullOrWhiteSpace(username))
            return ServiceResult<UserSummary>.Failure("invalid_username", "Username is required.");

        if (string.IsNullOrWhiteSpace(displayName))
            return ServiceResult<UserSummary>.Failure("invalid_display_name", "Display name is required.");

        if (string.IsNullOrWhiteSpace(password) || password.Length < 8)
            return ServiceResult<UserSummary>.Failure("invalid_password", "Password must be at least 8 characters.");

        lock (stateStore.Gate)
        {
            var trimmedUsername = username.Trim();
            var normalizedUsername = NormalizeUsername(trimmedUsername);
            if (stateStore.UserIdsByUsername.ContainsKey(normalizedUsername))
                return ServiceResult<UserSummary>.Failure("duplicate_username", "Username is already taken.");

            var user = new UserAccount
            {
                Id = Guid.NewGuid(),
                Username = trimmedUsername,
                UsernameNormalized = normalizedUsername,
                DisplayName = displayName.Trim(),
                PasswordHash = string.Empty,
                Roles = stateStore.UsersById.Count == 0 ? UserRoles.Admin : string.Empty,
                ActiveCharacterId = null
            };

            user.PasswordHash = passwordHasher.HashPassword(user, password);

            stateStore.UsersById[user.Id] = user;
            stateStore.UserIdsByUsername[user.UsernameNormalized] = user.Id;

            persistenceService.PersistStateLocked();
            return ServiceResult<UserSummary>.Success(GameDtoMapper.ToUserSummary(user));
        }
    }

    public ServiceResult<(UserSummary User, string SessionToken)> Login(string username, string password)
    {
        if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
            return ServiceResult<(UserSummary User, string SessionToken)>.Failure("invalid_credentials", "Invalid username or password.");

        lock (stateStore.Gate)
        {
            var normalizedUsername = NormalizeUsername(username.Trim());
            if (!stateStore.UserIdsByUsername.TryGetValue(normalizedUsername, out var userId))
                return ServiceResult<(UserSummary User, string SessionToken)>.Failure("invalid_credentials", "Invalid username or password.");

            var user = stateStore.UsersById[userId];
            var verification = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
            if (verification == PasswordVerificationResult.Failed)
                return ServiceResult<(UserSummary User, string SessionToken)>.Failure("invalid_credentials", "Invalid username or password.");

            if (verification == PasswordVerificationResult.SuccessRehashNeeded)
                user.PasswordHash = passwordHasher.HashPassword(user, password);

            var session = CreateSession(userId);
            persistenceService.PersistStateLocked();
            return ServiceResult<(UserSummary User, string SessionToken)>.Success((GameDtoMapper.ToUserSummary(user), session.Token));
        }
    }

    public void Logout(string sessionToken)
    {
        lock (stateStore.Gate)
        {
            if (stateStore.SessionsByToken.Remove(sessionToken))
                persistenceService.PersistStateLocked();
        }
    }

    public UserSummary? GetUserBySession(string sessionToken)
    {
        lock (stateStore.Gate)
        {
            var user = GameContextResolver.ResolveUserLocked(stateStore, sessionToken);
            return user is null ? null : GameDtoMapper.ToUserSummary(user);
        }
    }

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

            Guid? campaignId = null;
            if (user.ActiveCharacterId is Guid activeCharacterId)
            {
                if (!stateStore.CharactersById.TryGetValue(activeCharacterId, out var activeCharacter))
                {
                    user.ActiveCharacterId = null;
                    persistenceService.PersistStateLocked();
                }
                else
                    campaignId = activeCharacter.CampaignId;
            }

            return ServiceResult<MeResponse>.Success(new(GameDtoMapper.ToUserSummary(user), user.ActiveCharacterId, campaignId));
        }
    }

    private UserSession CreateSession(Guid userId)
    {
        var token = Guid.NewGuid().ToString("N");
        var session = new UserSession
        {
            Token = token,
            UserId = userId,
            CreatedAtUtc = DateTimeOffset.UtcNow
        };

        stateStore.SessionsByToken[token] = session;
        return session;
    }

    private static string NormalizeUsername(string username)
    {
        return username.ToUpperInvariant();
    }
}