"""Case Journey — reconstructs a case's pipeline story from persisted state.

Seven beats, one per implemented worker line. Each beat's `occurred` flag and
caption are derived from real data — this is a replay of recorded events, not
a script. Beats that did not happen for a case are returned with occurred=False.
"""

from __future__ import annotations

from typing import List, Optional

from pydantic import BaseModel, Field

from psyc.cockpit import inference
from psyc.lines import ledger as ledger_line
from psyc.lines import route as route_line
from psyc.lines import seal as seal_line
from psyc.models import Case, LedgerEntry, Outcome
from psyc.result import Err


_ACTED_OUTCOMES = {Outcome.SUBMITTED, Outcome.ACKNOWLEDGED, Outcome.ACTIONED}

# worker line -> (bot name, purpose, action verb)
_BOT = {
    "Scoutline": ("Scout", "I sweep public feeds for fresh threat signals.", "scanning feeds"),
    "Classifyline": ("Classifier", "I judge severity, TLP and incident type.", "assessing severity"),
    "Mapline": ("Mapper", "I locate the host and its jurisdiction.", "locating host"),
    "Sealine": ("Sealer", "I encrypt evidence for authorized recipients.", "encrypting evidence"),
    "Routeline": ("Router", "I decide who is allowed to receive this.", "evaluating destinations"),
    "Courier": ("Courier", "I deliver the payload to each destination.", "delivering payload"),
    "Ledgerline": ("Ledger", "I record every action, immutably.", "writing the record"),
}


class Beat(BaseModel):
    index: int
    line: str
    bot: str = ""
    purpose: str = ""
    action: str = ""
    title: str
    occurred: bool
    caption: str
    facts: List[str] = Field(default_factory=list)
    model_answer: Optional[str] = None  # live model prediction (Classifier bot only)


def build_journey(case: Case) -> List[Beat]:
    entries = ledger_line.list_by_case(case.case_id, limit=200)
    beats = [
        _detected(case),
        _classified(case),
        _located(case),
        _sealed(case),
        _routed(case),
        _submitted(entries),
        _recorded(entries),
    ]
    for i, beat in enumerate(beats, start=1):
        beat.index = i
        beat.bot, beat.purpose, beat.action = _BOT.get(beat.line, (beat.line, "", "working"))
    return beats


def _detected(case: Case) -> Beat:
    feed = case.source_metadata.get("feed", case.source_type or "unknown source")
    obs = case.observables
    counts = []
    for label, items in (("URL", obs.urls), ("IP", obs.ips), ("domain", obs.domains),
                         ("CVE", obs.cves), ("hash", obs.hashes)):
        if items:
            counts.append(f"{len(items)} {label}{'' if len(items) == 1 else 's'}")
    facts = [case.summary]
    if counts:
        facts.append("observables: " + ", ".join(counts))
    return Beat(
        index=0, line="Scoutline", title="Detected", occurred=True,
        caption=f"Scoutline ingested this signal from {feed}.", facts=facts,
    )


def _classified(case: Case) -> Beat:
    c = case.classification
    if c.incident_type is None:
        return Beat(index=0, line="Classifyline", title="Classified", occurred=False,
                    caption="Not yet classified.")
    severity = c.severity.value if c.severity else "—"
    internal = c.internal_class.value if c.internal_class else "—"
    return Beat(
        index=0, line="Classifyline", title="Classified", occurred=True,
        caption=f"Classifyline rated it {severity} severity, TLP:{c.tlp.value}, internal class {internal}.",
        facts=[f"incident type: {c.incident_type.value}"],
        model_answer=inference.model_severity(case),
    )


def _located(case: Case) -> Beat:
    if case.victim.country:
        facts = [f"host IP(s): {', '.join(case.observables.ips)}"] if case.observables.ips else []
        return Beat(index=0, line="Mapline", title="Located", occurred=True,
                    caption=f"Mapline resolved the host to {case.victim.country}.", facts=facts)
    caption = "No host to geolocate for this case." if not case.observables.ips else "Not yet mapped."
    return Beat(index=0, line="Mapline", title="Located", occurred=False, caption=caption)


def _sealed(case: Case) -> Beat:
    pkg_id = case.evidence.sealed_package_id
    if not pkg_id:
        return Beat(index=0, line="Sealine", title="Sealed", occurred=False,
                    caption="Evidence not sealed.")
    result = seal_line.load_package(pkg_id)
    if isinstance(result, Err):
        return Beat(index=0, line="Sealine", title="Sealed", occurred=True,
                    caption=f"Evidence sealed — package {pkg_id}.")
    pkg = result.value
    return Beat(
        index=0, line="Sealine", title="Sealed", occurred=True,
        caption=f"Sealine encrypted the evidence for {len(pkg.recipients)} authorized recipient(s).",
        facts=[f"package {pkg.package_id}", f"recipients: {', '.join(pkg.recipients)}",
               f"plaintext SHA-256: {pkg.plaintext_hash[:24]}…"],
    )


def _routed(case: Case) -> Beat:
    if case.classification.incident_type is None:
        return Beat(index=0, line="Routeline", title="Routed", occurred=False,
                    caption="Routing pending classification.")
    routes, blocked = route_line.plan(case)
    facts = [f"✓ {r.destination_name}" for r in routes]
    facts += [f"⊘ {b.destination_name} — {b.reason}" for b in blocked]
    return Beat(
        index=0, line="Routeline", title="Routed", occurred=True,
        caption=f"Routeline cleared {len(routes)} destination(s) and blocked {len(blocked)} by policy.",
        facts=facts,
    )


def _submitted(entries: List[LedgerEntry]) -> Beat:
    acted = [e for e in entries if e.outcome in _ACTED_OUTCOMES]
    if not acted:
        return Beat(index=0, line="Courier", title="Submitted", occurred=False,
                    caption="Not yet submitted to any destination.")
    facts = []
    for e in acted:
        receipt = f" · {e.response_id}" if e.response_id else ""
        facts.append(f"{e.destination} → {e.outcome.value}{receipt}")
    return Beat(
        index=0, line="Courier", title="Submitted", occurred=True,
        caption=f"Courier delivered the payload to {len(acted)} destination(s).", facts=facts,
    )


def _recorded(entries: List[LedgerEntry]) -> Beat:
    if not entries:
        return Beat(index=0, line="Ledgerline", title="Recorded", occurred=False,
                    caption="No ledger entries yet.")
    return Beat(
        index=0, line="Ledgerline", title="Recorded", occurred=True,
        caption=f"Ledgerline wrote {len(entries)} immutable audit record(s).",
        facts=[f"latest entry: {entries[0].timestamp.strftime('%Y-%m-%d %H:%M:%S')} UTC"],
    )
