using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Tiku.Infrastructure.Persistence.Migrations
{
    /// <inheritdoc />
    public partial class AddAuthorizationCaching : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "authorization_cache_invalidations",
                columns: table => new
                {
                    id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
                    target_type = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: false),
                    tenant_id = table.Column<Guid>(type: "uuid", nullable: true),
                    user_id = table.Column<Guid>(type: "uuid", nullable: true),
                    session_id = table.Column<Guid>(type: "uuid", nullable: true),
                    realm = table.Column<string>(type: "text", nullable: true),
                    version = table.Column<long>(type: "bigint", nullable: true),
                    processed_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
                    attempt_count = table.Column<int>(type: "integer", nullable: false),
                    last_error = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
                    created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
                    updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
                },
                constraints: table =>
                {
                    table.PrimaryKey("pk_authorization_cache_invalidations", x => x.id);
                });

            migrationBuilder.CreateTable(
                name: "authorization_scope_versions",
                columns: table => new
                {
                    id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
                    realm = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
                    tenant_id = table.Column<Guid>(type: "uuid", nullable: true),
                    version = table.Column<long>(type: "bigint", nullable: false, defaultValue: 1L),
                    created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
                    updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
                },
                constraints: table =>
                {
                    table.PrimaryKey("pk_authorization_scope_versions", x => x.id);
                    table.CheckConstraint("ck_authorization_scope_versions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)");
                    table.ForeignKey(
                        name: "fk_authorization_scope_versions_tenants_tenant_id",
                        column: x => x.tenant_id,
                        principalTable: "tenants",
                        principalColumn: "id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateIndex(
                name: "ix_authorization_cache_invalidations_processed_at_created_at",
                table: "authorization_cache_invalidations",
                columns: new[] { "processed_at", "created_at" });

            migrationBuilder.CreateIndex(
                name: "ix_authorization_cache_invalidations_target_type_tenant_id_use~",
                table: "authorization_cache_invalidations",
                columns: new[] { "target_type", "tenant_id", "user_id", "session_id", "version" });

            migrationBuilder.CreateIndex(
                name: "ix_authorization_scope_versions_realm",
                table: "authorization_scope_versions",
                column: "realm",
                unique: true,
                filter: "realm = 'platform'");

            migrationBuilder.CreateIndex(
                name: "ix_authorization_scope_versions_tenant_id",
                table: "authorization_scope_versions",
                column: "tenant_id",
                unique: true,
                filter: "tenant_id is not null");

            migrationBuilder.Sql("""
                INSERT INTO authorization_scope_versions
                    (id, realm, tenant_id, version, created_at, updated_at)
                VALUES
                    (gen_random_uuid(), 'platform', NULL, 1, now(), now());

                INSERT INTO authorization_scope_versions
                    (id, realm, tenant_id, version, created_at, updated_at)
                SELECT gen_random_uuid(), 'tenant', id, 1, now(), now()
                FROM tenants;
                """);

            migrationBuilder.Sql("""
                CREATE OR REPLACE FUNCTION tiku_bump_tenant_authorization_version()
                RETURNS trigger
                LANGUAGE plpgsql
                AS $$
                DECLARE
                    scope_tenant_id uuid;
                    next_version bigint;
                BEGIN
                    IF TG_OP = 'DELETE' THEN
                        scope_tenant_id := OLD.tenant_id;
                    ELSE
                        scope_tenant_id := NEW.tenant_id;
                    END IF;

                    UPDATE authorization_scope_versions
                    SET version = version + 1, updated_at = now()
                    WHERE realm = 'tenant' AND tenant_id = scope_tenant_id
                    RETURNING version INTO next_version;

                    IF next_version IS NOT NULL THEN
                        INSERT INTO authorization_cache_invalidations
                            (id, target_type, tenant_id, realm, version, attempt_count, created_at, updated_at)
                        VALUES
                            (gen_random_uuid(), 'scope', scope_tenant_id, 'tenant', next_version, 0, now(), now());
                    END IF;
                    IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
                    RETURN NEW;
                END;
                $$;

                CREATE OR REPLACE FUNCTION tiku_bump_platform_authorization_version()
                RETURNS trigger
                LANGUAGE plpgsql
                AS $$
                DECLARE next_version bigint;
                BEGIN
                    UPDATE authorization_scope_versions
                    SET version = version + 1, updated_at = now()
                    WHERE realm = 'platform' AND tenant_id IS NULL
                    RETURNING version INTO next_version;
                    INSERT INTO authorization_cache_invalidations
                        (id, target_type, realm, version, attempt_count, created_at, updated_at)
                    VALUES
                        (gen_random_uuid(), 'scope', 'platform', next_version, 0, now(), now());
                    IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
                    RETURN NEW;
                END;
                $$;

                CREATE OR REPLACE FUNCTION tiku_bump_all_authorization_versions()
                RETURNS trigger
                LANGUAGE plpgsql
                AS $$
                BEGIN
                    UPDATE authorization_scope_versions SET version = version + 1, updated_at = now();
                    INSERT INTO authorization_cache_invalidations
                        (id, target_type, tenant_id, realm, version, attempt_count, created_at, updated_at)
                    SELECT gen_random_uuid(), 'scope', tenant_id, realm, version, 0, now(), now()
                    FROM authorization_scope_versions;
                    IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
                    RETURN NEW;
                END;
                $$;

                CREATE TRIGGER trg_tenant_backend_roles_authorization_version
                AFTER INSERT OR UPDATE OR DELETE ON tenant_backend_roles
                FOR EACH ROW EXECUTE FUNCTION tiku_bump_tenant_authorization_version();
                CREATE TRIGGER trg_tenant_backend_role_permissions_authorization_version
                AFTER INSERT OR UPDATE OR DELETE ON tenant_backend_role_permissions
                FOR EACH ROW EXECUTE FUNCTION tiku_bump_tenant_authorization_version();
                CREATE TRIGGER trg_tenant_backend_user_roles_authorization_version
                AFTER INSERT OR UPDATE OR DELETE ON tenant_backend_user_roles
                FOR EACH ROW EXECUTE FUNCTION tiku_bump_tenant_authorization_version();

                CREATE TRIGGER trg_platform_backend_roles_authorization_version
                AFTER INSERT OR UPDATE OR DELETE ON platform_backend_roles
                FOR EACH ROW EXECUTE FUNCTION tiku_bump_platform_authorization_version();
                CREATE TRIGGER trg_platform_backend_role_permissions_authorization_version
                AFTER INSERT OR UPDATE OR DELETE ON platform_backend_role_permissions
                FOR EACH ROW EXECUTE FUNCTION tiku_bump_platform_authorization_version();
                CREATE TRIGGER trg_platform_backend_user_roles_authorization_version
                AFTER INSERT OR UPDATE OR DELETE ON platform_backend_user_roles
                FOR EACH ROW EXECUTE FUNCTION tiku_bump_platform_authorization_version();

                CREATE TRIGGER trg_backend_permissions_authorization_version
                AFTER INSERT OR UPDATE OR DELETE ON backend_permissions
                FOR EACH STATEMENT EXECUTE FUNCTION tiku_bump_all_authorization_versions();
                """);
        }

        /// <inheritdoc />
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.Sql("""
                DROP FUNCTION IF EXISTS tiku_bump_tenant_authorization_version() CASCADE;
                DROP FUNCTION IF EXISTS tiku_bump_platform_authorization_version() CASCADE;
                DROP FUNCTION IF EXISTS tiku_bump_all_authorization_versions() CASCADE;
                """);
            migrationBuilder.DropTable(
                name: "authorization_cache_invalidations");

            migrationBuilder.DropTable(
                name: "authorization_scope_versions");
        }
    }
}
