using System;
using Microsoft.EntityFrameworkCore;

namespace RolemasterDb.App.Data;

public static class RolemasterDbSchemaUpgrader
{
    public static async Task EnsureLatestAsync(RolemasterDbContext dbContext, CancellationToken cancellationToken = default)
    {
        await EnsureAttackTableFumbleColumnsAsync(dbContext, cancellationToken);
        await EnsureCriticalResultCurationColumnsAsync(dbContext, cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "CriticalBranches" (
                "Id" INTEGER NOT NULL CONSTRAINT "PK_CriticalBranches" PRIMARY KEY AUTOINCREMENT,
                "CriticalResultId" INTEGER NOT NULL,
                "BranchKind" TEXT NOT NULL,
                "ConditionKey" TEXT NULL,
                "ConditionText" TEXT NOT NULL,
                "ConditionJson" TEXT NOT NULL,
                "RawText" TEXT NOT NULL,
                "DescriptionText" TEXT NOT NULL,
                "RawAffixText" TEXT NULL,
                "ParsedJson" TEXT NOT NULL,
                "SortOrder" INTEGER NOT NULL,
                CONSTRAINT "FK_CriticalBranches_CriticalResults_CriticalResultId"
                    FOREIGN KEY ("CriticalResultId") REFERENCES "CriticalResults" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_CriticalBranches_CriticalResultId"
                ON "CriticalBranches" ("CriticalResultId");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_CriticalBranches_CriticalResultId_SortOrder"
                ON "CriticalBranches" ("CriticalResultId", "SortOrder");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE TABLE IF NOT EXISTS "CriticalEffects" (
                "Id" INTEGER NOT NULL CONSTRAINT "PK_CriticalEffects" PRIMARY KEY AUTOINCREMENT,
                "CriticalResultId" INTEGER NULL,
                "CriticalBranchId" INTEGER NULL,
                "EffectCode" TEXT NOT NULL,
                "Target" TEXT NULL,
                "ValueInteger" INTEGER NULL,
                "ValueDecimal" TEXT NULL,
                "ValueExpression" TEXT NULL,
                "DurationRounds" INTEGER NULL,
                "PerRound" INTEGER NULL,
                "Modifier" INTEGER NULL,
                "BodyPart" TEXT NULL,
                "IsPermanent" INTEGER NOT NULL,
                "SourceType" TEXT NOT NULL,
                "SourceText" TEXT NULL,
                CONSTRAINT "FK_CriticalEffects_CriticalResults_CriticalResultId"
                    FOREIGN KEY ("CriticalResultId") REFERENCES "CriticalResults" ("Id") ON DELETE CASCADE,
                CONSTRAINT "FK_CriticalEffects_CriticalBranches_CriticalBranchId"
                    FOREIGN KEY ("CriticalBranchId") REFERENCES "CriticalBranches" ("Id") ON DELETE CASCADE
            );
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_CriticalEffects_EffectCode"
                ON "CriticalEffects" ("EffectCode");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_CriticalEffects_CriticalResultId"
                ON "CriticalEffects" ("CriticalResultId");
            """,
            cancellationToken);

        await dbContext.Database.ExecuteSqlRawAsync(
            """
            CREATE INDEX IF NOT EXISTS "IX_CriticalEffects_CriticalBranchId"
                ON "CriticalEffects" ("CriticalBranchId");
            """,
            cancellationToken);
    }

    private static async Task EnsureCriticalResultCurationColumnsAsync(RolemasterDbContext dbContext, CancellationToken cancellationToken)
    {
        if (!await TableExistsAsync(dbContext, "CriticalResults", cancellationToken))
        {
            return;
        }

        if (!await ColumnExistsAsync(dbContext, "CriticalResults", "IsCurated", cancellationToken))
        {
            await dbContext.Database.ExecuteSqlRawAsync(
                """
                ALTER TABLE "CriticalResults"
                ADD COLUMN "IsCurated" INTEGER NOT NULL DEFAULT 0;
                """,
                cancellationToken);
        }

        if (!await ColumnExistsAsync(dbContext, "CriticalResults", "SourcePageNumber", cancellationToken))
        {
            await dbContext.Database.ExecuteSqlRawAsync(
                """
                ALTER TABLE "CriticalResults"
                ADD COLUMN "SourcePageNumber" INTEGER NULL;
                """,
                cancellationToken);
        }

        if (!await ColumnExistsAsync(dbContext, "CriticalResults", "SourceImagePath", cancellationToken))
        {
            await dbContext.Database.ExecuteSqlRawAsync(
                """
                ALTER TABLE "CriticalResults"
                ADD COLUMN "SourceImagePath" TEXT NULL;
                """,
                cancellationToken);
        }

        if (!await ColumnExistsAsync(dbContext, "CriticalResults", "SourceImageCropJson", cancellationToken))
        {
            await dbContext.Database.ExecuteSqlRawAsync(
                """
                ALTER TABLE "CriticalResults"
                ADD COLUMN "SourceImageCropJson" TEXT NULL;
                """,
                cancellationToken);
        }
    }

    private static async Task EnsureAttackTableFumbleColumnsAsync(RolemasterDbContext dbContext, CancellationToken cancellationToken)
    {
        if (!await ColumnExistsAsync(dbContext, "AttackTables", "FumbleMinRoll", cancellationToken))
        {
            await dbContext.Database.ExecuteSqlRawAsync(
                """
                ALTER TABLE "AttackTables"
                ADD COLUMN "FumbleMinRoll" INTEGER NULL;
                """,
                cancellationToken);
        }

        if (!await ColumnExistsAsync(dbContext, "AttackTables", "FumbleMaxRoll", cancellationToken))
        {
            await dbContext.Database.ExecuteSqlRawAsync(
                """
                ALTER TABLE "AttackTables"
                ADD COLUMN "FumbleMaxRoll" INTEGER NULL;
                """,
                cancellationToken);
        }
    }

    private static async Task<bool> ColumnExistsAsync(
        RolemasterDbContext dbContext,
        string tableName,
        string columnName,
        CancellationToken cancellationToken)
    {
        var connection = dbContext.Database.GetDbConnection();
        var shouldClose = connection.State != System.Data.ConnectionState.Open;
        if (shouldClose)
        {
            await connection.OpenAsync(cancellationToken);
        }

        try
        {
            await using var command = connection.CreateCommand();
            command.CommandText = $"PRAGMA table_info(\"{tableName}\");";

            await using var reader = await command.ExecuteReaderAsync(cancellationToken);
            while (await reader.ReadAsync(cancellationToken))
            {
                if (string.Equals(reader["name"]?.ToString(), columnName, StringComparison.Ordinal))
                {
                    return true;
                }
            }

            return false;
        }
        finally
        {
            if (shouldClose)
            {
                await connection.CloseAsync();
            }
        }
    }

    private static async Task<bool> TableExistsAsync(
        RolemasterDbContext dbContext,
        string tableName,
        CancellationToken cancellationToken)
    {
        var connection = dbContext.Database.GetDbConnection();
        var shouldClose = connection.State != System.Data.ConnectionState.Open;
        if (shouldClose)
        {
            await connection.OpenAsync(cancellationToken);
        }

        try
        {
            await using var command = connection.CreateCommand();
            command.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = $tableName;";

            var parameter = command.CreateParameter();
            parameter.ParameterName = "$tableName";
            parameter.Value = tableName;
            command.Parameters.Add(parameter);

            var result = await command.ExecuteScalarAsync(cancellationToken);
            return Convert.ToInt32(result) > 0;
        }
        finally
        {
            if (shouldClose)
            {
                await connection.CloseAsync();
            }
        }
    }
}
