"""Alembic auto-migrate on startup service."""

import logging
import time

from alembic import command as alembic_command
from alembic.autogenerate import api as ag_api
from alembic.config import Config
from alembic.runtime.migration import MigrationContext
from pydantic import BaseModel, DirectoryPath, Field, FilePath
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlmodel import SQLModel

logger = logging.getLogger(__name__)


class AlembicAutoMigrate(BaseModel):
    """Service to run alembic migrations on app startup, with optional autogeneration."""

    # Database
    db_url: str = Field(..., description="Synchronous SQLAlchemy URL (e.g., postgresql+psycopg://...)")

    # Alembic Locations
    ini_path: FilePath = Field(..., description="Path to 'alembic.ini'.")
    script_location: DirectoryPath = Field(..., description="Alembic 'alembic' folder, which contains 'env.py'.")

    # Behaviour
    autogenerate: bool = Field(default=False, description="Whether we may create a new revision automatically.")
    migration_message: str = Field(default="autogenerated at startup", description="Message for new revisions.")
    compare_type: bool = Field(default=True)
    compare_server_default: bool = Field(default=True)
    include_schemas: bool = Field(default=False)

    # ---------- Public API ----------
    def run(self) -> str | None:
        """Run migrations, possibly creating a new revision."""
        cfg = self._build_cfg(sync_url=self.db_url)
        created: str | None = None

        # preamble
        logger.info("starting (env=%s, policy=%s, url=%s, script_location=%s)")

        t0 = time.perf_counter()

        # Pre revision head upgrade (ensures new revisions CAN be created)
        # otherwise you will get alembic.util.exc.CommandError: Target database is not up to date.
        logger.info("upgrading to head...")
        alembic_command.upgrade(cfg, "head")
        logger.info("upgrade complete")

        # explain autogenerate decision
        has_new_revisions = False
        if self.autogenerate:
            logger.debug("checking for metadata diffs...")

            has_diffs = self._metadata_has_diffs()
            logger.info("metadata diffs detected=%s", has_diffs)

            if has_diffs:
                created = alembic_command.revision(cfg, message=self.migration_message, autogenerate=True)
                if created:
                    has_new_revisions = True
                logger.info("created new revision id=%s", created)
            else:
                logger.info("no new revision created")
        else:
            logger.info("autogenerate skipped (disabled via settings)")

        # Final upgrade to head (if we created a new revision)
        if has_new_revisions:
            logger.info("upgrading to head...")
            alembic_command.upgrade(cfg, "head")
            logger.info("upgrade complete")

        t1 = time.perf_counter()
        logger.info("done in %.2fs (created=%s)", t1 - t0, bool(created))
        return created

    # ---------- Internals ----------
    def _build_cfg(self, *, sync_url: str) -> Config:
        cfg = Config(self.ini_path)
        cfg.set_main_option("script_location", str(self.script_location))
        cfg.set_main_option("sqlalchemy.url", str(sync_url))  # <- SYNC URL here
        return cfg

    def _metadata_has_diffs(self) -> list:
        """Check if the current metadata has diffs compared to the database schema."""
        engine: Engine = create_engine(self.db_url)
        with engine.begin() as conn:
            mc = MigrationContext.configure(
                conn,
                opts={
                    "compare_type": self.compare_type,
                    "compare_server_default": self.compare_server_default,
                    "include_schemas": self.include_schemas,
                },
            )
            diffs = ag_api.compare_metadata(mc, SQLModel.metadata)
            return diffs
