USE [RobotInstaller];
GO

SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
GO

IF OBJECT_ID(N'dbo.ApplicationPackages', N'U') IS NOT NULL
    OR OBJECT_ID(N'dbo.PackageVersions', N'U') IS NOT NULL
    OR OBJECT_ID(N'dbo.Applications', N'U') IS NOT NULL
    OR OBJECT_ID(N'dbo.Packages', N'U') IS NOT NULL
    OR OBJECT_ID(N'dbo.Documents', N'U') IS NOT NULL
    OR OBJECT_ID(N'dbo.EmailConfirmationTokens', N'U') IS NOT NULL
    OR OBJECT_ID(N'dbo.Users', N'U') IS NOT NULL
BEGIN
    THROW 50001, 'Schema tables already exist. Review, drop, or migrate existing tables before running 02_schema.sql.', 1;
END;
GO

CREATE TABLE dbo.Users
(
    Id UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT PK_Users PRIMARY KEY CLUSTERED
        CONSTRAINT DF_Users_Id DEFAULT NEWSEQUENTIALID(),
    Username NVARCHAR(100) NOT NULL,
    Email NVARCHAR(255) NOT NULL,
    PasswordHash NVARCHAR(500) NOT NULL,
    FullName NVARCHAR(200) NULL,
    Role NVARCHAR(50) NOT NULL
        CONSTRAINT DF_Users_Role DEFAULT N'User',
    IsActive BIT NOT NULL
        CONSTRAINT DF_Users_IsActive DEFAULT 1,
    CreatedAt DATETIME2(3) NOT NULL
        CONSTRAINT DF_Users_CreatedAt DEFAULT SYSUTCDATETIME(),
    UpdatedAt DATETIME2(3) NULL,
    CONSTRAINT CK_Users_Role CHECK (Role IN (N'Admin', N'User')),
    CONSTRAINT CK_Users_Username_NotBlank CHECK (LEN(LTRIM(RTRIM(Username))) > 0),
    CONSTRAINT CK_Users_Email_NotBlank CHECK (LEN(LTRIM(RTRIM(Email))) > 0)
);
GO

CREATE TABLE dbo.EmailConfirmationTokens
(
    Id UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT PK_EmailConfirmationTokens PRIMARY KEY CLUSTERED
        CONSTRAINT DF_EmailConfirmationTokens_Id DEFAULT NEWSEQUENTIALID(),
    UserId UNIQUEIDENTIFIER NOT NULL,
    TokenHash CHAR(64) NOT NULL,
    ExpiresAt DATETIME2(3) NOT NULL,
    ConfirmedAt DATETIME2(3) NULL,
    CreatedAt DATETIME2(3) NOT NULL
        CONSTRAINT DF_EmailConfirmationTokens_CreatedAt DEFAULT SYSUTCDATETIME(),
    CONSTRAINT FK_EmailConfirmationTokens_User
        FOREIGN KEY (UserId) REFERENCES dbo.Users(Id) ON DELETE CASCADE
);
GO

CREATE TABLE dbo.Documents
(
    Id UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT PK_Documents PRIMARY KEY CLUSTERED
        CONSTRAINT DF_Documents_Id DEFAULT NEWSEQUENTIALID(),
    Title NVARCHAR(200) NOT NULL,
    Category NVARCHAR(50) NOT NULL
        CONSTRAINT DF_Documents_Category DEFAULT N'other',
    Summary NVARCHAR(1000) NULL,
    Content NVARCHAR(MAX) NULL,
    FilePath NVARCHAR(1000) NULL,
    OriginalFileName NVARCHAR(260) NULL,
    MimeType NVARCHAR(200) NULL,
    FileSizeBytes BIGINT NULL,
    CreatedByUserId UNIQUEIDENTIFIER NOT NULL,
    CreatedAt DATETIME2(3) NOT NULL
        CONSTRAINT DF_Documents_CreatedAt DEFAULT SYSUTCDATETIME(),
    UpdatedAt DATETIME2(3) NULL,
    CONSTRAINT FK_Documents_CreatedByUser
        FOREIGN KEY (CreatedByUserId) REFERENCES dbo.Users(Id),
    CONSTRAINT CK_Documents_Title_NotBlank CHECK (LEN(LTRIM(RTRIM(Title))) > 0),
    CONSTRAINT CK_Documents_Category CHECK (
        Category IN (N'introduction', N'guide', N'user-guide', N'technical', N'policy', N'other')
    ),
    CONSTRAINT CK_Documents_FileSizeBytes CHECK (FileSizeBytes IS NULL OR FileSizeBytes >= 0),
    CONSTRAINT CK_Documents_HasReadableContent CHECK (
        NULLIF(LTRIM(RTRIM(Content)), N'') IS NOT NULL OR FilePath IS NOT NULL
    )
);
GO

