"""Process events from all pipeline runners and update session state."""
from __future__ import annotations

import time
from pathlib import Path
from typing import Optional

from .session import SessionState, STEPS, STEP_SEQUENCE

STEP_NUM_TO_KEY = {
    "01":  "step-01-input-ingestion",
    "02":  "step-02-prd-generation",
    "02c": "step-02c-dalfin",
    "07":  "step-07-save-ddl-to-db",
    "03":  "step-03-backend-generation",
    "04":  "step-04-ir-generation",
    "05":  "step-05-react-generation",
}
NEXT_STEP_NUM = {
    "02":  "01",
    "02c": "02",
    "07":  "02c",
    "03":  "07",
    "04":  "03",
    "05":  "04",
}


# ─── Sub-step helpers ─────────────────────────────────────────────────────────

def _push_substep(p: dict, step_key: Optional[str], msg: str) -> None:
    sk = step_key or p.get("current_step")
    if not sk or sk not in p["steps"]:
        return
    items = p["steps"][sk]["sub_steps"]
    if items and not items[-1]["done"]:
        items[-1]["done"] = True
    items.append({"msg": msg, "done": False})


def _done_substep(p: dict, step_key: Optional[str], msg: str) -> None:
    sk = step_key or p.get("current_step")
    if not sk or sk not in p["steps"]:
        return
    p["steps"][sk]["sub_steps"].append({"msg": msg, "done": True})


def _finish_substeps(p: dict, step_key: str) -> None:
    for item in p["steps"][step_key]["sub_steps"]:
        item["done"] = True


def _resolve_step_key(step_field: str, current: Optional[str]) -> Optional[str]:
    for k in STEP_SEQUENCE:
        if k in step_field or step_field in k:
            return k
    return current


# ─── Main pipeline event processing ──────────────────────────────────────────

