"""프로젝트 JSON 내보내기 / 가져오기 API 테스트."""

import shutil
from pathlib import Path

import pytest
from fastapi.testclient import TestClient

from app.core.config import settings
from app.core.database import Base, engine
from app.main import app
from tests._safety_guards import safe_drop_all, safe_rmtree


@pytest.fixture(autouse=True)
def _setup_db():
    Base.metadata.create_all(engine)
    with TestClient(app):
        pass
    yield
    safe_drop_all(engine, Base.metadata)
    proj_dir = Path(settings.projects_dir)
    if proj_dir.exists():
        safe_rmtree(proj_dir)


@pytest.fixture()
def client():
    with TestClient(app, raise_server_exceptions=False) as c:
        yield c


# ── helpers ──────────────────────────────────────────────────────────────────

def _admin_login(client: TestClient):
    resp = client.post("/api/v1/auth/login", json={"username": "admin", "password": "admin123"})
    assert resp.status_code == 200


def _create_project(client: TestClient, name: str = "Export Test") -> str:
    _admin_login(client)
    resp = client.post("/api/v1/projects/", json={"name": name, "description": "desc"})
    assert resp.status_code == 200
    return resp.json()["id"]


# ── export tests ─────────────────────────────────────────────────────────────

def test_export_returns_valid_json(client: TestClient):
    """GET /export returns a dict with all expected top-level keys."""
    project_id = _create_project(client)

    resp = client.get(f"/api/v1/projects/{project_id}/export")
    assert resp.status_code == 200, resp.text

    data = resp.json()

    expected_keys = {
        "export_version",
        "exported_at",
        "project",
        "members",
        "episodes",
        "entities",
        "entity_aliases",
        "relations",
        "relation_participants",
        "scene_stills",
        "entity_episode_links",
        "images",
        "world_guides",
        "webbook_packages",
        "generation_traces",
        "operation_logs",
    }
    assert expected_keys.issubset(data.keys()), f"Missing keys: {expected_keys - data.keys()}"


def test_export_project_fields(client: TestClient):
    """Exported project dict contains name and id."""
    project_id = _create_project(client, name="My Film")

    resp = client.get(f"/api/v1/projects/{project_id}/export")
    assert resp.status_code == 200

    data = resp.json()
    assert data["project"]["id"] == project_id
    assert data["project"]["name"] == "My Film"
    assert data["export_version"] == "1.0"


def test_export_includes_member(client: TestClient):
    """Members list includes the project owner."""
    project_id = _create_project(client)

    resp = client.get(f"/api/v1/projects/{project_id}/export")
    assert resp.status_code == 200

    data = resp.json()
    assert len(data["members"]) >= 1
    roles = [m["role"] for m in data["members"]]
    assert "owner" in roles


def test_export_lists_are_serialisable(client: TestClient):
    """All list fields are lists (not None)."""
    project_id = _create_project(client)

    resp = client.get(f"/api/v1/projects/{project_id}/export")
    assert resp.status_code == 200

    data = resp.json()
    list_fields = [
        "members", "episodes", "entities", "entity_aliases",
        "relations", "relation_participants", "scene_stills",
        "entity_episode_links", "images", "world_guides",
        "webbook_packages", "generation_traces", "operation_logs",
    ]
    for field in list_fields:
        assert isinstance(data[field], list), f"{field} should be a list"


def test_export_requires_auth(client: TestClient):
    """Export endpoint returns 401 when not logged in."""
    # Create a project while logged in, then log out
    project_id = _create_project(client)
    client.post("/api/v1/auth/logout")

    resp = client.get(f"/api/v1/projects/{project_id}/export")
    assert resp.status_code == 401


def test_export_nonexistent_project(client: TestClient):
    """Export of a non-existent project returns 404."""
    _admin_login(client)
    resp = client.get("/api/v1/projects/00000000-0000-0000-0000-000000000000/export")
    assert resp.status_code == 404


# ── import tests ─────────────────────────────────────────────────────────────

def test_import_creates_new_project(client: TestClient):
    """Importing exported JSON creates a brand-new project with a different ID."""
    original_id = _create_project(client, name="Source Project")

    # export
    export_resp = client.get(f"/api/v1/projects/{original_id}/export")
    assert export_resp.status_code == 200
    exported = export_resp.json()

    # import
    import_resp = client.post("/api/v1/projects/import", json={"data": exported})
    assert import_resp.status_code == 200, import_resp.text

    result = import_resp.json()
    assert "project_id" in result
    new_id = result["project_id"]

    # must be a different project
    assert new_id != original_id

    # verify new project exists
    get_resp = client.get(f"/api/v1/projects/{new_id}")
    assert get_resp.status_code == 200
    assert get_resp.json()["name"] == "Source Project"


def test_import_with_new_name(client: TestClient):
    """Import with new_name overrides the project name."""
    original_id = _create_project(client, name="Original Name")

    export_resp = client.get(f"/api/v1/projects/{original_id}/export")
    assert export_resp.status_code == 200
    exported = export_resp.json()

    import_resp = client.post(
        "/api/v1/projects/import",
        json={"data": exported, "new_name": "Imported Copy"},
    )
    assert import_resp.status_code == 200

    new_id = import_resp.json()["project_id"]
    get_resp = client.get(f"/api/v1/projects/{new_id}")
    assert get_resp.status_code == 200
    assert get_resp.json()["name"] == "Imported Copy"