CREATE TABLE dbo.Packages
(
    Id UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT PK_Packages PRIMARY KEY CLUSTERED
        CONSTRAINT DF_Packages_Id DEFAULT NEWSEQUENTIALID(),
    PackageCode NVARCHAR(100) NOT NULL,
    PackageName NVARCHAR(200) NOT NULL,
    PackageType NVARCHAR(20) NOT NULL,
    Description NVARCHAR(MAX) NULL,
    CreatedByUserId UNIQUEIDENTIFIER NOT NULL,
    CreatedAt DATETIME2(3) NOT NULL
        CONSTRAINT DF_Packages_CreatedAt DEFAULT SYSUTCDATETIME(),
    UpdatedAt DATETIME2(3) NULL,
    IsActive BIT NOT NULL
        CONSTRAINT DF_Packages_IsActive DEFAULT 1,
    CONSTRAINT FK_Packages_CreatedByUser
        FOREIGN KEY (CreatedByUserId) REFERENCES dbo.Users(Id),
    CONSTRAINT CK_Packages_PackageType CHECK (PackageType IN (N'deb', N'apt', N'docker')),
    CONSTRAINT CK_Packages_PackageCode_NotBlank CHECK (LEN(LTRIM(RTRIM(PackageCode))) > 0),
    CONSTRAINT CK_Packages_PackageName_NotBlank CHECK (LEN(LTRIM(RTRIM(PackageName))) > 0)
);
GO

CREATE TABLE dbo.PackageVersions
(
    Id UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT PK_PackageVersions PRIMARY KEY CLUSTERED
        CONSTRAINT DF_PackageVersions_Id DEFAULT NEWSEQUENTIALID(),
    PackageId UNIQUEIDENTIFIER NOT NULL,
    Version NVARCHAR(50) NOT NULL,
    FilePath NVARCHAR(1000) NULL,
    DockerImage NVARCHAR(500) NULL,
    FileChecksumSha256 CHAR(64) NULL,
    FileSizeBytes BIGINT NULL,
    ChangeLog NVARCHAR(MAX) NULL,
    ReleaseDate DATETIME2(3) NOT NULL
        CONSTRAINT DF_PackageVersions_ReleaseDate DEFAULT SYSUTCDATETIME(),
    UploadedAt DATETIME2(3) NOT NULL
        CONSTRAINT DF_PackageVersions_UploadedAt DEFAULT SYSUTCDATETIME(),
    IsLatest BIT NOT NULL
        CONSTRAINT DF_PackageVersions_IsLatest DEFAULT 0,
    IsDeprecated BIT NOT NULL
        CONSTRAINT DF_PackageVersions_IsDeprecated DEFAULT 0,
    CONSTRAINT FK_PackageVersions_Package
        FOREIGN KEY (PackageId) REFERENCES dbo.Packages(Id) ON DELETE CASCADE,
    CONSTRAINT CK_PackageVersions_Version_NotBlank CHECK (LEN(LTRIM(RTRIM(Version))) > 0),
    CONSTRAINT CK_PackageVersions_FileSizeBytes CHECK (FileSizeBytes IS NULL OR FileSizeBytes >= 0),
    CONSTRAINT CK_PackageVersions_FileChecksumSha256 CHECK (
        FileChecksumSha256 IS NULL
        OR FileChecksumSha256 NOT LIKE '%[^0-9A-Fa-f]%'
    )
);
GO

