using System.Data;
using Dapper;
using PlotLine.Models;

namespace PlotLine.Data;

public interface IUserRepository
{
    Task<AppUser?> GetByEmailAsync(string email);
    Task<AppUser?> GetByIdAsync(int userId);
    Task<AppUser> CreateAsync(string email, string displayName, string passwordHash, bool emailConfirmed);
    Task<AppUser?> UpdateLoginSuccessAsync(int userId);
    Task<AppUser?> UpdateFailedLoginAsync(int userId, DateTime? lockoutEndUtc);
    Task<bool> ConfirmEmailAsync(int userId, Guid token);
    Task<bool> ResetPasswordAsync(int userId, Guid token, string passwordHash);
    Task<AppUser?> ChangePasswordAsync(int userId, string passwordHash);
    Task<AppUser?> EnableTwoFactorAsync(int userId);
    Task<AppUser?> DisableTwoFactorAsync(int userId);
    Task<IReadOnlyList<string>> ListHardDeleteFilePathsAsync(int userId);
    Task<bool> HardDeleteAccountDataAsync(int userId);
}

public interface IAuthenticationRepository
{
    Task<UserEmailVerificationToken> CreateEmailVerificationTokenAsync(int userId, Guid token, DateTime expiryUtc);
    Task<UserEmailVerificationToken?> GetEmailVerificationTokenAsync(Guid token);
    Task<UserPasswordResetToken> CreatePasswordResetTokenAsync(int userId, Guid token, DateTime expiryUtc);
    Task<UserPasswordResetToken?> GetPasswordResetTokenAsync(Guid token);
    Task<UserTwoFactor?> GetTwoFactorAsync(int userId);
    Task<UserTwoFactor> SaveTwoFactorAsync(int userId, string secretKey, string? recoveryCodes, DateTime? confirmedUtc);
    Task<AppUser?> EnableTwoFactorAsync(int userId);
    Task<AppUser?> DisableTwoFactorAsync(int userId);
    Task<UserTwoFactor?> SaveRecoveryCodesAsync(int userId, string recoveryCodes);
    Task<string?> GetRecoveryCodesAsync(int userId);
    Task<bool> UpdateLastUsedTimeStepAsync(int userId, long lastUsedTimeStep);
    Task<long> InsertLoginAuditAsync(UserLoginAudit audit);
}

public sealed class UserRepository(ISqlConnectionFactory connectionFactory) : IUserRepository
{
    public async Task<AppUser?> GetByEmailAsync(string email)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.User_GetByEmail",
            new { Email = email },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser?> GetByIdAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.User_GetById",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser> CreateAsync(string email, string displayName, string passwordHash, bool emailConfirmed)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<AppUser>(
            "dbo.User_Create",
            new { Email = email, DisplayName = displayName, PasswordHash = passwordHash, EmailConfirmed = emailConfirmed },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser?> UpdateLoginSuccessAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.User_UpdateLoginSuccess",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser?> UpdateFailedLoginAsync(int userId, DateTime? lockoutEndUtc)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.User_UpdateFailedLogin",
            new { UserID = userId, LockoutEndUtc = lockoutEndUtc },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<bool> ConfirmEmailAsync(int userId, Guid token)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<bool>(
            "dbo.User_ConfirmEmail",
            new { UserID = userId, Token = token },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<bool> ResetPasswordAsync(int userId, Guid token, string passwordHash)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<bool>(
            "dbo.User_ResetPassword",
            new { UserID = userId, Token = token, PasswordHash = passwordHash },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser?> ChangePasswordAsync(int userId, string passwordHash)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.User_ChangePassword",
            new { UserID = userId, PasswordHash = passwordHash },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser?> EnableTwoFactorAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.User_EnableTwoFactor",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser?> DisableTwoFactorAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.User_DisableTwoFactor",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<IReadOnlyList<string>> ListHardDeleteFilePathsAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        var rows = await connection.QueryAsync<string>(
            "dbo.UserHardDelete_ListFilePaths",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
        return rows.ToList();
    }

    public async Task<bool> HardDeleteAccountDataAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<bool>(
            "dbo.UserHardDelete_ForAccountDeletion",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }
}

public sealed class AuthenticationRepository(ISqlConnectionFactory connectionFactory) : IAuthenticationRepository
{
    public async Task<UserEmailVerificationToken> CreateEmailVerificationTokenAsync(int userId, Guid token, DateTime expiryUtc)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<UserEmailVerificationToken>(
            "dbo.User_CreateEmailVerificationToken",
            new { UserID = userId, Token = token, ExpiryUtc = expiryUtc },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<UserEmailVerificationToken?> GetEmailVerificationTokenAsync(Guid token)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<UserEmailVerificationToken>(
            "dbo.User_GetEmailVerificationToken",
            new { Token = token },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<UserPasswordResetToken> CreatePasswordResetTokenAsync(int userId, Guid token, DateTime expiryUtc)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<UserPasswordResetToken>(
            "dbo.User_CreatePasswordResetToken",
            new { UserID = userId, Token = token, ExpiryUtc = expiryUtc },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<UserPasswordResetToken?> GetPasswordResetTokenAsync(Guid token)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<UserPasswordResetToken>(
            "dbo.User_GetPasswordResetToken",
            new { Token = token },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<UserTwoFactor?> GetTwoFactorAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<UserTwoFactor>(
            "dbo.UserTwoFactor_Get",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<UserTwoFactor> SaveTwoFactorAsync(int userId, string secretKey, string? recoveryCodes, DateTime? confirmedUtc)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<UserTwoFactor>(
            "dbo.UserTwoFactor_Save",
            new { UserID = userId, SecretKey = secretKey, RecoveryCodes = recoveryCodes, ConfirmedUtc = confirmedUtc },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser?> EnableTwoFactorAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.UserTwoFactor_Enable",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<AppUser?> DisableTwoFactorAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<AppUser>(
            "dbo.UserTwoFactor_Disable",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<UserTwoFactor?> SaveRecoveryCodesAsync(int userId, string recoveryCodes)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<UserTwoFactor>(
            "dbo.UserTwoFactor_SaveRecoveryCodes",
            new { UserID = userId, RecoveryCodes = recoveryCodes },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<string?> GetRecoveryCodesAsync(int userId)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleOrDefaultAsync<string?>(
            "dbo.UserTwoFactor_GetRecoveryCodes",
            new { UserID = userId },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<bool> UpdateLastUsedTimeStepAsync(int userId, long lastUsedTimeStep)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<bool>(
            "dbo.UserTwoFactor_UpdateLastUsedTimeStep",
            new { UserID = userId, LastUsedTimeStep = lastUsedTimeStep },
            commandType: CommandType.StoredProcedure);
    }

    public async Task<long> InsertLoginAuditAsync(UserLoginAudit audit)
    {
        using var connection = connectionFactory.CreateConnection();
        return await connection.QuerySingleAsync<long>(
            "dbo.User_InsertLoginAudit",
            new
            {
                audit.UserID,
                audit.EmailAttempted,
                audit.IpAddress,
                audit.UserAgent,
                audit.WasSuccessful,
                audit.Reason
            },
            commandType: CommandType.StoredProcedure);
    }
}
