"""enterprise tenant and sync durability schema

Revision ID: 20260715_0006
Revises: 20260714_0005
Create Date: 2026-07-15 12:00:00
"""

from typing import Sequence, Union

import sqlalchemy as sa
from alembic import op

revision: str = "20260715_0006"
down_revision: Union[str, None] = "20260714_0005"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    op.create_table(
        "enterprises",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("name", sa.String(length=255), nullable=False),
        sa.Column("slug", sa.String(length=64), nullable=False),
        sa.Column("status", sa.String(length=16), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("slug"),
    )
    op.create_index("idx_enterprises_slug", "enterprises", ["slug"], unique=True)
    op.create_index("idx_enterprises_status", "enterprises", ["status"], unique=False)

    op.create_table(
        "enterprise_members",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("user_id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("role", sa.String(length=32), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("user_id", "enterprise_id", name="uq_enterprise_members_user_enterprise"),
    )
    op.create_index("idx_enterprise_members_user", "enterprise_members", ["user_id"], unique=False)
    op.create_index("idx_enterprise_members_enterprise", "enterprise_members", ["enterprise_id"], unique=False)

    op.create_table(
        "farm_hubs",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("name", sa.String(length=255), nullable=False),
        sa.Column("hub_site_id", sa.String(length=36), nullable=False),
        sa.Column("url", sa.String(length=512), nullable=True),
        sa.Column("status", sa.String(length=16), nullable=False),
        sa.Column("last_seen", sa.DateTime(timezone=True), nullable=True),
        sa.Column("wesp_version", sa.String(length=64), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("hub_site_id"),
    )
    op.create_index("idx_farm_hubs_enterprise", "farm_hubs", ["enterprise_id"], unique=False)
    op.create_index("idx_farm_hubs_status", "farm_hubs", ["status"], unique=False)

    op.create_table(
        "user_farm_access",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("user_id", sa.String(length=36), nullable=False),
        sa.Column("farm_hub_id", sa.String(length=36), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["farm_hub_id"], ["farm_hubs.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("user_id", "farm_hub_id", name="uq_user_farm_access_user_farm"),
    )
    op.create_index("idx_user_farm_access_user", "user_farm_access", ["user_id"], unique=False)
    op.create_index("idx_user_farm_access_farm", "user_farm_access", ["farm_hub_id"], unique=False)

    op.create_table(
        "hub_credentials",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("farm_hub_id", sa.String(length=36), nullable=False),
        sa.Column("secret_hash", sa.String(length=255), nullable=False),
        sa.Column("paired_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
        sa.ForeignKeyConstraint(["farm_hub_id"], ["farm_hubs.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index("idx_hub_credentials_farm", "hub_credentials", ["farm_hub_id"], unique=False)

    op.create_table(
        "hub_pairing_sessions",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("code", sa.String(length=6), nullable=False),
        sa.Column("code_hash", sa.String(length=64), nullable=False),
        sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("confirmed_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("farm_hub_id", sa.String(length=36), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["farm_hub_id"], ["farm_hubs.id"], ondelete="SET NULL"),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index("idx_hub_pairing_code", "hub_pairing_sessions", ["code"], unique=False)
    op.create_index("idx_hub_pairing_enterprise", "hub_pairing_sessions", ["enterprise_id"], unique=False)

    _create_sync_tables()
    _create_zootech_tables()

    bind = op.get_bind()
    if bind.dialect.name == "postgresql":
        _create_rls_policies()


def _create_sync_tables() -> None:
    op.create_table(
        "sync_outbox",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("event_id", sa.String(length=36), nullable=False),
        sa.Column("origin", sa.String(length=32), nullable=False),
        sa.Column("origin_site_id", sa.String(length=36), nullable=True),
        sa.Column("seq", sa.BigInteger(), nullable=False),
        sa.Column("domain", sa.String(length=16), nullable=False),
        sa.Column("table_name", sa.String(length=64), nullable=False),
        sa.Column("record_id", sa.String(length=64), nullable=False),
        sa.Column("action", sa.String(length=16), nullable=False),
        sa.Column("version", sa.Integer(), nullable=False),
        sa.Column("content_hash", sa.String(length=64), nullable=False),
        sa.Column("payload_json", sa.Text(), nullable=False),
        sa.Column("status", sa.String(length=16), nullable=False),
        sa.Column("emitted_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("event_id"),
    )
    op.create_index("idx_sync_outbox_enterprise_status", "sync_outbox", ["enterprise_id", "status"], unique=False)
    op.create_index("idx_sync_outbox_table_record", "sync_outbox", ["table_name", "record_id"], unique=False)

    op.create_table(
        "sync_event_log",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("event_id", sa.String(length=36), nullable=False),
        sa.Column("origin", sa.String(length=32), nullable=False),
        sa.Column("origin_site_id", sa.String(length=36), nullable=True),
        sa.Column("seq", sa.BigInteger(), nullable=False),
        sa.Column("domain", sa.String(length=16), nullable=False),
        sa.Column("table_name", sa.String(length=64), nullable=False),
        sa.Column("record_id", sa.String(length=64), nullable=False),
        sa.Column("action", sa.String(length=16), nullable=False),
        sa.Column("version", sa.Integer(), nullable=False),
        sa.Column("content_hash", sa.String(length=64), nullable=False),
        sa.Column("payload_json", sa.Text(), nullable=False),
        sa.Column("received_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("event_id"),
    )
    op.create_index("idx_sync_event_log_enterprise", "sync_event_log", ["enterprise_id"], unique=False)
    op.create_index("idx_sync_event_log_seq", "sync_event_log", ["seq"], unique=False)

    op.create_table(
        "sync_applied_events",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("site_id", sa.String(length=36), nullable=False),
        sa.Column("event_id", sa.String(length=36), nullable=False),
        sa.Column("applied_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("site_id", "event_id", name="uq_sync_applied_events_site_event"),
    )
    op.create_index("idx_sync_applied_enterprise", "sync_applied_events", ["enterprise_id"], unique=False)

    op.create_table(
        "sync_cursors",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("farm_hub_id", sa.String(length=36), nullable=False),
        sa.Column("direction", sa.String(length=16), nullable=False),
        sa.Column("last_acked_seq", sa.BigInteger(), nullable=False),
        sa.Column("last_pulled_seq", sa.BigInteger(), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["farm_hub_id"], ["farm_hubs.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("farm_hub_id", "direction", name="uq_sync_cursors_hub_direction"),
    )

    op.create_table(
        "sync_record_state",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("table_name", sa.String(length=64), nullable=False),
        sa.Column("record_id", sa.String(length=64), nullable=False),
        sa.Column("agreed_version", sa.Integer(), nullable=False),
        sa.Column("agreed_hash", sa.String(length=64), nullable=False),
        sa.Column("last_event_id", sa.String(length=36), nullable=True),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("enterprise_id", "table_name", "record_id", name="uq_sync_record_state_ent_table_record"),
    )

    op.create_table(
        "sync_conflicts",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("table_name", sa.String(length=64), nullable=False),
        sa.Column("record_id", sa.String(length=64), nullable=False),
        sa.Column("farm_hub_id", sa.String(length=36), nullable=True),
        sa.Column("orchestrator_snapshot_json", sa.Text(), nullable=False),
        sa.Column("hub_snapshot_json", sa.Text(), nullable=False),
        sa.Column("held_event_ids_json", sa.Text(), nullable=False),
        sa.Column("status", sa.String(length=16), nullable=False),
        sa.Column("resolution", sa.String(length=32), nullable=True),
        sa.Column("resolved_by", sa.String(length=36), nullable=True),
        sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["farm_hub_id"], ["farm_hubs.id"], ondelete="SET NULL"),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index("idx_sync_conflicts_status", "sync_conflicts", ["status"], unique=False)
    op.create_index("idx_sync_conflicts_record", "sync_conflicts", ["record_id"], unique=False)

    op.create_table(
        "sync_reconcile_runs",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("mismatches_count", sa.Integer(), nullable=False),
        sa.Column("auto_repaired", sa.Integer(), nullable=False),
        sa.Column("status", sa.String(length=16), nullable=False),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
    )

    op.create_table(
        "report_sync_state",
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("farm_hub_id", sa.String(length=36), nullable=False),
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("last_report_cursor", sa.BigInteger(), nullable=False),
        sa.Column("last_pull_at", sa.DateTime(timezone=True), nullable=True),
        sa.ForeignKeyConstraint(["enterprise_id"], ["enterprises.id"], ondelete="CASCADE"),
        sa.ForeignKeyConstraint(["farm_hub_id"], ["farm_hubs.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("farm_hub_id"),
    )


def _create_zootech_tables() -> None:
    op.create_table(
        "zootech_component",
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("name", sa.String(length=100), nullable=False),
        sa.Column("type", sa.String(length=100), nullable=False),
        sa.Column("is_active", sa.Boolean(), nullable=False),
        sa.Column("dry_matter", sa.Float(), nullable=False),
        sa.Column("protein", sa.Float(), nullable=False),
        sa.Column("energy", sa.Float(), nullable=False),
        sa.Column("price", sa.Float(), nullable=False),
        sa.Column("external_no", sa.Integer(), nullable=True),
        sa.Column("version", sa.Integer(), nullable=False),
        sa.Column("content_hash", sa.String(length=64), nullable=False),
        sa.Column("is_deleted", sa.Boolean(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("enterprise_id", "id", name="uq_zootech_component_ent_id"),
    )
    op.create_index("idx_zootech_component_enterprise", "zootech_component", ["enterprise_id"], unique=False)

    op.create_table(
        "zootech_recipe",
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("name", sa.String(length=100), nullable=False),
        sa.Column("heads_per_trip", sa.Integer(), nullable=False),
        sa.Column("mixing_time", sa.Integer(), nullable=False),
        sa.Column("trip_percent", sa.Float(), nullable=False),
        sa.Column("dry_matter_locked", sa.Boolean(), nullable=False),
        sa.Column("unloading_link_broken", sa.Boolean(), nullable=False),
        sa.Column("target_component_id", sa.String(length=36), nullable=True),
        sa.Column("ration_type", sa.String(length=10), nullable=True),
        sa.Column("version", sa.Integer(), nullable=False),
        sa.Column("content_hash", sa.String(length=64), nullable=False),
        sa.Column("is_deleted", sa.Boolean(), nullable=False),
        sa.Column("payload_json", sa.Text(), nullable=True),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("enterprise_id", "id", name="uq_zootech_recipe_ent_id"),
    )
    op.create_index("idx_zootech_recipe_enterprise", "zootech_recipe", ["enterprise_id"], unique=False)

    op.create_table(
        "zootech_ingredient",
        sa.Column("enterprise_id", sa.String(length=36), nullable=False),
        sa.Column("id", sa.String(length=36), nullable=False),
        sa.Column("recipe_id", sa.String(length=36), nullable=False),
        sa.Column("component_id", sa.String(length=36), nullable=True),
        sa.Column("name", sa.String(length=100), nullable=False),
        sa.Column("amount", sa.Float(), nullable=False),
        sa.Column("weight_per_head", sa.Float(), nullable=True),
        sa.Column("dry_matter", sa.Float(), nullable=False),
        sa.Column("dry_matter_per_head", sa.Float(), nullable=True),
        sa.Column("order", sa.Integer(), nullable=False),
        sa.Column("version", sa.Integer(), nullable=False),
        sa.Column("content_hash", sa.String(length=64), nullable=False),
        sa.Column("is_deleted", sa.Boolean(), nullable=False),
        sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
        sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint("enterprise_id", "id", name="uq_zootech_ingredient_ent_id"),
    )
    op.create_index("idx_zootech_ingredient_enterprise", "zootech_ingredient", ["enterprise_id"], unique=False)
    op.create_index("idx_zootech_ingredient_recipe", "zootech_ingredient", ["recipe_id"], unique=False)


def _create_rls_policies() -> None:
    for table in ("zootech_component", "zootech_recipe", "zootech_ingredient"):
        op.execute(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY")
        op.execute(
            f"""
            CREATE POLICY tenant_isolation_{table} ON {table}
            USING (enterprise_id = current_setting('app.enterprise_id', true))
            WITH CHECK (enterprise_id = current_setting('app.enterprise_id', true))
            """
        )


def downgrade() -> None:
    bind = op.get_bind()
    if bind.dialect.name == "postgresql":
        for table in ("zootech_component", "zootech_recipe", "zootech_ingredient"):
            op.execute(f"DROP POLICY IF EXISTS tenant_isolation_{table} ON {table}")

    for table in (
        "zootech_ingredient",
        "zootech_recipe",
        "zootech_component",
        "report_sync_state",
        "sync_reconcile_runs",
        "sync_conflicts",
        "sync_record_state",
        "sync_cursors",
        "sync_applied_events",
        "sync_event_log",
        "sync_outbox",
        "hub_pairing_sessions",
        "hub_credentials",
        "user_farm_access",
        "farm_hubs",
        "enterprise_members",
        "enterprises",
    ):
        op.drop_table(table)