CREATE TABLE dbo.Applications
(
    Id UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT PK_Applications PRIMARY KEY CLUSTERED
        CONSTRAINT DF_Applications_Id DEFAULT NEWSEQUENTIALID(),
    AppCode NVARCHAR(100) NOT NULL,
    AppName NVARCHAR(200) NOT NULL,
    AppVersion NVARCHAR(50) NOT NULL
        CONSTRAINT DF_Applications_AppVersion DEFAULT N'1.0.0',
    Description NVARCHAR(MAX) NULL,
    CreatedByUserId UNIQUEIDENTIFIER NOT NULL,
    CreatedAt DATETIME2(3) NOT NULL
        CONSTRAINT DF_Applications_CreatedAt DEFAULT SYSUTCDATETIME(),
    UpdatedAt DATETIME2(3) NULL,
    Status NVARCHAR(50) NOT NULL
        CONSTRAINT DF_Applications_Status DEFAULT N'Draft',
    Notes NVARCHAR(500) NULL,
    OpenUrl NVARCHAR(500) NULL,
    CONSTRAINT FK_Applications_CreatedByUser
        FOREIGN KEY (CreatedByUserId) REFERENCES dbo.Users(Id),
    CONSTRAINT CK_Applications_Status CHECK (Status IN (N'Draft', N'Released', N'Archived')),
    CONSTRAINT CK_Applications_AppCode_NotBlank CHECK (LEN(LTRIM(RTRIM(AppCode))) > 0),
    CONSTRAINT CK_Applications_AppName_NotBlank CHECK (LEN(LTRIM(RTRIM(AppName))) > 0),
    CONSTRAINT CK_Applications_AppVersion_NotBlank CHECK (LEN(LTRIM(RTRIM(AppVersion))) > 0)
);
GO

CREATE TABLE dbo.ApplicationPackages
(
    Id UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT PK_ApplicationPackages PRIMARY KEY CLUSTERED
        CONSTRAINT DF_ApplicationPackages_Id DEFAULT NEWSEQUENTIALID(),
    ApplicationId UNIQUEIDENTIFIER NOT NULL,
    PackageId UNIQUEIDENTIFIER NOT NULL,
    SelectedVersionId UNIQUEIDENTIFIER NULL,
    AddedAt DATETIME2(3) NOT NULL
        CONSTRAINT DF_ApplicationPackages_AddedAt DEFAULT SYSUTCDATETIME(),
    Notes NVARCHAR(500) NULL,
    CONSTRAINT FK_ApplicationPackages_Application
        FOREIGN KEY (ApplicationId) REFERENCES dbo.Applications(Id) ON DELETE CASCADE,
    CONSTRAINT FK_ApplicationPackages_Package
        FOREIGN KEY (PackageId) REFERENCES dbo.Packages(Id) ON DELETE CASCADE
);
GO

CREATE UNIQUE INDEX UX_Users_Username ON dbo.Users(Username);
CREATE UNIQUE INDEX UX_Users_Email ON dbo.Users(Email);
GO

CREATE UNIQUE INDEX UX_EmailConfirmationTokens_TokenHash
ON dbo.EmailConfirmationTokens(TokenHash);

CREATE INDEX IX_EmailConfirmationTokens_UserId
ON dbo.EmailConfirmationTokens(UserId);
GO

CREATE INDEX IX_Documents_Category_UpdatedAt
ON dbo.Documents(Category, UpdatedAt DESC, CreatedAt DESC);

CREATE INDEX IX_Documents_CreatedByUserId
ON dbo.Documents(CreatedByUserId);
GO

CREATE UNIQUE INDEX UX_Packages_PackageCode ON dbo.Packages(PackageCode);
CREATE INDEX IX_Packages_CreatedByUserId ON dbo.Packages(CreatedByUserId);
CREATE INDEX IX_Packages_PackageType ON dbo.Packages(PackageType);
GO

