"""recompute book matching keys

Revision ID: ed41acf21270
Revises: 4358e7d4743a
Create Date: 2026-08-15 15:44:28.341020

"""

import warnings

import sqlalchemy as sa
from alembic import op
from advanced_alchemy.types import EncryptedString, EncryptedText, GUID, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash, FernetBackend
from advanced_alchemy.types.encrypted_string import PGCryptoBackend
from advanced_alchemy.types.password_hash.argon2 import Argon2Hasher
from advanced_alchemy.types.password_hash.passlib import PasslibHasher
from advanced_alchemy.types.password_hash.pwdlib import PwdlibHasher
from sqlalchemy import Text  # noqa: F401

__all__ = ["downgrade", "upgrade", "schema_upgrades", "schema_downgrades", "data_upgrades", "data_downgrades"]

sa.GUID = GUID
sa.DateTimeUTC = DateTimeUTC
sa.ORA_JSONB = ORA_JSONB
sa.EncryptedString = EncryptedString
sa.EncryptedText = EncryptedText
sa.StoredObject = StoredObject
sa.PasswordHash = PasswordHash
sa.Argon2Hasher = Argon2Hasher
sa.PasslibHasher = PasslibHasher
sa.PwdlibHasher = PwdlibHasher
sa.FernetBackend = FernetBackend
sa.PGCryptoBackend = PGCryptoBackend

# revision identifiers, used by Alembic.
revision = 'ed41acf21270'
down_revision = '4358e7d4743a'
branch_labels = None
depends_on = None


def upgrade() -> None:
    with warnings.catch_warnings():
        warnings.filterwarnings("ignore", category=UserWarning)
        with op.get_context().autocommit_block():
            schema_upgrades()
            data_upgrades()

def downgrade() -> None:
    with warnings.catch_warnings():
        warnings.filterwarnings("ignore", category=UserWarning)
        with op.get_context().autocommit_block():
            data_downgrades()
            schema_downgrades()

def schema_upgrades() -> None:
    """schema upgrade migrations go here."""
    pass

def schema_downgrades() -> None:
    """schema downgrade migrations go here."""
    pass

def data_upgrades() -> None:
    """
    Recompute every matching key against the current normalization.

    The keys are derived, so changing a helper in `services/matching.py` silently
    invalidates every row already written — a book stored under the old rules simply
    stops matching one stored under the new ones, with nothing to show that anything
    is wrong. `normalize_title` learned to strip the compact edition markers a cover
    actually carries ("2E", "5e"), which moved `Building Microservices, 2E` onto the
    same key as `Building Microservices`.

    Any later change to those helpers wants a revision that looks exactly like this
    one. It is idempotent and safe to re-run.
    """
    from chitai.services.matching import (
        normalize_author,
        normalize_identifier,
        normalize_title,
    )

    connection = op.get_bind()

    books = connection.execute(sa.text("SELECT id, title FROM books")).fetchall()
    _apply(
        connection,
        "UPDATE books SET normalized_title = :key WHERE id = :id",
        [{"id": id, "key": normalize_title(title)} for id, title in books],
    )

    authors = connection.execute(sa.text("SELECT id, name FROM authors")).fetchall()
    _apply(
        connection,
        "UPDATE authors SET normalized_name = :key WHERE id = :id",
        [{"id": id, "key": normalize_author(name)} for id, name in authors],
    )

    identifiers = connection.execute(
        sa.text("SELECT id, name, value FROM identifiers")
    ).fetchall()
    _apply(
        connection,
        "UPDATE identifiers SET normalized_value = :key WHERE id = :id",
        [
            {"id": id, "key": normalize_identifier(name, value)}
            for id, name, value in identifiers
        ],
    )


def _apply(connection, statement: str, parameters: list[dict]) -> None:
    """Run one update per row, in batches, skipping the work when there are none."""
    batch_size = 1000

    for start in range(0, len(parameters), batch_size):
        connection.execute(sa.text(statement), parameters[start : start + batch_size])


def data_downgrades() -> None:
    """Add any optional data downgrade migrations here!"""
