SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO

IF COL_LENGTH(N'dbo.AppUser', N'SecurityStamp') IS NULL
BEGIN
    ALTER TABLE dbo.AppUser ADD SecurityStamp uniqueidentifier NOT NULL CONSTRAINT DF_AppUser_SecurityStamp DEFAULT NEWID();
END;
GO

IF EXISTS
(
    SELECT 1
    FROM sys.columns
    WHERE object_id = OBJECT_ID(N'dbo.UserLoginAudit')
        AND name = N'Reason'
        AND max_length < 1000
)
BEGIN
    ALTER TABLE dbo.UserLoginAudit ALTER COLUMN Reason nvarchar(500) NULL;
END;
GO

CREATE OR ALTER PROCEDURE dbo.User_GetByEmail
    @Email nvarchar(256)
AS
BEGIN
    SET NOCOUNT ON;

    SELECT UserID, Email, DisplayName, PasswordHash, EmailConfirmed, IsLocked, FailedLoginAttempts,
        LockoutEndUtc, TwoFactorEnabled, CreatedUtc, UpdatedUtc, LastLoginUtc, SecurityStamp
    FROM dbo.AppUser
    WHERE Email = @Email;
END;
GO

CREATE OR ALTER PROCEDURE dbo.User_GetById
    @UserID int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT UserID, Email, DisplayName, PasswordHash, EmailConfirmed, IsLocked, FailedLoginAttempts,
        LockoutEndUtc, TwoFactorEnabled, CreatedUtc, UpdatedUtc, LastLoginUtc, SecurityStamp
    FROM dbo.AppUser
    WHERE UserID = @UserID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.User_UpdateLoginSuccess
    @UserID int
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.AppUser
    SET FailedLoginAttempts = 0,
        IsLocked = 0,
        LockoutEndUtc = NULL,
        LastLoginUtc = SYSUTCDATETIME(),
        UpdatedUtc = SYSUTCDATETIME()
    WHERE UserID = @UserID;

    EXEC dbo.User_GetById @UserID = @UserID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.User_UpdateFailedLogin
    @UserID int,
    @LockoutEndUtc datetime2 = NULL
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.AppUser
    SET FailedLoginAttempts = FailedLoginAttempts + 1,
        IsLocked = CASE WHEN @LockoutEndUtc IS NULL THEN IsLocked ELSE 1 END,
        LockoutEndUtc = COALESCE(@LockoutEndUtc, LockoutEndUtc),
        UpdatedUtc = SYSUTCDATETIME()
    WHERE UserID = @UserID;

    EXEC dbo.User_GetById @UserID = @UserID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.User_ChangePassword
    @UserID int,
    @PasswordHash nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.AppUser
    SET PasswordHash = @PasswordHash,
        SecurityStamp = NEWID(),
        FailedLoginAttempts = 0,
        IsLocked = 0,
        LockoutEndUtc = NULL,
        UpdatedUtc = SYSUTCDATETIME()
    WHERE UserID = @UserID;

    EXEC dbo.User_GetById @UserID = @UserID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.User_ResetPassword
    @UserID int,
    @Token uniqueidentifier,
    @PasswordHash nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.UserPasswordResetToken
    SET UsedUtc = SYSUTCDATETIME()
    WHERE UserID = @UserID
        AND Token = @Token
        AND UsedUtc IS NULL
        AND ExpiryUtc >= SYSUTCDATETIME();

    IF @@ROWCOUNT = 1
    BEGIN
        UPDATE dbo.AppUser
        SET PasswordHash = @PasswordHash,
            SecurityStamp = NEWID(),
            FailedLoginAttempts = 0,
            IsLocked = 0,
            LockoutEndUtc = NULL,
            UpdatedUtc = SYSUTCDATETIME()
        WHERE UserID = @UserID;

        SELECT CAST(1 AS bit);
        RETURN;
    END;

    SELECT CAST(0 AS bit);
END;
GO

CREATE OR ALTER PROCEDURE dbo.UserTwoFactor_Enable
    @UserID int
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.UserTwoFactor
    SET ConfirmedUtc = COALESCE(ConfirmedUtc, SYSUTCDATETIME())
    WHERE UserID = @UserID;

    UPDATE dbo.AppUser
    SET TwoFactorEnabled = 1,
        SecurityStamp = NEWID(),
        UpdatedUtc = SYSUTCDATETIME()
    WHERE UserID = @UserID;

    EXEC dbo.User_GetById @UserID = @UserID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.UserTwoFactor_Disable
    @UserID int
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.AppUser
    SET TwoFactorEnabled = 0,
        SecurityStamp = NEWID(),
        UpdatedUtc = SYSUTCDATETIME()
    WHERE UserID = @UserID;

    DELETE FROM dbo.UserTwoFactor
    WHERE UserID = @UserID;

    EXEC dbo.User_GetById @UserID = @UserID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.UserTwoFactor_SaveRecoveryCodes
    @UserID int,
    @RecoveryCodes nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.UserTwoFactor
    SET RecoveryCodes = @RecoveryCodes
    WHERE UserID = @UserID;

    UPDATE dbo.AppUser
    SET SecurityStamp = NEWID(),
        UpdatedUtc = SYSUTCDATETIME()
    WHERE UserID = @UserID;

    EXEC dbo.UserTwoFactor_Get @UserID = @UserID;
END;
GO

CREATE OR ALTER PROCEDURE dbo.User_InsertLoginAudit
    @UserID int = NULL,
    @EmailAttempted nvarchar(256) = NULL,
    @IpAddress nvarchar(100) = NULL,
    @UserAgent nvarchar(500) = NULL,
    @WasSuccessful bit,
    @Reason nvarchar(500) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO dbo.UserLoginAudit (UserID, EmailAttempted, IpAddress, UserAgent, WasSuccessful, Reason)
    VALUES (@UserID, @EmailAttempted, @IpAddress, @UserAgent, @WasSuccessful, @Reason);

    SELECT CAST(SCOPE_IDENTITY() AS bigint);
END;
GO
