from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Any

REPO_ROOT = Path(__file__).resolve().parents[1]
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

from packages.adapters.sqlalchemy_impl.models import Base

SNAPSHOT_PATH = Path("docs/schema-metadata-snapshot.json")


def column_signature(column) -> dict[str, Any]:
    return {
        "name": column.name,
        "type": str(column.type),
        "nullable": column.nullable,
        "primary_key": column.primary_key,
        "unique": bool(column.unique),
        "index": bool(column.index),
    }


def index_signature(index) -> dict[str, Any]:
    return {
        "name": index.name,
        "columns": [column.name for column in index.columns],
        "unique": index.unique,
    }


def metadata_signature() -> dict[str, Any]:
    tables: dict[str, Any] = {}
    for table_name in sorted(Base.metadata.tables):
        table = Base.metadata.tables[table_name]
        tables[table_name] = {
            "columns": [column_signature(column) for column in table.columns],
            "indexes": sorted(
                [index_signature(index) for index in table.indexes],
                key=lambda item: item["name"] or "",
            ),
            "primary_key": [column.name for column in table.primary_key.columns],
        }
    return {"tables": tables}


def write_snapshot(path: Path) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(metadata_signature(), indent=2, sort_keys=True) + "\n", encoding="utf-8")


def check_snapshot(path: Path) -> int:
    expected = json.loads(path.read_text(encoding="utf-8"))
    actual = metadata_signature()
    if actual == expected:
        return 0

    print(
        "SQLAlchemy metadata drift detected. "
        "If this schema change is intentional, add an Alembic revision and refresh "
        f"{path} with `python scripts/check_schema_metadata.py --write`."
    )
    expected_tables = set(expected.get("tables", {}))
    actual_tables = set(actual.get("tables", {}))
    if expected_tables != actual_tables:
        print(
            f"Table diff: missing={sorted(expected_tables - actual_tables)} added={sorted(actual_tables - expected_tables)}"
        )

    for table_name in sorted(expected_tables & actual_tables):
        if expected["tables"][table_name] != actual["tables"][table_name]:
            print(f"Changed table: {table_name}")
    return 1


def main() -> int:
    parser = argparse.ArgumentParser(description="Check SQLAlchemy metadata drift against committed snapshot.")
    parser.add_argument("--write", action="store_true", help="Refresh the committed metadata snapshot.")
    parser.add_argument("--snapshot", type=Path, default=SNAPSHOT_PATH)
    args = parser.parse_args()

    if args.write:
        write_snapshot(args.snapshot)
        return 0
    return check_snapshot(args.snapshot)


if __name__ == "__main__":
    raise SystemExit(main())
