"""에피소드 관리 API 라우터."""

import asyncio
import json

from fastapi import APIRouter, Depends, Request, UploadFile, File, Form
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session as OrmSession

from app.api.deps import get_db, get_current_user, verify_project_access
from app.core.config import settings
from app.core.database import SessionLocal
from app.core.errors import AppError
from app.core.job_manager import submit_background_job
from app.i18n.loader import t
from app.models.catalog import UserAccount
from app.models.project import Episode, PipelineProgress
from app.schemas.episode import EpisodeCreate, EpisodeUpdate, EpisodeResponse
from app.services.episode_service import EpisodeService
from app.services.analysis_service import AnalysisService

router = APIRouter(
    prefix="/api/v1/projects/{project_id}/episodes",
    tags=["episodes"],
)


def _make_service(
    project_id: str,
    db: OrmSession,
    user: UserAccount,
) -> EpisodeService:
    return EpisodeService(
        db=db,
        project_id=project_id,
        actor_id=user.id,
    )


@router.get("/", response_model=list[EpisodeResponse])
def list_episodes(
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    service = _make_service(project_id, db, current_user)
    return service.list_episodes()


@router.post("/", response_model=EpisodeResponse)
async def create_episode(
    episode_number: int = Form(...),
    title: str = Form(...),
    file: UploadFile = File(...),
    request: Request = None,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    ip = request.client.host if request and request.client else None
    pdf_bytes = await file.read()
    filename = file.filename or "screenplay.pdf"
    service = _make_service(project_id, db, current_user)
    return service.create_episode(episode_number, title, pdf_bytes, filename, ip)


@router.get("/{episode_id}", response_model=EpisodeResponse)
def get_episode(
    episode_id: str,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    service = _make_service(project_id, db, current_user)
    return service.get_episode(episode_id)


@router.get("/{episode_id}/fulltext")
def get_episode_fulltext(
    episode_id: str,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    """에피소드 시나리오 전문 반환 (씬 세그먼트 표시용)."""
    from app.core.config import settings
    episode = db.query(Episode).filter(Episode.id == episode_id, Episode.project_id == project_id).first()
    if not episode:
        raise AppError(code="episode.not_found", message=t("episode.not_found"), status_code=404)
    return {
        "fulltext": episode.fulltext or "",
        "segment_context_chars": settings.scene_segment_context_chars,
    }


@router.patch("/{episode_id}", response_model=EpisodeResponse)
def update_episode(
    episode_id: str,
    body: EpisodeUpdate,
    request: Request,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    ip = request.client.host if request.client else None
    updates = body.model_dump(exclude_none=True)
    service = _make_service(project_id, db, current_user)
    return service.update_episode(episode_id, updates, ip)


@router.delete("/{episode_id}")
def delete_episode(
    episode_id: str,
    request: Request,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    ip = request.client.host if request.client else None
    service = _make_service(project_id, db, current_user)
    service.delete_episode(episode_id, ip)
    return {"ok": True}


def _run_analysis_in_background(
    project_id: str,
    actor_id: str,
    episode_id: str,
    ip: str | None,
) -> None:
    """Run analysis in a background thread with its own DB session."""
    db = SessionLocal()
    try:
        svc = AnalysisService(
            db=db,
            project_id=project_id,
            actor_id=actor_id,
        )
        svc.run_analysis(episode_id, ip=ip)
    finally:
        db.close()


@router.post("/{episode_id}/analyze")
def analyze_episode(
    episode_id: str,
    request: Request,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    """Start LLM analysis for an episode (background thread)."""
    ip = request.client.host if request.client else None

    # Lock the row to prevent race condition between concurrent requests
    episode = (
        db.query(Episode)
        .filter(Episode.id == episode_id, Episode.project_id == project_id)
        .with_for_update()
        .first()
    )
    if not episode:
        raise AppError(
            code="episode.not_found",
            message=t("episode.not_found"),
            status_code=404,
        )
    if episode.status == "analyzing":
        raise AppError(
            code="analysis.already_running",
            message=t("analysis.already_running"),
            status_code=409,
        )
    if not episode.fulltext:
        raise AppError(
            code="analysis.no_text",
            message=t("analysis.no_text"),
            status_code=400,
        )
    if not settings.openai_api_key:
        raise AppError(
            code="analysis.openai_key_missing",
            message=t("analysis.openai_key_missing"),
            status_code=400,
        )

    # Set status to "analyzing" immediately to close the race window
    episode.status = "analyzing"
    episode.analysis_error = None
    db.commit()

    started = submit_background_job(
        job_key=f"analyze:{episode_id}",
        target=_run_analysis_in_background,
        args=(project_id, current_user.id, episode_id, ip),
        description=f"Analysis for episode {episode_id}",
    )
    if not started:
        raise AppError(code="analysis.already_running", message=t("analysis.already_running"), status_code=409)

    return {"ok": True, "message": t("analysis.started")}


@router.post("/{episode_id}/reanalyze-scenes")
def reanalyze_scenes(
    episode_id: str,
    request: Request,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    """씬만 재분석 (요소 유지)."""
    ip = request.client.host if request.client else None
    # Lock the row to prevent race condition between concurrent requests
    episode = (
        db.query(Episode)
        .filter(Episode.id == episode_id, Episode.project_id == project_id)
        .with_for_update()
        .first()
    )
    if not episode:
        raise AppError(code="episode.not_found", message=t("episode.not_found"), status_code=404)
    if episode.status == "analyzing":
        raise AppError(code="analysis.already_running", message=t("analysis.already_running"), status_code=409)
    if not episode.fulltext:
        raise AppError(code="analysis.no_text", message=t("analysis.no_text"), status_code=400)

    # Set status to "analyzing" immediately to close the race window
    episode.status = "analyzing"
    episode.analysis_error = None
    db.commit()

    def _run_reanalyze(pid, actor_id, ep_id, ip_addr):
        db2 = SessionLocal()
        try:
            from app.services.analysis_service import AnalysisService
            svc = AnalysisService(db=db2, project_id=pid, actor_id=actor_id)
            svc.reanalyze_scenes(ep_id, ip=ip_addr)
        finally:
            db2.close()

    started = submit_background_job(
        job_key=f"reanalyze:{episode_id}",
        target=_run_reanalyze,
        args=(project_id, current_user.id, episode_id, ip),
        description=f"Scene reanalysis for episode {episode_id}",
    )
    if not started:
        raise AppError(code="analysis.already_running", message=t("analysis.already_running"), status_code=409)
    return {"ok": True, "message": "씬 재분석이 시작되었습니다"}


@router.get("/{episode_id}/segment-preview")
def segment_preview(
    episode_id: str,
    threshold: int = 600,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    """정규식 세그먼테이션 미리보기 — threshold별 예상 씬 수."""
    episode = db.query(Episode).filter(Episode.id == episode_id, Episode.project_id == project_id).first()
    if not episode or not episode.fulltext:
        return {"base_scenes": 0, "split_candidates": 0, "estimated_total": 0, "scenes": []}

    from app.modules.pipeline.scene_extractor_v2 import _segment_scenes
    segments = _segment_scenes(episode.fulltext, split_threshold=threshold)
    long_scenes = [s for s in segments if s["length"] >= threshold]

    return {
        "base_scenes": len(segments),
        "split_candidates": len(long_scenes),
        "estimated_total": len(segments),
        "threshold": threshold,
        "scenes": [
            {
                "index": s["scene_index"],
                "heading": s["heading"],
                "length": s["length"],
                "will_split": s["length"] >= threshold,
            }
            for s in segments
        ],
    }


_OPERATION_KEYS = ("analysis", "reference_image_generation", "image_generation", "webbook", "pdf_render")


@router.get("/{episode_id}/progress")
def get_progress(
    episode_id: str,
    project_id: str = Depends(verify_project_access),
    db: OrmSession = Depends(get_db),
    current_user: UserAccount = Depends(get_current_user),
):
    """Get pipeline progress for an episode."""
    all_progress = (
        db.query(PipelineProgress)
        .filter(
            PipelineProgress.project_id == project_id,
            PipelineProgress.episode_id == episode_id,
        )
        .all()
    )

    def _format_progress(row):
        return {
            "status": row.status,
            "current_step": row.current_step or "",
            "completed_steps": row.completed_steps or 0,
            "total_steps": row.total_steps or 0,
            "error_message": row.error_message,
            "started_at": row.started_at,
            "updated_at": row.updated_at,
            "completed_at": row.completed_at,
        }

    result = {}
    for key in _OPERATION_KEYS:
        # Pick the most recently started row for each operation
        matches = [p for p in all_progress if p.operation == key]
        if matches:
            best = max(matches, key=lambda p: p.started_at or p.updated_at)
            result[key] = _format_progress(best)
        else:
            result[key] = None
    return result


@router.get("/{episode_id}/progress/stream")
async def stream_progress(
    episode_id: str,
    project_id: str = Depends(verify_project_access),
    current_user: UserAccount = Depends(get_current_user),
):
    """SSE endpoint for real-time progress updates."""

    async def event_stream():
        db = SessionLocal()
        try:
            while True:
                progress_rows = (
                    db.query(PipelineProgress)
                    .filter(
                        PipelineProgress.project_id == project_id,
                        PipelineProgress.episode_id == episode_id,
                    )
                    .all()
                )

                data = {}
                for key in _OPERATION_KEYS:
                    matches = [p for p in progress_rows if p.operation == key]
                    if matches:
                        best = max(matches, key=lambda p: p.started_at or p.updated_at)
                        data[key] = {
                            "status": best.status,
                            "current_step": best.current_step or "",
                            "completed_steps": best.completed_steps or 0,
                            "total_steps": best.total_steps or 0,
                        }

                yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"

                if (
                    progress_rows
                    and all(p.status in ("completed", "error") for p in progress_rows)
                ):
                    yield 'data: {"done": true}\n\n'
                    break

                db.expire_all()
                await asyncio.sleep(2)
        finally:
            db.close()

    return StreamingResponse(
        event_stream(),
        media_type="text/event-stream",
        headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
    )
