"""D6 T0 — entity_canon.metadata_json column 검증.

spec: docs/superpowers/specs/2026-05-09-deterministic-bg-id-and-catalog-lineage.md §4.3
plan: docs/superpowers/plans/2026-05-09-d6-deterministic-bg-id-and-catalog-lineage-implementation.md T0

R2 B5 결정:
- alembic-only ownership (database.py `_migrations` block 미추가).
- sequential prefix `007_d6_entity_canon_metadata_json` + 006 inspector idempotent 패턴.
- D6 prerequisite — EntityCanon.stable_traits 의 list 계약 보존을 위해
  별도 metadata_json column 추가. location entity 의 space_profile 저장.
"""
from __future__ import annotations

import json
import os
import uuid
from datetime import datetime, timezone

import pytest
from sqlalchemy import inspect, text


# tests/conftest.py 의 session-scope `_ensure_d6_test_db_schema` 가 schema 를
# 보장하지만, 같은 세션에서 `safe_drop_all` 을 쓰는 다른 file (예: test_project_
# export_import.py) 이 본 module 보다 먼저 돌면 entity_canon table 자체가 사라진
# 상태로 inspect-only test 진입 가능. module-scope autouse 로 본 module 진입
# 직전 schema 재보장 — defensive (review iter2 I1 cross-order).
@pytest.fixture(scope="module", autouse=True)
def _module_level_d6_schema_fence():
    from sqlalchemy import create_engine
    from sqlalchemy.exc import OperationalError, ProgrammingError
    from app.core.database import Base, engine as _engine
    import app.models.catalog  # noqa: F401
    import app.models.project  # noqa: F401
    Base.metadata.create_all(_engine, checkfirst=True)
    with _engine.connect() as conn:
        try:
            conn.execute(text(
                "ALTER TABLE entity_canon ADD COLUMN IF NOT EXISTS "
                "metadata_json TEXT NOT NULL DEFAULT '{}'"
            ))
            conn.commit()
        except (ProgrammingError, OperationalError) as exc:
            raise RuntimeError(
                f"_module_level_d6_schema_fence: ALTER failed: {exc!r}"
            ) from exc
    yield


@pytest.fixture
def project_seeded_session(tmp_path, monkeypatch):
    """test DB(`theroad_test`)에 fresh user/project row seed (FK 충족) + rollback.

    yield: (session, project_id)

    `db_session_with_project` (test_entity_canon_short_id_swap.py:25) 와 동일
    패턴이지만 episode 미필요 — entity_canon 만 만든다.
    """
    monkeypatch.setattr("app.core.config.settings.projects_dir", str(tmp_path))

    from app.models.catalog import UserAccount, ProjectRegistry  # noqa: F401
    from app.models.project import EntityCanon  # noqa: F401
    from app.core.database import SessionLocal, init_db

    init_db()
    session = SessionLocal()
    try:
        now = datetime.now(timezone.utc).isoformat()
        uid = f"test-user-{uuid.uuid4()}"
        pid = f"test-d6-{uuid.uuid4()}"

        session.add(UserAccount(
            id=uid, username=f"u_{uid}", display_name="t",
            password_hash="x", role="creator", is_active=1,
            created_at=now, updated_at=now,
        ))
        session.flush()
        session.add(ProjectRegistry(
            id=pid, name="d6-mig-test", description="",
            created_by=uid, created_at=now, updated_at=now,
        ))
        session.flush()
        yield session, pid
    finally:
        session.rollback()
        session.close()


def test_entity_canon_has_metadata_json_column():
    """entity_canon 테이블에 metadata_json column 존재."""
    from app.core.database import engine

    insp = inspect(engine)
    cols = {c["name"] for c in insp.get_columns("entity_canon")}
    assert "metadata_json" in cols, (
        "entity_canon.metadata_json column 누락 — alembic 007 미실행 또는 model 미반영. "
        "회피: `cd backend && .venv/bin/alembic upgrade head`."
    )


def test_metadata_json_column_text_not_null():
    """metadata_json: TEXT NOT NULL DEFAULT '{}' (server-side)."""
    from app.core.database import engine

    insp = inspect(engine)
    col = next(
        (c for c in insp.get_columns("entity_canon") if c["name"] == "metadata_json"),
        None,
    )
    assert col is not None, "metadata_json column 누락"
    assert col["nullable"] is False, (
        "metadata_json must be NOT NULL (server_default '{}'). "
        f"Got nullable={col['nullable']}"
    )
    # Postgres TEXT type
    assert "TEXT" in str(col["type"]).upper(), (
        f"metadata_json type expected TEXT, got {col['type']!r}"
    )


def test_metadata_json_default_empty_dict_via_orm(project_seeded_session):
    """ORM EntityCanon insert 시 metadata_json 미지정 → server_default '{}'."""
    from app.models.project import EntityCanon

    db_session, pid = project_seeded_session
    now = datetime.now(timezone.utc).isoformat()

    ent = EntityCanon(
        id=f"ent-d6-mig-{uuid.uuid4().hex[:8]}",
        project_id=pid,
        short_id="L99",
        entity_type="location",
        name="d6_mig_test_loc",
        created_at=now,
        updated_at=now,
    )
    db_session.add(ent)
    db_session.commit()
    db_session.refresh(ent)

    assert ent.metadata_json is not None, "metadata_json fell through to NULL"
    assert json.loads(ent.metadata_json) == {}, (
        f"server_default expected '{{}}', got {ent.metadata_json!r}"
    )


def test_metadata_json_round_trip_location_space_profile(project_seeded_session):
    """metadata_json 에 location.space_profile dict 저장 + 재로드 OK."""
    from app.models.project import EntityCanon

    db_session, pid = project_seeded_session
    now = datetime.now(timezone.utc).isoformat()

    payload = {
        "location": {
            "space_profile": {
                "kind": "multi_space",
                "allowed_space_keys": ["main", "kitchen", "rooftop"],
                "default_space_key": "main",
            }
        }
    }
    ent = EntityCanon(
        id=f"ent-d6-rt-{uuid.uuid4().hex[:8]}",
        project_id=pid,
        short_id="L98",
        entity_type="location",
        name="d6_mig_test_rt_loc",
        metadata_json=json.dumps(payload),
        created_at=now,
        updated_at=now,
    )
    db_session.add(ent)
    db_session.commit()
    db_session.refresh(ent)

    parsed = json.loads(ent.metadata_json)
    assert parsed == payload
    assert parsed["location"]["space_profile"]["kind"] == "multi_space"
    assert "kitchen" in parsed["location"]["space_profile"]["allowed_space_keys"]
