"""Newsline — turn ledger + case activity into a human-readable digest.

Surfaces what psyc has *done* and what it has *seen* as a stream of news items
for the start page. Pure read aggregation over the existing case and ledger
stores — no new state.
"""

from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional

from pydantic import BaseModel

from psyc import db, log
from psyc.lines import ledger as ledger_line
from psyc.models import Case, LedgerEntry, Outcome, Severity


_log = log.get(__name__)


class NewsItem(BaseModel):
    timestamp: datetime
    kind: str                  # case | enforced | submitted | rejected | actioned | failed
    headline: str
    body: str
    severity: Optional[str] = None
    case_id: Optional[str] = None
    icon: str = "•"            # tiny glyph for the card


class FeedHealth(BaseModel):
    feed: str
    count: int
    latest: Optional[datetime] = None


# ---------- KPI strip ----------------------------------------------------

def kpis() -> Dict[str, int]:
    """Counters shown at the top of the home page."""
    cases = db.list_cases(limit=10_000)
    today = datetime.now(timezone.utc) - timedelta(hours=24)
    new_24h = sum(1 for c in cases if c.ingested_at and c.ingested_at >= today)
    high = sum(1 for c in cases
               if c.classification.severity in (Severity.HIGH, Severity.CRITICAL))
    ledger = ledger_line.list_recent(limit=10_000)
    enforcements_24h = sum(1 for e in ledger if e.timestamp >= today and e.outcome is Outcome.ACTIONED)
    return {
        "cases": len(cases),
        "iocs": db.ioc_count(),
        "new_24h": new_24h,
        "high_total": high,
        "enforcements_24h": enforcements_24h,
        "ledger_total": ledger_line.count(),
    }


# ---------- news items ---------------------------------------------------

_OUTCOME_RENDER = {
    Outcome.ACTIONED:    ("⚡", "enforced",  "psyc enforced the response for {case} → {dest}"),
    Outcome.SUBMITTED:   ("→",  "submitted", "Submitted to {dest} for {case}"),
    Outcome.ACKNOWLEDGED:("✓",  "submitted", "{dest} acknowledged the submission for {case}"),
    Outcome.REJECTED:    ("⊘",  "rejected",  "Blocked / declined: {dest} for {case}"),
    Outcome.FAILED:      ("✗",  "failed",    "Delivery to {dest} failed for {case}"),
    Outcome.PENDING_APPROVAL: ("⏳", "pending", "Awaiting approval: {dest} for {case}"),
}


def _ledger_to_news(e: LedgerEntry) -> NewsItem:
    icon, kind, fmt = _OUTCOME_RENDER.get(e.outcome, ("•", "ledger", "Ledger event for {case}"))
    headline = fmt.format(case=e.case_id, dest=e.destination)
    body = e.detail or f"{e.outcome.value} · TLP:{e.tlp.value}"
    return NewsItem(
        timestamp=e.timestamp, kind=kind, headline=headline, body=body,
        case_id=e.case_id, icon=icon,
    )


def _case_to_news(c: Case) -> NewsItem:
    sev = c.classification.severity.value if c.classification.severity else None
    incident = c.classification.incident_type.value if c.classification.incident_type else "case"
    feed = c.source_metadata.get("feed", "feed")
    headline = f"New {sev or 'unrated'} {incident} from {feed}"
    return NewsItem(
        timestamp=c.ingested_at, kind="case",
        headline=headline, body=c.summary[:200],
        severity=sev, case_id=c.case_id,
        icon={"critical": "🚨", "high": "⚠", "medium": "•", "low": "·"}.get(sev or "", "•"),
    )


def recent_items(limit: int = 30, high_only: bool = False) -> List[NewsItem]:
    """Interleave the latest ledger events with the latest case ingests, newest first."""
    items: List[NewsItem] = []
    for e in ledger_line.list_recent(limit=limit * 2):
        items.append(_ledger_to_news(e))
    cases = db.list_cases(limit=limit * 2)
    for c in cases:
        if high_only and c.classification.severity not in (Severity.HIGH, Severity.CRITICAL):
            continue
        items.append(_case_to_news(c))
    items.sort(key=lambda i: i.timestamp, reverse=True)
    return items[:limit]


# ---------- feed health (sidebar / footer of home) -----------------------

class Bucket(BaseModel):
    label: str
    items: List[NewsItem]


def bucket_items(items: List[NewsItem]) -> List[Bucket]:
    """Group items into Today / Yesterday / Earlier this week / Older."""
    now = datetime.now(timezone.utc)
    today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
    yesterday_start = today_start - timedelta(days=1)
    week_start = today_start - timedelta(days=7)
    order = ("Today", "Yesterday", "Earlier this week", "Older")
    buckets: Dict[str, List[NewsItem]] = {k: [] for k in order}
    for i in items:
        ts = i.timestamp
        if ts.tzinfo is None:
            ts = ts.replace(tzinfo=timezone.utc)
        if ts >= today_start: buckets["Today"].append(i)
        elif ts >= yesterday_start: buckets["Yesterday"].append(i)
        elif ts >= week_start: buckets["Earlier this week"].append(i)
        else: buckets["Older"].append(i)
    return [Bucket(label=k, items=buckets[k]) for k in order if buckets[k]]


_SEV_RANK = {Severity.CRITICAL: 3, Severity.HIGH: 2, Severity.MEDIUM: 1, Severity.LOW: 0}


def featured_case() -> Optional[Case]:
    """Pick a case to spotlight: highest-severity from the last 7 days,
    breaking ties by recency. Returns None if nothing HIGH+ in the window."""
    now = datetime.now(timezone.utc)
    week = now - timedelta(days=7)
    candidates: List[Case] = []
    for c in db.list_cases(limit=2000):
        ts = c.ingested_at
        if ts and ts.tzinfo is None:
            ts = ts.replace(tzinfo=timezone.utc)
        if ts and ts >= week and c.classification.severity in (Severity.HIGH, Severity.CRITICAL):
            candidates.append(c)
    if not candidates:
        return None
    candidates.sort(
        key=lambda c: (_SEV_RANK.get(c.classification.severity, -1), c.ingested_at),
        reverse=True,
    )
    return candidates[0]


def feed_health() -> List[FeedHealth]:
    """Per-feed counts + most-recent ingest. Useful as a 'sources live?' panel."""
    cases = db.list_cases(limit=10_000)
    buckets: Dict[str, FeedHealth] = {}
    for c in cases:
        feed = c.source_metadata.get("feed") or "unknown"
        h = buckets.get(feed)
        if h is None:
            buckets[feed] = FeedHealth(feed=feed, count=1, latest=c.ingested_at)
        else:
            h.count += 1
            if c.ingested_at and (h.latest is None or c.ingested_at > h.latest):
                h.latest = c.ingested_at
    out = list(buckets.values())
    out.sort(key=lambda h: h.count, reverse=True)
    return out
