"""Normalize ingredient.aisle and grocery_item.aisle to canonical labels.

Revision ID: 0015
Revises: 0014
Create Date: 2026-06-02

Normalizes free-text aisle values on `ingredient.aisle` and `grocery_item.aisle`
to a fixed canonical set. Runs in a single transaction (Alembic default); both
op.execute calls share the same session, so the temp backup tables persist
for the duration of the upgrade.

Backup tables: the temp tables `ingredient_aisle_backup` and
`grocery_item_aisle_backup` are created for the migration's session. They
auto-drop when the session ends. If you need a persistent backup, run
`backend/scripts/persist_aisle_backup.sql` BEFORE this migration.

Deploy via Docker (the db runs inside a container; no host psql required):

    # 1. dry-run preview:
    docker compose exec db psql -U mealplanner -d mealplanner \\
        -f /dev/stdin < backend/scripts/dry_run_aisle_migration.sql

    # 2. apply:
    docker compose exec backend alembic upgrade head
"""
from typing import Sequence, Union

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "0015"
down_revision: Union[str, None] = "0014"
branch_labels: Union[Sequence[str], None] = None
depends_on: Union[Sequence[str], None] = None


NORMALIZATION_RULES = [
    ("canned goods", "Pantry"),
    ("canned", "Pantry"),
    ("freezer", "Frozen"),
    ("frozen", "Frozen"),
    ("produce", "Produce"),
    ("fruit", "Produce"),
    ("vegetable", "Produce"),
    ("dairy", "Dairy & Eggs"),
    ("eggs", "Dairy & Eggs"),
    ("cheese", "Dairy & Eggs"),
    ("milk", "Dairy & Eggs"),
    ("yogurt", "Dairy & Eggs"),
    ("meat", "Meat & Seafood"),
    ("seafood", "Meat & Seafood"),
    ("fish", "Meat & Seafood"),
    ("chicken", "Meat & Seafood"),
    ("beef", "Meat & Seafood"),
    ("pork", "Meat & Seafood"),
    ("meat_seafood", "Meat & Seafood"),
    ("bakery", "Bakery"),
    ("bread", "Bakery"),
    ("beverage", "Beverages"),
    ("beverages", "Beverages"),
    ("drinks", "Beverages"),
    ("spice", "Spices"),
    ("spices", "Spices"),
    ("seasoning", "Spices"),
    ("pantry", "Pantry"),
    ("dry", "Pantry"),
    ("snack", "Pantry"),
    ("snacks", "Pantry"),
]

CASE_EXPR = (
    "CASE LOWER(COALESCE(aisle::text, '')) "
    + " ".join(f"WHEN '{src}' THEN '{dst}' " for src, dst in NORMALIZATION_RULES)
    + " WHEN '' THEN NULL "
    + " ELSE 'Other' END"
)


def _normalize(table: str) -> None:
    op.execute(
        f"CREATE TEMP TABLE {table}_aisle_backup AS "
        f"SELECT id, aisle FROM {table} WHERE aisle IS NOT NULL"
    )
    # Filter to non-NULL aisle rows so the SET target type is the column's
    # varchar(100) and matches the CASE expression's inferred type.
    op.execute(
        f"UPDATE {table} SET aisle = {CASE_EXPR}::varchar(100) "
        f"WHERE aisle IS NOT NULL"
    )


def upgrade() -> None:
    _normalize("ingredient")
    _normalize("grocery_item")


def downgrade() -> None:
    # The temp backup tables only exist for the migration's session.
    # Restoring the pre-normalization state is not possible from this
    # migration alone. Operators must restore from a database snapshot
    # taken before upgrade.
    raise NotImplementedError(
        "Cannot reverse aisle normalization without an external backup. "
        "Restore the database from a snapshot taken before 0015 was applied."
    )
