import json

from fastapi import APIRouter, HTTPException
from services.memory import (
    get_all_sessions,
    get_session,
    get_or_create_session,
    get_history,
    delete_session,
    update_session_title,
    update_session_persona,
    get_message_count,
    update_session_rpg,
    update_session_facts,
    update_session_global_plot,
    update_session_status_quo,
    update_session_genre,
    update_session_rpg_settings,
    get_quests,
    update_quest_by_id,
    set_session_affinity,
    update_session_narrative_stats,
    update_session_outfit,
    update_session_scene,
    update_session_plot_arc,
    get_last_message_preview,
    fork_session,
)
from models.schemas import (
    ForkSessionRequest,
    RebindPersonaRequest,
    QuestStatusPatch,
    RpgStateDebugPatch,
    SessionContextPatch,
)
from services.rpg_plot import reconcile_plot_arc
from services.rpg_state import parse_stats_json
from services.chat_prompt import get_system_prompt
from services.memory import rebind_session_persona
from services.personas import get_persona

router = APIRouter(prefix="/sessions", tags=["sessions"])


@router.get("/")
async def list_sessions():
    sessions = await get_all_sessions()
    result = []
    for s in sessions:
        count = await get_message_count(s["session_id"])
        preview = await get_last_message_preview(s["session_id"])
        result.append({**s, "message_count": count, "last_message_preview": preview})
    return result


@router.get("/{session_id}/quests")
async def list_quests(session_id: str):
    session = await get_session(session_id)
    if session and session.get("rpg_enabled"):
        persona = await get_persona(session.get("persona_id") or "default") or {}
        await reconcile_plot_arc(
            session_id,
            persona_name=persona.get("name", session.get("persona_id") or "Character"),
            recent_context=(session.get("status_quo") or "")[:2000],
            genre=session.get("genre") or "adventure",
        )
    return await get_quests(session_id)


