import json
import logging

from services.memory import (
    get_history,
    get_session,
    get_last_assistant_message_id,
    update_session_plot_arc,
    update_message_choices,
    get_quests,
)
from services.rpg_state import apply_narrator_post
from services.personas import get_persona
from services.rpg_facts import facts_to_prompt
from services.rpg_plot import generate_plot_arc, choices_from_narrator
from services.rpg_context import format_narrator_context
from services.rpg_narrator import narrator_post
from services.sd_prompt import generate_sd_prompt
from services.sd_images import run_sd_for_message

logger = logging.getLogger(__name__)

DEFAULT_RPG_SETTINGS = {
    "dice": True,
    "narrator": True,
    "quests": True,
    "affinity": True,
    "choices": True,
    "stats": False,
}


def get_rpg_settings(session: dict) -> dict:
    try:
        return {**DEFAULT_RPG_SETTINGS, **json.loads(session.get("rpg_settings_json") or "{}")}
    except Exception:
        return DEFAULT_RPG_SETTINGS


async def resolve_greeting(session_id: str, persona: dict) -> str:
    history = await get_history(session_id)
    for m in reversed(history):
        if m.get("role") == "assistant" and (m.get("content") or "").strip():
            return m["content"].strip()
    return (persona.get("first_mes") or "").strip()


async def ensure_plot_arc_and_quests(
    session_id: str,
    persona: dict,
    greeting: str,
    genre: str,
    *,
    seed_quests: bool = True,
) -> dict:
    session = await get_session(session_id) or {}
    arc_json = session.get("plot_arc_json") or "{}"
    try:
        arc = json.loads(arc_json) if isinstance(arc_json, str) else {}
    except Exception:
        arc = {}

    if arc:
        return arc

    from services.rpg_locale import infer_rp_language

    facts_block = facts_to_prompt(session.get("facts_json", "[]"))
    lang = infer_rp_language([{"role": "assistant", "content": greeting}])
    arc = await generate_plot_arc(
        persona.get("name", "Character"),
        persona.get("description", ""),
        persona.get("scenario", ""),
        greeting,
        facts_block=facts_block,
        genre=genre,
        lang=lang,
        recent_context=f"assistant: {greeting}",
    )
    if not arc:
        return {}

    await update_session_plot_arc(session_id, json.dumps(arc, ensure_ascii=False))
    if seed_quests:
        from services.rpg_story import sync_quest_to_current_step

        await sync_quest_to_current_step(session_id, arc)
    return arc


async def process_opening(session_id: str, persona_id: str, *, rpg: bool) -> dict:
    session = await get_session(session_id)
    if not session:
        raise ValueError("Session not found")

    history = await get_history(session_id)
    assistant_msgs = [m for m in history if m.get("role") == "assistant"]
    if not assistant_msgs:
        raise ValueError("No assistant message (first_mes) found")

    first_mes_text = assistant_msgs[-1].get("content", "").strip()
    if not first_mes_text:
        raise ValueError("Empty first_mes")

    msg_id = await get_last_assistant_message_id(session_id)
    persona = await get_persona(persona_id) or {}
    rpg_settings = get_rpg_settings(session)

    arc: dict = {}
    choices: list = []
    status_quo = session.get("status_quo") or ""
    outfit_json = session.get("outfit_json") or "[]"

    if rpg:
        genre = session.get("genre") or "adventure"
        arc = await ensure_plot_arc_and_quests(
            session_id,
            persona,
            first_mes_text,
            genre,
            seed_quests=rpg_settings.get("quests", True),
        )

        session = await get_session(session_id) or session
        ctx_txt = f"assistant: {first_mes_text}"
        arc_json = json.dumps(arc, ensure_ascii=False) if arc else ""
        facts_block = facts_to_prompt(session.get("facts_json", "[]"))

        quests_pre = await get_quests(session_id)
        narr_ctx = format_narrator_context(arc, quests_pre, session.get("status_quo") or "")
        from services.rpg_locale import infer_rp_language

        o_lang = infer_rp_language([{"role": "assistant", "content": first_mes_text}])
        post = await narrator_post(
            persona.get("name", persona_id),
            ctx_txt,
            arc_json,
            facts_block,
            is_opening=True,
            extra_context=narr_ctx,
            lang=o_lang,
        )

        if rpg_settings.get("choices", True):
            choices = choices_from_narrator(post.get("choices") or [])

        from services.rpg_state import apply_narrator_post_with_story

        await apply_narrator_post_with_story(
            session_id, post, rpg_settings, session, arc=arc
        )
        session = await get_session(session_id) or session
        status_quo = session.get("status_quo") or status_quo
        outfit_json = session.get("outfit_json") or outfit_json

    quests = await get_quests(session_id)
    messages = await get_history(session_id)
    bundle = await generate_sd_prompt(
        messages,
        persona_id,
        outfit_json=outfit_json,
        scene_json=session.get("scene_json", "{}") if session else "{}",
    )
    sd_out = await run_sd_for_message(bundle, msg_id) if bundle else {}

    updated = await get_session(session_id)
    if rpg and msg_id:
        from services.memory import save_state_snapshot

        await save_state_snapshot(session_id, msg_id)
    affinity = updated.get("affinity", 0) if updated else 0

    if msg_id and choices:
        await update_message_choices(
            msg_id, json.dumps(choices, ensure_ascii=False)
        )

    return {
        "plot_arc": arc,
        "quests": quests,
        "outfit_json": outfit_json,
        "status_quo": status_quo,
        "choices": choices,
        "image_prompt": sd_out.get("image_prompt"),
        "image_prompt_alt": sd_out.get("image_prompt_alt"),
        "image_path": sd_out.get("image_path"),
        "image_path_alt": sd_out.get("image_path_alt"),
        "image_error": sd_out.get("image_error"),
        "image_error_alt": sd_out.get("image_error_alt"),
        "affinity": affinity,
    }
