"""EpisodeProjectionService — t2i_appearance_count selected-shot 정정 테스트.

Phase1 BLOCKING2: sync_t2i_appearance_counts 가 selected/non-stale/still_index>=0
still 에만 의존해야 함. stale still 또는 is_selected=False still 의 t2i_prompt 는
카운트에 기여하면 안 된다.

DB 패턴: test_entity_canon_short_id_swap.py 의 db_session_with_project fixture 를
그대로 복사 (SessionLocal + init_db + UserAccount/ProjectRegistry/Episode seed + rollback).
"""
from __future__ import annotations

import json
import uuid
from datetime import datetime, timezone

import pytest


# ---------------------------------------------------------------------------
# Fixture — real DB session (theroad_test PG) with seeded project/episode rows
# ---------------------------------------------------------------------------

@pytest.fixture
def db_session_with_project(tmp_path, monkeypatch):
    """conftest.py 가 격리한 theroad_test DB + fresh project/episode seed + rollback.

    Copied verbatim from tests/services/test_entity_canon_short_id_swap.py.
    yield: (session, project_id, episode_id, tmp_path)
    """
    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, Episode  # 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-eproj-{uuid.uuid4()}"
        eid = f"test-eproj-ep-{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="eproj-test", description="",
            created_by=uid, created_at=now, updated_at=now,
        ))
        session.flush()
        session.add(Episode(
            id=eid, project_id=pid, episode_number=1,
            title="t", source_filename="f.txt", source_path="/tmp/f.txt",
            language="en", status="uploaded", created_at=now, updated_at=now,
        ))
        session.flush()
        yield session, pid, eid, tmp_path
    finally:
        session.rollback()
        session.close()


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------

def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _add_still(session, *, project_id, episode_id, still_index,
               is_selected, status, t2i_prompt_text):
    """Create a SceneStill row whose t2i_variations_json contains t2i_prompt_text."""
    from app.models.project import SceneStill

    variations = json.dumps([{"theme": "base", "theme_label": "Base", "t2i_prompt": t2i_prompt_text}])
    row = SceneStill(
        id=str(uuid.uuid4()),
        project_id=project_id,
        episode_id=episode_id,
        still_index=still_index,
        is_selected=is_selected,
        status=status,
        t2i_variations_json=variations,
        created_at=_now(),
    )
    session.add(row)
    return row


def _add_entity_with_link(session, *, project_id, episode_id, short_id):
    """Create EntityCanon + EntityEpisodeLink for the given short_id."""
    from app.models.project import EntityCanon, EntityEpisodeLink

    canon_id = str(uuid.uuid4())
    canon = EntityCanon(
        id=canon_id,
        project_id=project_id,
        short_id=short_id,
        entity_type="character",
        name=f"Entity_{short_id}",
        description="",
        stable_traits="{}",
        created_at=_now(),
        updated_at=_now(),
    )
    session.add(canon)
    session.flush()

    link = EntityEpisodeLink(
        id=str(uuid.uuid4()),
        project_id=project_id,
        canon_id=canon_id,
        episode_id=episode_id,
        source="extracted",
        t2i_appearance_count=0,
    )
    session.add(link)
    session.flush()
    return canon, link


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------

def test_t2i_count_excludes_stale_and_nonselected_stills(db_session_with_project):
    """stale / is_selected=False / still_index<0 SceneStill 의 t2i_prompt 는
    t2i_appearance_count 에 기여하지 않는다.

    Setup:
      (A) is_selected=True,  status="ok",    still_index=0  → C01 → COUNTED
      (B) is_selected=False, status="ok",    still_index=1  → C01 → NOT counted
      (C) is_selected=True,  status="stale", still_index=2  → C01 → NOT counted

    Expected: C01.t2i_appearance_count == 1 (only A)
    """
    session, pid, eid, _ = db_session_with_project

    # (A) selected + ok + idx 0 — should be counted
    _add_still(session, project_id=pid, episode_id=eid,
               still_index=0, is_selected=True, status="ok",
               t2i_prompt_text="C01 stands in the doorway")
    # (B) not selected + ok + idx 1 — should NOT be counted
    _add_still(session, project_id=pid, episode_id=eid,
               still_index=1, is_selected=False, status="ok",
               t2i_prompt_text="C01 reaches for the handle")
    # (C) selected + stale + idx 2 — should NOT be counted
    _add_still(session, project_id=pid, episode_id=eid,
               still_index=2, is_selected=True, status="stale",
               t2i_prompt_text="C01 runs across the field")
    session.flush()

    canon, link = _add_entity_with_link(session, project_id=pid, episode_id=eid, short_id="C01")

    from app.services.checkpoint_sync.episode_projection_service import EpisodeProjectionService

    svc = EpisodeProjectionService(session, pid, eid)
    svc.sync_t2i_appearance_counts(commit=False)

    # commit=False → count is updated in-memory on the ORM object.
    # Do NOT refresh (that would re-read from DB, overwriting in-memory state).
    assert link.t2i_appearance_count == 1, (
        f"Expected C01.t2i_appearance_count==1 (only selected+ok+idx0 still), "
        f"got {link.t2i_appearance_count}"
    )


def test_t2i_count_counts_all_selected_ok_stills(db_session_with_project):
    """Multiple selected+ok stills each referencing C01 all get counted."""
    session, pid, eid, _ = db_session_with_project

    for idx in range(3):
        _add_still(session, project_id=pid, episode_id=eid,
                   still_index=idx, is_selected=True, status="ok",
                   t2i_prompt_text="C01 is visible")
    session.flush()

    canon, link = _add_entity_with_link(session, project_id=pid, episode_id=eid, short_id="C01")

    from app.services.checkpoint_sync.episode_projection_service import EpisodeProjectionService

    svc = EpisodeProjectionService(session, pid, eid)
    svc.sync_t2i_appearance_counts(commit=False)

    # commit=False → count is updated in-memory on the ORM object.
    assert link.t2i_appearance_count == 3
