"""add email templates and logs tables

Revision ID: d7a4a3f06394
Revises: 404b3e2d2865
Create Date: 2025-12-27 20:48:00.661523

"""
from collections.abc import Sequence

import sqlalchemy as sa
from sqlalchemy import text

from alembic import op

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


def upgrade() -> None:
    # Create email_templates table
    op.create_table("email_templates",
        sa.Column("id", sa.Integer(), nullable=False),
        sa.Column("code", sa.String(length=100), nullable=False),
        sa.Column("language", sa.String(length=5), nullable=False),
        sa.Column("name", sa.String(length=255), nullable=False),
        sa.Column("description", sa.Text(), nullable=True),
        sa.Column("category", sa.String(length=50), nullable=False),
        sa.Column("subject", sa.String(length=500), nullable=False),
        sa.Column("body_html", sa.Text(), nullable=False),
        sa.Column("body_text", sa.Text(), nullable=True),
        sa.Column("variables", sa.Text(), nullable=True),
        sa.Column("is_active", sa.Boolean(), nullable=False),
        sa.Column("created_at", sa.DateTime(), nullable=False),
        sa.Column("updated_at", sa.DateTime(), nullable=False),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index(op.f("ix_email_templates_category"), "email_templates", ["category"], unique=False)
    op.create_index(op.f("ix_email_templates_code"), "email_templates", ["code"], unique=False)
    op.create_index(op.f("ix_email_templates_id"), "email_templates", ["id"], unique=False)

    # Create email_logs table
    op.create_table("email_logs",
        sa.Column("id", sa.Integer(), nullable=False),
        sa.Column("template_code", sa.String(length=100), nullable=True),
        sa.Column("template_id", sa.Integer(), nullable=True),
        sa.Column("recipient_email", sa.String(length=255), nullable=False),
        sa.Column("recipient_name", sa.String(length=255), nullable=True),
        sa.Column("subject", sa.String(length=500), nullable=False),
        sa.Column("body_html", sa.Text(), nullable=True),
        sa.Column("body_text", sa.Text(), nullable=True),
        sa.Column("from_email", sa.String(length=255), nullable=False),
        sa.Column("from_name", sa.String(length=255), nullable=True),
        sa.Column("reply_to", sa.String(length=255), nullable=True),
        sa.Column("status", sa.String(length=20), nullable=False),
        sa.Column("sent_at", sa.DateTime(), nullable=True),
        sa.Column("delivered_at", sa.DateTime(), nullable=True),
        sa.Column("opened_at", sa.DateTime(), nullable=True),
        sa.Column("clicked_at", sa.DateTime(), nullable=True),
        sa.Column("error_message", sa.Text(), nullable=True),
        sa.Column("retry_count", sa.Integer(), nullable=False),
        sa.Column("provider", sa.String(length=50), nullable=True),
        sa.Column("provider_message_id", sa.String(length=255), nullable=True),
        sa.Column("vendor_id", sa.Integer(), nullable=True),
        sa.Column("user_id", sa.Integer(), nullable=True),
        sa.Column("related_type", sa.String(length=50), nullable=True),
        sa.Column("related_id", sa.Integer(), nullable=True),
        sa.Column("extra_data", sa.Text(), nullable=True),
        sa.Column("created_at", sa.DateTime(), nullable=False),
        sa.Column("updated_at", sa.DateTime(), nullable=False),
        sa.ForeignKeyConstraint(["template_id"], ["email_templates.id"]),
        sa.ForeignKeyConstraint(["user_id"], ["users.id"]),
        sa.ForeignKeyConstraint(["vendor_id"], ["vendors.id"]),
        sa.PrimaryKeyConstraint("id")
    )
    op.create_index(op.f("ix_email_logs_id"), "email_logs", ["id"], unique=False)
    op.create_index(op.f("ix_email_logs_provider_message_id"), "email_logs", ["provider_message_id"], unique=False)
    op.create_index(op.f("ix_email_logs_recipient_email"), "email_logs", ["recipient_email"], unique=False)
    op.create_index(op.f("ix_email_logs_status"), "email_logs", ["status"], unique=False)
    op.create_index(op.f("ix_email_logs_template_code"), "email_logs", ["template_code"], unique=False)
    op.create_index(op.f("ix_email_logs_user_id"), "email_logs", ["user_id"], unique=False)
    op.create_index(op.f("ix_email_logs_vendor_id"), "email_logs", ["vendor_id"], unique=False)

    # application_logs - alter columns
    op.alter_column("application_logs", "created_at", existing_type=sa.DATETIME(), nullable=False)
    op.alter_column("application_logs", "updated_at", existing_type=sa.DATETIME(), nullable=False)

    # capacity_snapshots indexes (PostgreSQL IF EXISTS/IF NOT EXISTS)
    op.execute(text("DROP INDEX IF EXISTS ix_capacity_snapshots_date"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_capacity_snapshots_date ON capacity_snapshots (snapshot_date)"))
    op.execute(text("CREATE UNIQUE INDEX IF NOT EXISTS ix_capacity_snapshots_snapshot_date ON capacity_snapshots (snapshot_date)"))

    # cart_items - alter columns
    op.alter_column("cart_items", "created_at", existing_type=sa.DATETIME(), nullable=False)
    op.alter_column("cart_items", "updated_at", existing_type=sa.DATETIME(), nullable=False)

    # customer_addresses index rename
    op.execute(text("DROP INDEX IF EXISTS ix_customers_addresses_id"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_customer_addresses_id ON customer_addresses (id)"))

    # inventory - alter columns and constraints
    op.alter_column("inventory", "warehouse", existing_type=sa.VARCHAR(), nullable=False)
    op.alter_column("inventory", "bin_location", existing_type=sa.VARCHAR(), nullable=False)
    op.alter_column("inventory", "location", existing_type=sa.VARCHAR(), nullable=True)
    op.execute(text("DROP INDEX IF EXISTS idx_inventory_product_location"))
    op.execute(text("ALTER TABLE inventory DROP CONSTRAINT IF EXISTS uq_inventory_product_location"))
    op.execute(text("""
        DO $$
        BEGIN
            IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'uq_inventory_product_warehouse_bin') THEN
                ALTER TABLE inventory ADD CONSTRAINT uq_inventory_product_warehouse_bin UNIQUE (product_id, warehouse, bin_location);
            END IF;
        END $$;
    """))

    # marketplace_import_errors and translations indexes
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_marketplace_import_errors_import_job_id ON marketplace_import_errors (import_job_id)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_marketplace_product_translations_id ON marketplace_product_translations (id)"))

    # marketplace_products - alter columns
    op.alter_column("marketplace_products", "is_digital", existing_type=sa.BOOLEAN(), nullable=True)
    op.alter_column("marketplace_products", "is_active", existing_type=sa.BOOLEAN(), nullable=True)

    # marketplace_products indexes
    op.execute(text("DROP INDEX IF EXISTS idx_mp_is_active"))
    op.execute(text("DROP INDEX IF EXISTS idx_mp_platform"))
    op.execute(text("DROP INDEX IF EXISTS idx_mp_sku"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_marketplace_products_is_active ON marketplace_products (is_active)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_marketplace_products_is_digital ON marketplace_products (is_digital)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_marketplace_products_mpn ON marketplace_products (mpn)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_marketplace_products_platform ON marketplace_products (platform)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_marketplace_products_sku ON marketplace_products (sku)"))

    # order_item_exceptions - constraints and indexes
    op.execute(text("DROP INDEX IF EXISTS uq_order_item_exception"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_order_item_exceptions_original_gtin ON order_item_exceptions (original_gtin)"))
    op.execute(text("""
        DO $$
        BEGIN
            IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'uq_order_item_exceptions_order_item_id') THEN
                ALTER TABLE order_item_exceptions ADD CONSTRAINT uq_order_item_exceptions_order_item_id UNIQUE (order_item_id);
            END IF;
        END $$;
    """))

    # order_items - alter column
    op.alter_column("order_items", "needs_product_match", existing_type=sa.BOOLEAN(), nullable=True)

    # order_items indexes
    op.execute(text("DROP INDEX IF EXISTS ix_order_items_gtin"))
    op.execute(text("DROP INDEX IF EXISTS ix_order_items_product_id"))

    # product_translations index
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_product_translations_id ON product_translations (id)"))

    # products indexes
    op.execute(text("DROP INDEX IF EXISTS idx_product_active"))
    op.execute(text("DROP INDEX IF EXISTS idx_product_featured"))
    op.execute(text("DROP INDEX IF EXISTS idx_product_gtin"))
    op.execute(text("DROP INDEX IF EXISTS idx_product_vendor_gtin"))

    # products constraint
    op.execute(text("ALTER TABLE products DROP CONSTRAINT IF EXISTS uq_product"))
    op.execute(text("""
        DO $$
        BEGIN
            IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'uq_vendor_marketplace_product') THEN
                ALTER TABLE products ADD CONSTRAINT uq_vendor_marketplace_product UNIQUE (vendor_id, marketplace_product_id);
            END IF;
        END $$;
    """))

    # products new indexes
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_product_vendor_active ON products (vendor_id, is_active)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_product_vendor_featured ON products (vendor_id, is_featured)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_products_gtin ON products (gtin)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_products_vendor_sku ON products (vendor_sku)"))

    # vendor_domains indexes
    op.execute(text("DROP INDEX IF EXISTS ix_vendors_domains_domain"))
    op.execute(text("DROP INDEX IF EXISTS ix_vendors_domains_id"))
    op.execute(text("CREATE UNIQUE INDEX IF NOT EXISTS ix_vendor_domains_domain ON vendor_domains (domain)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_vendor_domains_id ON vendor_domains (id)"))

    # vendor_subscriptions - alter column and FK
    op.alter_column("vendor_subscriptions", "payment_retry_count", existing_type=sa.INTEGER(), nullable=False)
    op.execute(text("""
        DO $$
        BEGIN
            IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_vendor_subscriptions_tier_id') THEN
                ALTER TABLE vendor_subscriptions ADD CONSTRAINT fk_vendor_subscriptions_tier_id
                    FOREIGN KEY (tier_id) REFERENCES subscription_tiers(id);
            END IF;
        END $$;
    """))

    # vendor_themes indexes
    op.execute(text("DROP INDEX IF EXISTS ix_vendors_themes_id"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_vendor_themes_id ON vendor_themes (id)"))

    # vendor_users indexes
    op.execute(text("DROP INDEX IF EXISTS ix_vendors_users_id"))
    op.execute(text("DROP INDEX IF EXISTS ix_vendors_users_invitation_token"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_vendor_users_id ON vendor_users (id)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_vendor_users_invitation_token ON vendor_users (invitation_token)"))

    # vendors - alter column
    op.alter_column("vendors", "company_id", existing_type=sa.INTEGER(), nullable=False)


def downgrade() -> None:
    # vendors
    op.alter_column("vendors", "company_id", existing_type=sa.INTEGER(), nullable=True)

    # vendor_users indexes
    op.execute(text("DROP INDEX IF EXISTS ix_vendor_users_invitation_token"))
    op.execute(text("DROP INDEX IF EXISTS ix_vendor_users_id"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_vendors_users_invitation_token ON vendor_users (invitation_token)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_vendors_users_id ON vendor_users (id)"))

    # vendor_themes indexes
    op.execute(text("DROP INDEX IF EXISTS ix_vendor_themes_id"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_vendors_themes_id ON vendor_themes (id)"))

    # vendor_subscriptions
    op.execute(text("ALTER TABLE vendor_subscriptions DROP CONSTRAINT IF EXISTS fk_vendor_subscriptions_tier_id"))
    op.alter_column("vendor_subscriptions", "payment_retry_count", existing_type=sa.INTEGER(), nullable=True)

    # vendor_domains indexes
    op.execute(text("DROP INDEX IF EXISTS ix_vendor_domains_id"))
    op.execute(text("DROP INDEX IF EXISTS ix_vendor_domains_domain"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_vendors_domains_id ON vendor_domains (id)"))
    op.execute(text("CREATE UNIQUE INDEX IF NOT EXISTS ix_vendors_domains_domain ON vendor_domains (domain)"))

    # products constraint and indexes
    op.execute(text("ALTER TABLE products DROP CONSTRAINT IF EXISTS uq_vendor_marketplace_product"))
    op.execute(text("""
        DO $$
        BEGIN
            IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'uq_product') THEN
                ALTER TABLE products ADD CONSTRAINT uq_product UNIQUE (vendor_id, marketplace_product_id);
            END IF;
        END $$;
    """))

    op.execute(text("DROP INDEX IF EXISTS ix_products_vendor_sku"))
    op.execute(text("DROP INDEX IF EXISTS ix_products_gtin"))
    op.execute(text("DROP INDEX IF EXISTS idx_product_vendor_featured"))
    op.execute(text("DROP INDEX IF EXISTS idx_product_vendor_active"))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_product_vendor_gtin ON products (vendor_id, gtin)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_product_gtin ON products (gtin)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_product_featured ON products (vendor_id, is_featured)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_product_active ON products (vendor_id, is_active)"))

    # product_translations
    op.execute(text("DROP INDEX IF EXISTS ix_product_translations_id"))

    # order_items
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_order_items_product_id ON order_items (product_id)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_order_items_gtin ON order_items (gtin)"))
    op.alter_column("order_items", "needs_product_match", existing_type=sa.BOOLEAN(), nullable=False)

    # order_item_exceptions
    op.execute(text("ALTER TABLE order_item_exceptions DROP CONSTRAINT IF EXISTS uq_order_item_exceptions_order_item_id"))
    op.execute(text("DROP INDEX IF EXISTS ix_order_item_exceptions_original_gtin"))
    op.execute(text("CREATE UNIQUE INDEX IF NOT EXISTS uq_order_item_exception ON order_item_exceptions (order_item_id)"))

    # marketplace_products indexes
    op.execute(text("DROP INDEX IF EXISTS ix_marketplace_products_sku"))
    op.execute(text("DROP INDEX IF EXISTS ix_marketplace_products_platform"))
    op.execute(text("DROP INDEX IF EXISTS ix_marketplace_products_mpn"))
    op.execute(text("DROP INDEX IF EXISTS ix_marketplace_products_is_digital"))
    op.execute(text("DROP INDEX IF EXISTS ix_marketplace_products_is_active"))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_mp_sku ON marketplace_products (sku)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_mp_platform ON marketplace_products (platform)"))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_mp_is_active ON marketplace_products (is_active)"))

    # marketplace_products columns
    op.alter_column("marketplace_products", "is_active", existing_type=sa.BOOLEAN(), nullable=False)
    op.alter_column("marketplace_products", "is_digital", existing_type=sa.BOOLEAN(), nullable=False)

    # marketplace imports
    op.execute(text("DROP INDEX IF EXISTS ix_marketplace_product_translations_id"))
    op.execute(text("DROP INDEX IF EXISTS ix_marketplace_import_errors_import_job_id"))

    # inventory
    op.execute(text("ALTER TABLE inventory DROP CONSTRAINT IF EXISTS uq_inventory_product_warehouse_bin"))
    op.execute(text("""
        DO $$
        BEGIN
            IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'uq_inventory_product_location') THEN
                ALTER TABLE inventory ADD CONSTRAINT uq_inventory_product_location UNIQUE (product_id, location);
            END IF;
        END $$;
    """))
    op.execute(text("CREATE INDEX IF NOT EXISTS idx_inventory_product_location ON inventory (product_id, location)"))
    op.alter_column("inventory", "location", existing_type=sa.VARCHAR(), nullable=False)
    op.alter_column("inventory", "bin_location", existing_type=sa.VARCHAR(), nullable=True)
    op.alter_column("inventory", "warehouse", existing_type=sa.VARCHAR(), nullable=True)

    # customer_addresses
    op.execute(text("DROP INDEX IF EXISTS ix_customer_addresses_id"))
    op.execute(text("CREATE INDEX IF NOT EXISTS ix_customers_addresses_id ON customer_addresses (id)"))

    # cart_items
    op.alter_column("cart_items", "updated_at", existing_type=sa.DATETIME(), nullable=True)
    op.alter_column("cart_items", "created_at", existing_type=sa.DATETIME(), nullable=True)

    # capacity_snapshots
    op.execute(text("DROP INDEX IF EXISTS ix_capacity_snapshots_snapshot_date"))
    op.execute(text("DROP INDEX IF EXISTS ix_capacity_snapshots_date"))
    op.execute(text("CREATE UNIQUE INDEX IF NOT EXISTS ix_capacity_snapshots_date ON capacity_snapshots (snapshot_date)"))

    # application_logs
    op.alter_column("application_logs", "updated_at", existing_type=sa.DATETIME(), nullable=True)
    op.alter_column("application_logs", "created_at", existing_type=sa.DATETIME(), nullable=True)

    # Drop email tables
    op.drop_index(op.f("ix_email_logs_vendor_id"), table_name="email_logs")
    op.drop_index(op.f("ix_email_logs_user_id"), table_name="email_logs")
    op.drop_index(op.f("ix_email_logs_template_code"), table_name="email_logs")
    op.drop_index(op.f("ix_email_logs_status"), table_name="email_logs")
    op.drop_index(op.f("ix_email_logs_recipient_email"), table_name="email_logs")
    op.drop_index(op.f("ix_email_logs_provider_message_id"), table_name="email_logs")
    op.drop_index(op.f("ix_email_logs_id"), table_name="email_logs")
    op.drop_table("email_logs")
    op.drop_index(op.f("ix_email_templates_id"), table_name="email_templates")
    op.drop_index(op.f("ix_email_templates_code"), table_name="email_templates")
    op.drop_index(op.f("ix_email_templates_category"), table_name="email_templates")
    op.drop_table("email_templates")