@router.patch("/{session_id}/context")
async def patch_session_context(session_id: str, body: SessionContextPatch):
    """Live-edit session context (outfit, scene, plot, facts, status quo)."""
    from services.outfit_tags import parse_and_normalize_outfit_json

    session = await get_session(session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Сессия не найдена")

    if body.status_quo is not None:
        await update_session_status_quo(session_id, body.status_quo)
    if body.global_plot is not None:
        await update_session_global_plot(session_id, body.global_plot)
    if body.outfit_json is not None:
        try:
            normalized = parse_and_normalize_outfit_json(body.outfit_json)
            json.loads(normalized)
        except json.JSONDecodeError as e:
            raise HTTPException(status_code=400, detail=f"outfit_json: {e}") from e
        await update_session_outfit(session_id, normalized)
    if body.scene_json is not None:
        try:
            json.loads(body.scene_json or "{}")
        except json.JSONDecodeError as e:
            raise HTTPException(status_code=400, detail=f"scene_json: {e}") from e
        await update_session_scene(session_id, body.scene_json)
    if body.facts_json is not None:
        from services.rpg_facts import (
            parse_facts_list,
            facts_list_to_json,
            dedupe_facts_fuzzy,
            compress_facts,
            FACTS_DEDUP_THRESHOLD,
            FACTS_COMPRESS_TARGET,
        )

        try:
            facts = dedupe_facts_fuzzy(parse_facts_list(body.facts_json))
        except json.JSONDecodeError as e:
            raise HTTPException(status_code=400, detail=f"facts_json: {e}") from e
        if len(facts) > FACTS_DEDUP_THRESHOLD:
            facts = await compress_facts(
                facts,
                status_quo=(session.get("status_quo") or ""),
                scene_context=session.get("scene_json") or "{}",
                target=FACTS_COMPRESS_TARGET,
            )
            facts = dedupe_facts_fuzzy(facts)
        normalized = facts_list_to_json(facts)
        await update_session_facts(session_id, normalized)
    if body.plot_arc_json is not None:
        try:
            json.loads(body.plot_arc_json or "{}")
        except json.JSONDecodeError as e:
            raise HTTPException(status_code=400, detail=f"plot_arc_json: {e}") from e
        await update_session_plot_arc(session_id, body.plot_arc_json)
    if body.affinity is not None:
        await set_session_affinity(session_id, body.affinity)
    stats_changed = any(
        getattr(body, k) is not None for k in ("lust", "stamina", "tension")
    )
    if stats_changed:
        stats = parse_stats_json(session.get("narrative_stats_json"))
        for key in ("lust", "stamina", "tension"):
            val = getattr(body, key, None)
            if val is not None:
                stats[key] = max(0, min(10, int(val)))
        await update_session_narrative_stats(
            session_id, json.dumps(stats, ensure_ascii=False)
        )

    updated = await get_session(session_id) or session
    return {
        "status": "updated",
        "outfit_json": updated.get("outfit_json", "[]"),
        "scene_json": updated.get("scene_json", "{}"),
        "affinity": updated.get("affinity", 0),
        "narrative_stats": parse_stats_json(updated.get("narrative_stats_json")),
    }


@router.patch("/{session_id}/rpg-state")
async def patch_rpg_state(session_id: str, body: RpgStateDebugPatch):
    """Debug: set affinity and/or narrative stats (lust/stamina/tension 0–10)."""
    session = await get_session(session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Сессия не найдена")
    affinity = session.get("affinity", 0)
    if body.affinity is not None:
        affinity = await set_session_affinity(session_id, body.affinity)
    stats = parse_stats_json(session.get("narrative_stats_json"))
    changed_stats = False
    for key in ("lust", "stamina", "tension"):
        val = getattr(body, key, None)
        if val is not None:
            stats[key] = max(0, min(10, int(val)))
            changed_stats = True
    if changed_stats:
        import json as _json

        await update_session_narrative_stats(
            session_id, _json.dumps(stats, ensure_ascii=False)
        )
    return {
        "affinity": affinity,
        "narrative_stats": stats,
        "target": "current_player",
    }


@router.patch("/{session_id}/quests/{quest_id}")
async def patch_quest(session_id: str, quest_id: int, body: QuestStatusPatch):
    status = body.status.strip()
    if status not in ("active", "done", "failed"):
        raise HTTPException(status_code=400, detail="status must be active, done, or failed")
    ok = await update_quest_by_id(quest_id, session_id, status)
    if not ok:
        raise HTTPException(status_code=404, detail="Quest not found")
    if status in ("done", "failed"):
        session = await get_session(session_id)
        if session and session.get("rpg_enabled"):
            persona = await get_persona(session.get("persona_id") or "default") or {}
            await reconcile_plot_arc(
                session_id,
                persona_name=persona.get("name", session.get("persona_id") or "Character"),
                recent_context=(session.get("status_quo") or "")[:2000],
                genre=session.get("genre") or "adventure",
            )
    return {"status": "updated", "quest_id": quest_id, "new_status": status}


@router.get("/{session_id}")
async def get_session_route(session_id: str):
    s = await get_session(session_id)
    if not s:
        raise HTTPException(status_code=404, detail="Сессия не найдена")
    return s


@router.post("/{session_id}/rebind-persona")
async def rebind_persona(session_id: str, body: RebindPersonaRequest):
    session = await get_session(session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Сессия не найдена")
    persona = await get_persona(body.persona_id)
    if not persona:
        raise HTTPException(status_code=400, detail="Персонаж не найден")

    hist = [] if body.clear_history else await get_history(session_id)
    static = await get_system_prompt(body.persona_id, hist, "")
    first_mes = (persona.get("first_mes") or "").strip() if body.clear_history else None

    try:
        await rebind_session_persona(
            session_id,
            body.persona_id,
            clear_history=body.clear_history,
            static_prompt=static,
            first_mes=first_mes or None,
        )
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    return {
        "persona_id": body.persona_id,
        "persona_name": persona.get("name", body.persona_id),
        "system_prompt_preview": static[:500],
        "clear_history": body.clear_history,
    }


@router.patch("/{session_id}")
async def patch_session(session_id: str, data: dict):
    create_pid = data.get("persona_id") if "persona_id" in data else None
    await get_or_create_session(session_id, create_pid)
    if "title" in data:
        await update_session_title(session_id, data["title"])
    if "persona_id" in data:
        await update_session_persona(session_id, data["persona_id"])
    if "rpg_enabled" in data:
        await update_session_rpg(session_id, bool(data["rpg_enabled"]))
    if "facts_json" in data:
        await update_session_facts(session_id, data["facts_json"])
    if "global_plot" in data:
        await update_session_global_plot(session_id, data["global_plot"])
    if "status_quo" in data:
        await update_session_status_quo(session_id, data["status_quo"])
    if "genre" in data:
        await update_session_genre(session_id, data["genre"])
    if "rpg_settings_json" in data:
        await update_session_rpg_settings(session_id, data["rpg_settings_json"])
    return {"status": "updated"}


@router.post("/{session_id}/fork")
async def fork_session_route(session_id: str, req: ForkSessionRequest):
    new_id = await fork_session(session_id, req.until_message_id)
    if not new_id:
        raise HTTPException(status_code=404, detail="Сессия не найдена")
    return {"session_id": new_id, "source_session_id": session_id}


@router.delete("/{session_id}")
async def remove_session(session_id: str):
    await delete_session(session_id)
    return {"status": "deleted", "session_id": session_id}
