namespace SpaceGame.Api.Auth.Simulation;

public sealed class AuthService(
    IAuthRepository authRepository,
    LocalPasswordHasher passwordHasher,
    ITokenService tokenService,
    RefreshTokenFactory refreshTokenFactory,
    IPasswordResetDelivery passwordResetDelivery)
{
    public async Task<RegisterResponse> RegisterAsync(RegisterRequest request, CancellationToken cancellationToken)
    {
        var email = NormalizeEmail(request.Email);
        ValidatePassword(request.Password);

        if (await authRepository.FindUserByEmailAsync(email, cancellationToken) is not null)
        {
            throw new InvalidOperationException("An account already exists for that email.");
        }

        var user = await authRepository.CreateUserAsync(email, passwordHasher.HashPassword(request.Password), [], cancellationToken);
        return new RegisterResponse(user.Id, user.Email, true);
    }

    public async Task<AuthSessionResponse> LoginAsync(LoginRequest request, CancellationToken cancellationToken)
    {
        var email = NormalizeEmail(request.Email);
        var user = await authRepository.FindUserByEmailAsync(email, cancellationToken)
          ?? throw new InvalidOperationException("Invalid email or password.");
        if (!passwordHasher.VerifyPassword(request.Password, user.PasswordHash))
        {
            throw new InvalidOperationException("Invalid email or password.");
        }

        return await CreateSessionAsync(user, cancellationToken);
    }

    public async Task<AuthSessionResponse> RefreshAsync(RefreshTokenRequest request, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.RefreshToken))
        {
            throw new InvalidOperationException("Refresh token is required.");
        }

        var tokenHash = refreshTokenFactory.HashToken(request.RefreshToken);
        var record = await authRepository.FindRefreshTokenAsync(tokenHash, cancellationToken)
          ?? throw new InvalidOperationException("Refresh token is invalid.");
        if (record.RevokedAtUtc is not null || record.ExpiresAtUtc <= DateTimeOffset.UtcNow)
        {
            throw new InvalidOperationException("Refresh token is expired.");
        }

        var user = await authRepository.FindUserByIdAsync(record.UserId, cancellationToken)
          ?? throw new InvalidOperationException("User account was not found.");
        await authRepository.RevokeRefreshTokenAsync(record.Id, cancellationToken);
        return await CreateSessionAsync(user, cancellationToken);
    }

    public async Task<ForgotPasswordResponse> ForgotPasswordAsync(ForgotPasswordRequest request, CancellationToken cancellationToken)
    {
        var email = NormalizeEmail(request.Email);
        var user = await authRepository.FindUserByEmailAsync(email, cancellationToken);
        if (user is null)
        {
            return new ForgotPasswordResponse(true);
        }

        var resetToken = refreshTokenFactory.CreateToken();
        var resetTokenHash = refreshTokenFactory.HashToken(resetToken);
        var expiresAtUtc = DateTimeOffset.UtcNow.AddMinutes(30);
        await authRepository.StorePasswordResetTokenAsync(user.Id, resetTokenHash, expiresAtUtc, cancellationToken);
        return await passwordResetDelivery.DeliverAsync(user, resetToken, cancellationToken);
    }

    public async Task ResetPasswordAsync(ResetPasswordRequest request, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(request.Token))
        {
            throw new InvalidOperationException("Reset token is required.");
        }

        ValidatePassword(request.NewPassword);
        var tokenHash = refreshTokenFactory.HashToken(request.Token);
        var record = await authRepository.FindPasswordResetTokenAsync(tokenHash, cancellationToken)
          ?? throw new InvalidOperationException("Reset token is invalid.");
        if (record.ConsumedAtUtc is not null || record.ExpiresAtUtc <= DateTimeOffset.UtcNow)
        {
            throw new InvalidOperationException("Reset token is expired.");
        }

        await authRepository.UpdatePasswordHashAsync(record.UserId, passwordHasher.HashPassword(request.NewPassword), cancellationToken);
        await authRepository.ConsumePasswordResetTokenAsync(record.Id, cancellationToken);
        await authRepository.RevokeAllRefreshTokensAsync(record.UserId, cancellationToken);
    }

    private async Task<AuthSessionResponse> CreateSessionAsync(UserAccount user, CancellationToken cancellationToken)
    {
        var (accessToken, accessExpiresAtUtc) = tokenService.CreateAccessToken(user);
        var (refreshToken, refreshTokenHash, refreshExpiresAtUtc) = tokenService.CreateRefreshToken();
        await authRepository.StoreRefreshTokenAsync(user.Id, refreshTokenHash, refreshExpiresAtUtc, cancellationToken);
        return new AuthSessionResponse(user.Id, user.Email, user.Roles, accessToken, accessExpiresAtUtc, refreshToken, refreshExpiresAtUtc);
    }

    private static string NormalizeEmail(string email)
    {
        if (string.IsNullOrWhiteSpace(email))
        {
            throw new InvalidOperationException("Email is required.");
        }

        return email.Trim().ToLowerInvariant();
    }

    private static void ValidatePassword(string password)
    {
        if (string.IsNullOrWhiteSpace(password) || password.Length < 8)
        {
            throw new InvalidOperationException("Password must be at least 8 characters.");
        }
    }
}