def process_pipeline_events(sess: SessionState) -> bool:
    """Drain and process pipeline runner events. Returns True if any events processed."""
    if sess.runner is None:
        return False
    events = sess.runner.drain()
    if not events:
        return False

    p = sess.pipeline
    started = sess._started

    for ev in events:
        t = ev["type"]

        if t == "log":
            p["logs"].append(ev["text"])
            if len(p["logs"]) > 700:
                p["logs"] = p["logs"][-700:]

        elif t == "run_id":
            if not p["run_id"]:
                p["run_id"] = ev["run_id"]
            _done_substep(p, STEP_NUM_TO_KEY["01"], f"Run created: {ev['run_id']}")

        elif t == "substep":
            _push_substep(p, p["current_step"], ev["message"])

        elif t == "step_print":
            num = ev["step_num"]
            key = STEP_NUM_TO_KEY.get(num)
            if num not in started:
                started.add(num)
                prev = NEXT_STEP_NUM.get(num)
                if prev:
                    pk = STEP_NUM_TO_KEY.get(prev)
                    if pk and p["steps"][pk]["status"] == "running":
                        s = p["steps"][pk]
                        s["status"] = "done"
                        if s["start_time"]:
                            s["duration_ms"] = (time.time() - s["start_time"]) * 1000
                        _finish_substeps(p, pk)
                if key and p["steps"][key]["status"] == "pending":
                    p["steps"][key]["status"] = "running"
                    p["steps"][key]["start_time"] = time.time()
                    p["current_step"] = key
                    _push_substep(p, key, ev["message"])

        elif t == "llm_calling":
            p["current_llm"] = f"{ev['model']}  {ev.get('label', '')}"
            label = ev.get("label", "").strip()
            msg = f"Calling {ev['model']}" + (f": {label}" if label else "")
            _push_substep(p, p["current_step"], msg)

        elif t == "llm_call":
            p["current_llm"] = None
            sk = _resolve_step_key(ev["step"], p["current_step"])
            if sk:
                s = p["steps"][sk]
                s["llm_calls"] += 1
                s["input_tokens"] += ev["input_tokens"]
                s["output_tokens"] += ev["output_tokens"]
                s["total_tokens"] += ev["total_tokens"]
                if ev["attempt"] > 1:
                    s["retries"] += 1
                s["llm_details"].append({
                    "model":   ev["model"],
                    "attempt": f"{ev['attempt']}/{ev['total_attempts']}",
                    "page":    ev.get("page") or "—",
                    "in":      ev["input_tokens"],
                    "out":     ev["output_tokens"],
                    "total":   ev["total_tokens"],
                    "ms":      int(ev["duration_ms"]),
                })
                items = s["sub_steps"]
                if items and not items[-1]["done"]:
                    items[-1]["done"] = True
                if ev.get("page") and sk == "step-05-react-generation":
                    if ev["page"] not in p["artifacts"]["react_pages"]:
                        p["artifacts"]["react_pages"].append(ev["page"])
                        _done_substep(p, sk, f"Page done: {ev['page']}")
            tot = p["totals"]
            tot["input_tokens"] += ev["input_tokens"]
            tot["output_tokens"] += ev["output_tokens"]
            tot["total_tokens"] += ev["total_tokens"]
            tot["llm_calls"] += 1

        elif t == "artifact":
            if ev["name"] == "ddl":
                p["artifacts"]["ddl"] = ev.get("filename", "schema.sql")
                _done_substep(p, STEP_NUM_TO_KEY["02"], f"DDL generated: {ev.get('filename', 'schema.sql')}")
            elif ev["name"] == "prd":
                p["artifacts"]["prd"] = True
                _done_substep(p, STEP_NUM_TO_KEY["02"], "PRD generated")

        elif t == "backend_info":
            a = p["artifacts"]
            a["system_name"] = ev["system"]
            a["api_endpoints"] = ev["endpoints"]
            sk = STEP_NUM_TO_KEY["03"]
            p["steps"][sk]["notes"].update(
                {"system": ev["system"], "modules": ev["modules"], "endpoints": ev["endpoints"]}
            )
            _done_substep(p, sk, f"System: {ev['system']} — {ev['endpoints']} endpoints")

        elif t == "mantara_done":
            p["artifacts"]["mantara_done"] = True
            _done_substep(p, STEP_NUM_TO_KEY["02c"], "mantara_schema.json + dalfin.json generated")

        elif t == "ddl_to_db":
            sk = STEP_NUM_TO_KEY["07"]
            if ev["success"]:
                p["artifacts"]["ddl_to_db"] = "success"
                _done_substep(p, sk, "DDL deployed to database")
            else:
                p["artifacts"]["ddl_to_db"] = "failed"
                _done_substep(p, sk, "DDL deployment failed — continuing without DB")

        elif t == "ir_pages":
            p["artifacts"]["ir_pages"] = ev["pages"]
            p["artifacts"]["cir"] = True
            pages = ev["pages"]
            preview = ", ".join(pages[:3]) + ("…" if len(pages) > 3 else "")
            _done_substep(p, STEP_NUM_TO_KEY["04"], f"IR ready: {len(pages)} pages ({preview})")

        elif t == "api_module":
            m = ev["module"]
            if m not in p["artifacts"]["api_modules"]:
                p["artifacts"]["api_modules"].append(m)
                _done_substep(p, STEP_NUM_TO_KEY["03"], f"Module generated: {m}")

        elif t == "project_done":
            p["project_root"] = ev["project_root"]
            _done_substep(p, STEP_NUM_TO_KEY["05"], f"Project built: {Path(ev['project_root']).name}")

        elif t == "run_summary":
            tot = p["totals"]
            tot["input_tokens"] = ev["total_input"]
            tot["output_tokens"] = ev["total_output"]
            tot["total_tokens"] = ev["total_input"] + ev["total_output"]
            tot["llm_calls"] = ev["llm_calls"]

        elif t == "finished":
            rc = ev["return_code"]
            sess.status = "done" if rc == 0 else "error"
            for key, _ in STEPS:
                s = p["steps"][key]
                if s["status"] == "running":
                    s["status"] = "done" if rc == 0 else "error"
                    if s["start_time"]:
                        s["duration_ms"] = (time.time() - s["start_time"]) * 1000
                    _finish_substeps(p, key)
            p["current_step"] = None
            p["current_llm"] = None
            if ev.get("log_path"):
                p["log_path"] = ev["log_path"]

    return True


# ─── Management runner event processing ──────────────────────────────────────

