"""Phase 7S.3 email-to-engagement mapping

Revision ID: 20260612_phase_7s3_email_mapping
Revises: 20260611_phase_7s2_imap_incoming
Create Date: 2026-06-12

SQLite-safe revision:
- Adds reference id columns as plain Integer columns instead of adding
  ForeignKey constraints through ALTER TABLE, because SQLite cannot ALTER
  constraints without batch copy/move migration.
- SQLAlchemy model relationships can still use these id columns for lookups.
"""
from __future__ import annotations

from alembic import op
import sqlalchemy as sa


revision = "20260612_phase_7s3_email_mapping"
down_revision = "20260611_phase_7s2_imap_incoming"
branch_labels = None
depends_on = None


def _inspector():
    return sa.inspect(op.get_bind())


def _has_table(table_name: str) -> bool:
    return table_name in _inspector().get_table_names()


def _has_column(table_name: str, column_name: str) -> bool:
    if not _has_table(table_name):
        return False
    return column_name in {col["name"] for col in _inspector().get_columns(table_name)}


def _create_index_if_missing(table_name: str, index_name: str, columns: list[str]) -> None:
    if not _has_table(table_name):
        return
    existing = {idx["name"] for idx in _inspector().get_indexes(table_name)}
    if index_name not in existing:
        op.create_index(index_name, table_name, columns)


def _add_column_if_missing(table_name: str, column: sa.Column) -> None:
    if _has_table(table_name) and not _has_column(table_name, column.name):
        op.add_column(table_name, column)


def upgrade() -> None:
    if not _has_table("email_incoming_messages"):
        return

    _add_column_if_missing(
        "email_incoming_messages",
        sa.Column("tracking_code", sa.String(length=80), nullable=True),
    )

    # SQLite does not support ALTER TABLE ADD CONSTRAINT for ForeignKey.
    # Keep these as nullable integer reference columns. Application code and
    # SQLAlchemy models can still resolve the linked records safely.
    _add_column_if_missing(
        "email_incoming_messages",
        sa.Column("matched_engagement_id", sa.Integer(), nullable=True),
    )
    _add_column_if_missing(
        "email_incoming_messages",
        sa.Column("matched_task_id", sa.Integer(), nullable=True),
    )
    _add_column_if_missing(
        "email_incoming_messages",
        sa.Column("matched_invoice_id", sa.Integer(), nullable=True),
    )
    _add_column_if_missing(
        "email_incoming_messages",
        sa.Column("mapping_status", sa.String(length=30), nullable=False, server_default="UNMAPPED"),
    )
    _add_column_if_missing(
        "email_incoming_messages",
        sa.Column("mapping_notes", sa.Text(), nullable=True),
    )
    _add_column_if_missing(
        "email_incoming_messages",
        sa.Column("mapped_at_utc", sa.DateTime(timezone=True), nullable=True),
    )

    for name, cols in {
        "ix_email_incoming_messages_tracking_code": ["tracking_code"],
        "ix_email_incoming_messages_matched_engagement_id": ["matched_engagement_id"],
        "ix_email_incoming_messages_matched_task_id": ["matched_task_id"],
        "ix_email_incoming_messages_matched_invoice_id": ["matched_invoice_id"],
        "ix_email_incoming_messages_mapping_status": ["mapping_status"],
        "ix_email_incoming_messages_mapped_at_utc": ["mapped_at_utc"],
    }.items():
        _create_index_if_missing("email_incoming_messages", name, cols)


def downgrade() -> None:
    # Keep defensive because SQLite cannot reliably drop columns/constraints
    # in all local development environments without a batch table rewrite.
    pass
