"""Initial schema

Revision ID: 799b73fe9f6c
Revises:
Create Date: 2025-12-09 16:36:57.351967

"""

from __future__ import annotations

from typing import Sequence

import sqlalchemy as sa
from alembic import op

from pycharter.db.migrations.schema_helper import get_schema, ref_table

# revision identifiers, used by Alembic.
revision: str = "799b73fe9f6c"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
    bind = op.get_bind()
    schema = get_schema(bind)
    is_sqlite = schema is None
    # SQLite has no now(); use CURRENT_TIMESTAMP. PostgreSQL uses now().
    datetime_default = sa.text("CURRENT_TIMESTAMP") if is_sqlite else sa.text("now()")
    ref = lambda t: ref_table(t, bind)

    # Step 1: Create independent tables (no foreign key dependencies)
    op.create_table(
        "owners",
        sa.Column("id", sa.String(length=255), nullable=False),
        sa.Column("name", sa.String(length=255), nullable=True),
        sa.Column("email", sa.String(length=255), nullable=True),
        sa.Column("team", sa.String(length=255), nullable=True),
        sa.Column("additional_info", sa.JSON(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("email", name="uq_owners_email"),
        schema=schema,
    )
    op.create_table(
        "domains",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("name", sa.String(length=255), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("name"),
        sa.UniqueConstraint("name", name="uq_domains_name"),
        schema=schema,
    )
    op.create_table(
        "systems",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("name", sa.String(length=255), nullable=False),
        sa.Column("app_id", sa.String(length=255), nullable=True),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("name"),
        sa.UniqueConstraint("name", name="uq_systems_name"),
        schema=schema,
    )

    # Step 2: Create data_contracts table WITHOUT foreign keys to circular dependencies
    # (We'll add those foreign keys later after the referenced tables exist)
    op.create_table(
        "data_contracts",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("name", sa.String(length=255), nullable=False),
        sa.Column("version", sa.String(length=50), nullable=False),
        sa.Column("status", sa.String(length=50), nullable=True),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("schema_id", sa.UUID(), nullable=True),
        sa.Column("coercion_rules_id", sa.UUID(), nullable=True),
        sa.Column("validation_rules_id", sa.UUID(), nullable=True),
        sa.Column("metadata_record_id", sa.UUID(), nullable=True),
        sa.Column("schema_version", sa.String(length=50), nullable=True),
        sa.Column("coercion_rules_version", sa.String(length=50), nullable=True),
        sa.Column("validation_rules_version", sa.String(length=50), nullable=True),
        sa.Column("metadata_version", sa.String(length=50), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column("created_by", sa.String(length=255), nullable=True),
        sa.Column("updated_by", sa.String(length=255), nullable=True),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("name", "version", name="uq_data_contracts_name_version"),
        schema=schema,
    )

    # Step 3: Create schemas (references data_contracts)
    op.create_table(
        "schemas",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("title", sa.String(length=255), nullable=False),
        sa.Column("data_contract_id", sa.UUID(), nullable=False),
        sa.Column("version", sa.String(length=50), nullable=False),
        sa.Column("schema_data", sa.JSON(), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["data_contract_id"], [ref("data_contracts") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "data_contract_id", "version", name="uq_schemas_contract_version"
        ),
        schema=schema,
    )

    # Step 4: Create coercion_rules and validation_rules (references data_contracts and schemas)
    op.create_table(
        "coercion_rules",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("title", sa.String(length=255), nullable=False),
        sa.Column("data_contract_id", sa.UUID(), nullable=False),
        sa.Column("description", sa.String(length=500), nullable=True),
        sa.Column("version", sa.String(length=50), nullable=False),
        sa.Column("rules", sa.JSON(), nullable=False),
        sa.Column("schema_id", sa.UUID(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["data_contract_id"], [ref("data_contracts") + ".id"], ondelete="CASCADE"
        ),
        sa.ForeignKeyConstraint(
            ["schema_id"], [ref("schemas") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "data_contract_id", "version", name="uq_coercion_rules_contract_version"
        ),
        schema=schema,
    )
    op.create_table(
        "validation_rules",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("title", sa.String(length=255), nullable=False),
        sa.Column("data_contract_id", sa.UUID(), nullable=False),
        sa.Column("description", sa.String(length=500), nullable=True),
        sa.Column("version", sa.String(length=50), nullable=False),
        sa.Column("rules", sa.JSON(), nullable=False),
        sa.Column("schema_id", sa.UUID(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["data_contract_id"], [ref("data_contracts") + ".id"], ondelete="CASCADE"
        ),
        sa.ForeignKeyConstraint(
            ["schema_id"], [ref("schemas") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "data_contract_id", "version", name="uq_validation_rules_contract_version"
        ),
        schema=schema,
    )

    # Step 5: Create metadata_records (references data_contracts)
    op.create_table(
        "metadata_records",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("title", sa.String(length=255), nullable=False),
        sa.Column("data_contract_id", sa.UUID(), nullable=False),
        sa.Column("version", sa.String(length=50), nullable=False),
        sa.Column("status", sa.String(length=50), nullable=True),
        sa.Column("type", sa.String(length=50), nullable=True),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.Column("created_by", sa.String(length=255), nullable=True),
        sa.Column("updated_by", sa.String(length=255), nullable=True),
        sa.Column("governance_rules", sa.JSON(), nullable=True),
        sa.ForeignKeyConstraint(
            ["data_contract_id"], [ref("data_contracts") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "data_contract_id", "version", name="uq_metadata_records_contract_version"
        ),
        schema=schema,
    )

    # Step 6: Create metadata record association tables
    op.create_table(
        "metadata_record_bu_sme",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("owner_id", sa.String(length=255), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"], [ref("owners") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("metadata_record_id", "owner_id", name="uq_mr_bu_sme"),
        schema=schema,
    )
    op.create_table(
        "metadata_record_business_owners",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("owner_id", sa.String(length=255), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"], [ref("owners") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "metadata_record_id", "owner_id", name="uq_mr_business_owner"
        ),
        schema=schema,
    )
    op.create_table(
        "metadata_record_domains",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("domain_id", sa.UUID(), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["domain_id"], [ref("domains") + ".id"], ondelete="CASCADE"
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("metadata_record_id", "domain_id", name="uq_mr_domain"),
        schema=schema,
    )
    op.create_table(
        "metadata_record_it_application_owners",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("owner_id", sa.String(length=255), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"], [ref("owners") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "metadata_record_id", "owner_id", name="uq_mr_it_application_owner"
        ),
        schema=schema,
    )
    op.create_table(
        "metadata_record_it_sme",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("owner_id", sa.String(length=255), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"], [ref("owners") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("metadata_record_id", "owner_id", name="uq_mr_it_sme"),
        schema=schema,
    )
    op.create_table(
        "metadata_record_support_lead",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("owner_id", sa.String(length=255), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["owner_id"], [ref("owners") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "metadata_record_id", "owner_id", name="uq_mr_support_lead"
        ),
        schema=schema,
    )
    op.create_table(
        "metadata_record_system_pulls",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("system_id", sa.UUID(), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["system_id"], [ref("systems") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "metadata_record_id", "system_id", name="uq_mr_system_pull"
        ),
        schema=schema,
    )
    op.create_table(
        "metadata_record_system_pushes",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("system_id", sa.UUID(), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["system_id"], [ref("systems") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "metadata_record_id", "system_id", name="uq_mr_system_push"
        ),
        schema=schema,
    )
    op.create_table(
        "metadata_record_system_sources",
        sa.Column("id", sa.UUID(), nullable=False),
        sa.Column("metadata_record_id", sa.UUID(), nullable=False),
        sa.Column("system_id", sa.UUID(), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=datetime_default,
            nullable=True,
        ),
        sa.ForeignKeyConstraint(
            ["metadata_record_id"],
            [ref("metadata_records") + ".id"],
            ondelete="CASCADE",
        ),
        sa.ForeignKeyConstraint(
            ["system_id"], [ref("systems") + ".id"], ondelete="CASCADE"
        ),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "metadata_record_id", "system_id", name="uq_mr_system_source"
        ),
        schema=schema,
    )

    # Step 7: Add foreign keys from data_contracts to other tables (circular dependencies)
    # These are deferred because the referenced tables now exist.
    # SQLite does not support ALTER ADD CONSTRAINT; use batch_alter_table.
    is_sqlite = schema is None
    if is_sqlite:
        with op.batch_alter_table("data_contracts", schema=schema) as batch:
            batch.create_foreign_key(
                "fk_data_contracts_schema_id",
                "schemas",
                ["schema_id"],
                ["id"],
                ondelete="CASCADE",
            )
            batch.create_foreign_key(
                "fk_data_contracts_coercion_rules_id",
                "coercion_rules",
                ["coercion_rules_id"],
                ["id"],
                ondelete="SET NULL",
            )
            batch.create_foreign_key(
                "fk_data_contracts_validation_rules_id",
                "validation_rules",
                ["validation_rules_id"],
                ["id"],
                ondelete="SET NULL",
            )
            batch.create_foreign_key(
                "fk_data_contracts_metadata_record_id",
                "metadata_records",
                ["metadata_record_id"],
                ["id"],
                ondelete="SET NULL",
            )
    else:
        op.create_foreign_key(
            "fk_data_contracts_schema_id",
            "data_contracts",
            "schemas",
            ["schema_id"],
            ["id"],
            source_schema=schema,
            referent_schema=schema,
            ondelete="CASCADE",
        )
        op.create_foreign_key(
            "fk_data_contracts_coercion_rules_id",
            "data_contracts",
            "coercion_rules",
            ["coercion_rules_id"],
            ["id"],
            source_schema=schema,
            referent_schema=schema,
            ondelete="SET NULL",
        )
        op.create_foreign_key(
            "fk_data_contracts_validation_rules_id",
            "data_contracts",
            "validation_rules",
            ["validation_rules_id"],
            ["id"],
            source_schema=schema,
            referent_schema=schema,
            ondelete="SET NULL",
        )
        op.create_foreign_key(
            "fk_data_contracts_metadata_record_id",
            "data_contracts",
            "metadata_records",
            ["metadata_record_id"],
            ["id"],
            source_schema=schema,
            referent_schema=schema,
            ondelete="SET NULL",
        )


def downgrade() -> None:
    bind = op.get_bind()
    schema = get_schema(bind)
    is_sqlite = schema is None
    # Drop foreign keys first (SQLite requires batch_alter_table)
    if is_sqlite:
        with op.batch_alter_table("data_contracts", schema=schema) as batch:
            batch.drop_constraint(
                "fk_data_contracts_metadata_record_id", type_="foreignkey"
            )
            batch.drop_constraint(
                "fk_data_contracts_validation_rules_id", type_="foreignkey"
            )
            batch.drop_constraint(
                "fk_data_contracts_coercion_rules_id", type_="foreignkey"
            )
            batch.drop_constraint("fk_data_contracts_schema_id", type_="foreignkey")
    else:
        op.drop_constraint(
            "fk_data_contracts_metadata_record_id",
            "data_contracts",
            schema=schema,
            type_="foreignkey",
        )
        op.drop_constraint(
            "fk_data_contracts_validation_rules_id",
            "data_contracts",
            schema=schema,
            type_="foreignkey",
        )
        op.drop_constraint(
            "fk_data_contracts_coercion_rules_id",
            "data_contracts",
            schema=schema,
            type_="foreignkey",
        )
        op.drop_constraint(
            "fk_data_contracts_schema_id",
            "data_contracts",
            schema=schema,
            type_="foreignkey",
        )

    # Drop tables in reverse order
    op.drop_table("metadata_record_system_sources", schema=schema)
    op.drop_table("metadata_record_system_pushes", schema=schema)
    op.drop_table("metadata_record_system_pulls", schema=schema)
    op.drop_table("metadata_record_support_lead", schema=schema)
    op.drop_table("metadata_record_it_sme", schema=schema)
    op.drop_table("metadata_record_it_application_owners", schema=schema)
    op.drop_table("metadata_record_domains", schema=schema)
    op.drop_table("metadata_record_business_owners", schema=schema)
    op.drop_table("metadata_record_bu_sme", schema=schema)
    op.drop_table("validation_rules", schema=schema)
    op.drop_table("systems", schema=schema)
    op.drop_table("schemas", schema=schema)
    op.drop_table("owners", schema=schema)
    op.drop_table("metadata_records", schema=schema)
    op.drop_table("domains", schema=schema)
    op.drop_table("data_contracts", schema=schema)
    op.drop_table("coercion_rules", schema=schema)