def process_tenant_events(sess: SessionState) -> bool:
    if sess.tenant_runner is None:
        return False
    events = sess.tenant_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.tenant_logs.append(ev["text"])
        elif t == "tenant_done":
            sess.tenant_status = "done"
            sess.tenant_result = ev["result"]
        elif t == "tenant_error":
            sess.tenant_status = "error"
        elif t == "finished":
            if sess.tenant_status not in ("done", "error"):
                sess.tenant_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_efs_events(sess: SessionState) -> bool:
    if sess.efs_runner is None:
        return False
    events = sess.efs_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.efs_logs.append(ev["text"])
            if len(sess.efs_logs) > 200:
                sess.efs_logs = sess.efs_logs[-200:]
        elif t == "efs_done":
            sess.efs_dest = ev["dest"]
        elif t == "finished":
            sess.efs_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_scripts_events(sess: SessionState) -> bool:
    if sess.scripts_runner is None:
        return False
    events = sess.scripts_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.scripts_logs.append(ev["text"])
        elif t == "scripts_done":
            sess.scripts_status = "done"
            sess.scripts_dest = ev["dest"]
        elif t == "scripts_error":
            sess.scripts_status = "error"
        elif t == "finished":
            if sess.scripts_status not in ("done", "error"):
                sess.scripts_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_gitlab_events(sess: SessionState) -> bool:
    if sess.gitlab_runner is None:
        return False
    events = sess.gitlab_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.gitlab_logs.append(ev["text"])
        elif t == "gitlab_done":
            sess.gitlab_status = "done"
            sess.gitlab_url = ev["url"]
        elif t == "gitlab_error":
            sess.gitlab_status = "error"
        elif t == "finished":
            if sess.gitlab_status not in ("done", "error"):
                sess.gitlab_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_jenkins_events(sess: SessionState) -> bool:
    if sess.jenkins_runner is None:
        return False
    events = sess.jenkins_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.jenkins_logs.append(ev["text"])
        elif t == "jenkins_done":
            sess.jenkins_status = "done"
            sess.jenkins_url = ev["url"]
        elif t == "jenkins_error":
            sess.jenkins_status = "error"
        elif t == "finished":
            if sess.jenkins_status not in ("done", "error"):
                sess.jenkins_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_jenkins_trig_events(sess: SessionState) -> bool:
    if sess.jenkins_trig_runner is None:
        return False
    events = sess.jenkins_trig_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.jenkins_trig_logs.append(ev["text"])
        elif t == "jenkins_trig_done":
            sess.jenkins_trig_status = "done"
            sess.jenkins_trig_url = ev["url"]
        elif t == "jenkins_trig_error":
            sess.jenkins_trig_status = "error"
        elif t == "finished":
            if sess.jenkins_trig_status not in ("done", "error"):
                sess.jenkins_trig_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_pr_events(sess: SessionState) -> bool:
    if sess.pr_runner is None:
        return False
    events = sess.pr_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.pr_logs.append(ev["text"])
        elif t == "runner_done":
            sess.pr_status = "done"
            sess.pr_url = ev["url"]
        elif t == "runner_error":
            sess.pr_status = "error"
        elif t == "finished":
            if sess.pr_status not in ("done", "error"):
                sess.pr_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_runner_trig_events(sess: SessionState) -> bool:
    if sess.pr_trig_runner is None:
        return False
    events = sess.pr_trig_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.pr_trig_logs.append(ev["text"])
        elif t == "runner_trig_done":
            sess.pr_trig_status = "done"
            sess.pr_trig_url = ev["url"]
        elif t == "runner_trig_error":
            sess.pr_trig_status = "error"
        elif t == "finished":
            if sess.pr_trig_status not in ("done", "error"):
                sess.pr_trig_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_db_events(sess: SessionState) -> bool:
    if sess.db_runner is None:
        return False
    events = sess.db_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.db_logs.append(ev["text"])
        elif t == "db_done":
            sess.db_status = "done"
        elif t == "db_error":
            sess.db_status = "error"
        elif t == "finished":
            if sess.db_status not in ("done", "error"):
                sess.db_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_setup_events(sess: SessionState) -> bool:
    if sess.setup_runner is None:
        return False
    events = sess.setup_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.setup_logs.append(ev["text"])
        elif t == "setup_done":
            sess.setup_status = "done"
        elif t == "setup_error":
            sess.setup_status = "error"
        elif t == "finished":
            if sess.setup_status not in ("done", "error"):
                sess.setup_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_build_events(sess: SessionState) -> bool:
    if sess.build_runner is None:
        return False
    events = sess.build_runner.drain()
    if not events:
        return False
    for ev in events:
        t = ev["type"]
        if t == "log":
            sess.build_logs.append(ev["text"])
        elif t == "build_done":
            sess.build_status = "done"
        elif t == "build_error":
            sess.build_status = "error"
        elif t == "finished":
            if sess.build_status not in ("done", "error"):
                sess.build_status = "done" if ev["return_code"] == 0 else "error"
    return True


def process_all_events(sess: SessionState) -> bool:
    """Drain all active runners. Returns True if any runner had events."""
    changed = False
    changed |= process_pipeline_events(sess)
    changed |= process_tenant_events(sess)
    changed |= process_efs_events(sess)
    changed |= process_scripts_events(sess)
    changed |= process_gitlab_events(sess)
    changed |= process_jenkins_events(sess)
    changed |= process_jenkins_trig_events(sess)
    changed |= process_pr_events(sess)
    changed |= process_runner_trig_events(sess)
    changed |= process_db_events(sess)
    changed |= process_setup_events(sess)
    changed |= process_build_events(sess)
    return changed