CREATE UNIQUE INDEX UX_PackageVersions_PackageId_Version
ON dbo.PackageVersions(PackageId, Version);

CREATE UNIQUE INDEX UX_PackageVersions_Id_PackageId
ON dbo.PackageVersions(Id, PackageId);

CREATE UNIQUE INDEX UX_PackageVersions_OneLatestPerPackage
ON dbo.PackageVersions(PackageId)
WHERE IsLatest = 1;

CREATE INDEX IX_PackageVersions_PackageId_ReleaseDate
ON dbo.PackageVersions(PackageId, ReleaseDate DESC);
GO

CREATE UNIQUE INDEX UX_Applications_AppCode
ON dbo.Applications(AppCode);

CREATE INDEX IX_Applications_CreatedByUserId ON dbo.Applications(CreatedByUserId);
CREATE INDEX IX_Applications_Status ON dbo.Applications(Status);
GO

CREATE UNIQUE INDEX UX_ApplicationPackages_ApplicationId_PackageId
ON dbo.ApplicationPackages(ApplicationId, PackageId);

CREATE INDEX IX_ApplicationPackages_PackageId
ON dbo.ApplicationPackages(PackageId);

CREATE INDEX IX_ApplicationPackages_SelectedVersionId
ON dbo.ApplicationPackages(SelectedVersionId);
GO

ALTER TABLE dbo.ApplicationPackages
ADD CONSTRAINT FK_ApplicationPackages_SelectedVersionBelongsToPackage
    FOREIGN KEY (SelectedVersionId, PackageId)
    REFERENCES dbo.PackageVersions(Id, PackageId);
GO

CREATE OR ALTER PROCEDURE dbo.SetLatestPackageVersion
    @PackageVersionId UNIQUEIDENTIFIER
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    DECLARE @PackageId UNIQUEIDENTIFIER;

    SELECT @PackageId = PackageId
    FROM dbo.PackageVersions
    WHERE Id = @PackageVersionId;

    IF @PackageId IS NULL
    BEGIN
        THROW 50002, 'Package version does not exist.', 1;
    END;

    BEGIN TRANSACTION;

    UPDATE dbo.PackageVersions
    SET IsLatest = 0
    WHERE PackageId = @PackageId;

    UPDATE dbo.PackageVersions
    SET IsLatest = 1,
        IsDeprecated = 0
    WHERE Id = @PackageVersionId;

    COMMIT TRANSACTION;
END;
GO

CREATE OR ALTER PROCEDURE dbo.DeletePackageVersion
    @PackageVersionId UNIQUEIDENTIFIER
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    DECLARE @PackageId UNIQUEIDENTIFIER;
    DECLARE @WasLatest BIT;

    SELECT
        @PackageId = PackageId,
        @WasLatest = IsLatest
    FROM dbo.PackageVersions
    WHERE Id = @PackageVersionId;

    IF @PackageId IS NULL
    BEGIN
        THROW 50003, 'Package version does not exist.', 1;
    END;

    BEGIN TRANSACTION;

    DELETE FROM dbo.ApplicationPackages
    WHERE SelectedVersionId = @PackageVersionId;

    DELETE FROM dbo.PackageVersions
    WHERE Id = @PackageVersionId;

    IF @WasLatest = 1
    BEGIN
        DECLARE @NextLatestId UNIQUEIDENTIFIER;

        SELECT TOP (1) @NextLatestId = Id
        FROM dbo.PackageVersions
        WHERE PackageId = @PackageId
          AND IsDeprecated = 0
        ORDER BY ReleaseDate DESC, UploadedAt DESC;

        IF @NextLatestId IS NOT NULL
        BEGIN
            EXEC dbo.SetLatestPackageVersion @NextLatestId;
        END;
    END;

    COMMIT TRANSACTION;
END;
GO

PRINT N'RobotInstaller schema was created successfully.';
GO