def test_import_remaps_ids(client: TestClient):
    """Import round-trip produces new IDs — the project IDs must differ."""
    original_id = _create_project(client, name="Remap Test")

    export_resp = client.get(f"/api/v1/projects/{original_id}/export")
    assert export_resp.status_code == 200
    exported = export_resp.json()

    # import twice → two distinct new projects
    resp1 = client.post("/api/v1/projects/import", json={"data": exported})
    resp2 = client.post("/api/v1/projects/import", json={"data": exported})
    assert resp1.status_code == 200
    assert resp2.status_code == 200

    id1 = resp1.json()["project_id"]
    id2 = resp2.json()["project_id"]

    assert id1 != original_id
    assert id2 != original_id
    assert id1 != id2


def test_import_invalid_format(client: TestClient):
    """POST /import with missing required keys returns 400."""
    _admin_login(client)
    resp = client.post("/api/v1/projects/import", json={"data": {"not_a_real_key": True}})
    assert resp.status_code == 400
    assert resp.json()["error"]["code"] == "import.invalid_format"


def test_import_requires_auth(client: TestClient):
    """Import endpoint returns 401 when not logged in."""
    # need to be logged out
    _admin_login(client)
    project_id = _create_project(client)
    export_resp = client.get(f"/api/v1/projects/{project_id}/export")
    exported = export_resp.json()
    client.post("/api/v1/auth/logout")

    resp = client.post("/api/v1/projects/import", json={"data": exported})
    assert resp.status_code == 401


def test_export_import_round_trip_data_integrity(client: TestClient):
    """After import, the project name and member count are preserved."""
    original_id = _create_project(client, name="Integrity Check")

    export_resp = client.get(f"/api/v1/projects/{original_id}/export")
    assert export_resp.status_code == 200
    exported = export_resp.json()

    original_member_count = len(exported["members"])

    import_resp = client.post("/api/v1/projects/import", json={"data": exported})
    assert import_resp.status_code == 200
    new_id = import_resp.json()["project_id"]

    # check via members endpoint — importing user becomes owner
    members_resp = client.get(f"/api/v1/projects/{new_id}/members")
    assert members_resp.status_code == 200
    # At minimum the importing admin is present as owner
    assert len(members_resp.json()) >= 1
    # Project name preserved
    get_resp = client.get(f"/api/v1/projects/{new_id}")
    assert get_resp.json()["name"] == "Integrity Check"


def test_export_import_round_trip_entity_canon_metadata_json(client: TestClient):
    """D6 review I2: EntityCanon.metadata_json round-trip preserved.

    `_row_to_dict` (export) 는 새 column 자동 포함하지만 import side 의 EntityCanon(...)
    constructor 는 명시적 keyword 만 받음. metadata_json 누락 시 import 후 column
    이 server_default '{}' 로 reset → space_profile 손실.

    short_id / t2i_prompt 도 같은 desync 카테고리 — 함께 보존 검증.
    """
    import json
    import uuid as _uuid
    from datetime import datetime, timezone
    from app.core.database import SessionLocal
    from app.models.project import EntityCanon

    original_id = _create_project(client, name="D6 metadata_json Round-Trip")

    # 직접 EntityCanon insert — admin 권한으로 우회 필요 X (DB 직접 쓰기).
    payload = {
        "location": {
            "space_profile": {
                "kind": "multi_space",
                "allowed_space_keys": ["main", "kitchen", "rooftop"],
                "default_space_key": "main",
            }
        }
    }
    entity_id = "ent-d6-rt-" + _uuid.uuid4().hex[:8]
    now = datetime.now(timezone.utc).isoformat()
    db = SessionLocal()
    try:
        db.add(EntityCanon(
            id=entity_id,
            project_id=original_id,
            short_id="L09",
            entity_type="location",
            name="d6_rt_location",
            description="d6 round-trip test loc",
            stable_traits='["bright", "indoor"]',
            metadata_json=json.dumps(payload),
            t2i_prompt="bright indoor location",
            status="active",
            created_at=now,
            updated_at=now,
        ))
        db.commit()
    finally:
        db.close()

    # export
    export_resp = client.get(f"/api/v1/projects/{original_id}/export")
    assert export_resp.status_code == 200
    exported = export_resp.json()

    # export payload 안에 metadata_json 노출 확인
    entities = exported.get("entities", [])
    assert any(e.get("id") == entity_id for e in entities), (
        f"export 에 entity {entity_id} 누락"
    )
    src_ent = next(e for e in entities if e["id"] == entity_id)
    assert "metadata_json" in src_ent, "_row_to_dict 가 metadata_json 누락"
    assert json.loads(src_ent["metadata_json"]) == payload

    # import (id remap → 다른 entity_id)
    import_resp = client.post("/api/v1/projects/import", json={"data": exported})
    assert import_resp.status_code == 200
    new_pid = import_resp.json()["project_id"]

    # 새 project 의 entity_canon row 에서 metadata_json 보존 확인
    db = SessionLocal()
    try:
        ents = db.query(EntityCanon).filter_by(project_id=new_pid).all()
        loc = next((e for e in ents if e.short_id == "L09"), None)
        assert loc is not None, "import 후 L09 location entity 누락"
        assert loc.metadata_json is not None and loc.metadata_json != "{}", (
            f"D6 review I2 회귀: metadata_json 손실 — {loc.metadata_json!r}"
        )
        parsed = json.loads(loc.metadata_json)
        assert parsed == payload, f"round-trip drift: {parsed} != {payload}"
        # 함께 desync 카테고리: short_id / t2i_prompt 도 보존
        assert loc.short_id == "L09"
        assert loc.t2i_prompt == "bright indoor location"
    finally:
        db.close()
