"""Phase 13: Executive Dashboard service — aggregate posture data across all phases."""

from __future__ import annotations

from datetime import date, datetime, timedelta
from typing import List, Optional
from uuid import UUID

from sqlalchemy.orm import Session

from app.models.executive_dashboard import PostureSnapshot
from app.models.technique import Technique
from app.models.risk_intelligence import TechniqueRiskProfile
from app.models.ownership_queue import (
    TechniqueOwnership, RevalidationQueueItem, QueueStatus,
)
from app.models.knowledge import Playbook, LessonLearned
from app.models.attack_path import AttackPathExecution, ExecutionStatus
from app.models.test import Test
from app.models.osint_item import OsintItem
from app.models.enums import TechniqueStatus


# ── Internal aggregation helpers ──────────────────────────────────────────────

def _aggregate_coverage(db: Session) -> dict:
    """Aggregate technique coverage counts from live data."""
    techniques = db.query(Technique).all()
    total = len(techniques)

    counts = {
        TechniqueStatus.validated:    0,
        TechniqueStatus.partial:      0,
        TechniqueStatus.not_covered:  0,
    }
    for t in techniques:
        s = t.status_global
        if s in counts:
            counts[s] += 1

    validated = counts[TechniqueStatus.validated]
    partial   = counts[TechniqueStatus.partial]
    not_covered = total - validated - partial
    coverage_pct = round((validated + partial * 0.5) / total * 100.0, 2) if total > 0 else 0.0

    return {
        "total_techniques":  total,
        "validated_count":   validated,
        "partial_count":     partial,
        "not_covered_count": not_covered,
        "coverage_pct":      coverage_pct,
    }


def _aggregate_risk(db: Session) -> dict:
    """Aggregate risk metrics from TechniqueRiskProfile."""
    profiles = db.query(TechniqueRiskProfile).all()
    if not profiles:
        return {
            "avg_risk_score": 0.0,
            "critical_count": 0,
            "high_count":     0,
            "medium_count":   0,
            "low_count":      0,
        }

    by_level = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
    score_sum = 0.0
    for p in profiles:
        score_sum += p.risk_score
        lvl = p.risk_level or "info"
        by_level[lvl] = by_level.get(lvl, 0) + 1

    return {
        "avg_risk_score": round(score_sum / len(profiles), 2),
        "critical_count": by_level["critical"],
        "high_count":     by_level["high"],
        "medium_count":   by_level["medium"],
        "low_count":      by_level["low"],
    }


def _aggregate_operations(db: Session) -> dict:
    """Aggregate operational queue and orphan counts."""
    open_queue = db.query(RevalidationQueueItem).filter(
        RevalidationQueueItem.status.in_([QueueStatus.pending, QueueStatus.in_progress]),
    ).count()

    total_tech = db.query(Technique).count()
    owned_count = db.query(TechniqueOwnership).filter(
        TechniqueOwnership.owner_id.isnot(None)
    ).count()
    orphans = total_tech - owned_count

    return {
        "open_queue_items":  open_queue,
        "orphan_techniques": max(orphans, 0),
    }


def _aggregate_knowledge(db: Session) -> dict:
    """Count active playbooks and lessons learned."""
    playbook_count = db.query(Playbook).filter(Playbook.is_active == True).count()
    lesson_count   = db.query(LessonLearned).filter(LessonLearned.is_active == True).count()
    return {
        "playbook_count": playbook_count,
        "lesson_count":   lesson_count,
    }


def _aggregate_mttd(db: Session) -> dict:
    """Aggregate MTTD from completed attack-path executions in the last 30 days."""
    cutoff = datetime.utcnow() - timedelta(days=30)
    execs = db.query(AttackPathExecution).filter(
        AttackPathExecution.status == ExecutionStatus.completed,
        AttackPathExecution.completed_at >= cutoff,
    ).all()

    count = len(execs)
    mttd_values = [e.mttd_seconds for e in execs if e.mttd_seconds is not None]
    dr_values   = [e.detection_rate for e in execs if e.detection_rate is not None]

    return {
        "executions_30d":     count,
        "mttd_avg_seconds":   round(sum(mttd_values) / len(mttd_values), 2) if mttd_values else None,
        "detection_rate_30d": round(sum(dr_values)   / len(dr_values),   4) if dr_values   else None,
    }


def _build_extra_breakdown(db: Session) -> dict:
    """Build the by-tactic breakdown stored in the `extra` JSONB field."""
    techniques = db.query(Technique).all()
    tactic_map: dict = {}
    for t in techniques:
        tac = t.tactic or "Unknown"
        if tac not in tactic_map:
            tactic_map[tac] = {"total": 0, "validated": 0, "partial": 0, "not_covered": 0}
        tactic_map[tac]["total"] += 1
        s = t.status_global
        if s == TechniqueStatus.validated:
            tactic_map[tac]["validated"] += 1
        elif s == TechniqueStatus.partial:
            tactic_map[tac]["partial"] += 1
        else:
            tactic_map[tac]["not_covered"] += 1

    coverage_by_tactic = [
        {
            "tactic":       tac,
            "total":        v["total"],
            "validated":    v["validated"],
            "partial":      v["partial"],
            "not_covered":  v["not_covered"],
            "coverage_pct": round(
                (v["validated"] + v["partial"] * 0.5) / v["total"] * 100.0, 2
            ) if v["total"] > 0 else 0.0,
        }
        for tac, v in sorted(tactic_map.items())
    ]
    return {"coverage_by_tactic": coverage_by_tactic}


# ── Snapshot persistence ───────────────────────────────────────────────────────

def take_posture_snapshot(
    db: Session,
    created_by: Optional[UUID] = None,
) -> PostureSnapshot:
    """
    Aggregate all phases and write (or update) today's PostureSnapshot.
    Upserts on snapshot_date — only one row per calendar day.
    """
    today = date.today()

    coverage   = _aggregate_coverage(db)
    risk       = _aggregate_risk(db)
    operations = _aggregate_operations(db)
    knowledge  = _aggregate_knowledge(db)
    mttd       = _aggregate_mttd(db)
    extra      = _build_extra_breakdown(db)

    existing = db.query(PostureSnapshot).filter(
        PostureSnapshot.snapshot_date == today,
    ).first()

    values = {
        **coverage,
        **risk,
        **operations,
        **knowledge,
        **mttd,
        "extra": extra,
    }

    if existing:
        for k, v in values.items():
            setattr(existing, k, v)
        existing.created_by = created_by
        db.commit()
        db.refresh(existing)
        return existing

    snap = PostureSnapshot(snapshot_date=today, created_by=created_by, **values)
    db.add(snap)
    db.commit()
    db.refresh(snap)
    return snap


# ── Live / read-only aggregations (no DB write) ───────────────────────────────

def get_live_kpis(db: Session) -> dict:
    """Return current KPIs without persisting a snapshot."""
    coverage   = _aggregate_coverage(db)
    risk       = _aggregate_risk(db)
    operations = _aggregate_operations(db)
    knowledge  = _aggregate_knowledge(db)
    mttd       = _aggregate_mttd(db)
    return {**coverage, **risk, **operations, **knowledge, **mttd, "snapshot_date": date.today()}


def get_coverage_by_tactic(db: Session) -> list:
    """Per-tactic validated/partial/not_covered breakdown."""
    extra = _build_extra_breakdown(db)
    return extra["coverage_by_tactic"]


def get_posture_history(
    db: Session,
    days: int = 30,
) -> List[PostureSnapshot]:
    """Return the last `days` PostureSnapshot rows ordered ascending."""
    cutoff = date.today() - timedelta(days=days)
    return (
        db.query(PostureSnapshot)
        .filter(PostureSnapshot.snapshot_date >= cutoff)
        .order_by(PostureSnapshot.snapshot_date.asc())
        .all()
    )


def get_top_risks(db: Session, limit: int = 5) -> list:
    """Return top-N risk profiles with technique details."""
    from app.models.risk_intelligence import TechniqueRiskProfile

    rows = (
        db.query(TechniqueRiskProfile, Technique)
        .join(Technique, TechniqueRiskProfile.technique_id == Technique.id)
        .order_by(TechniqueRiskProfile.risk_score.desc())
        .limit(limit)
        .all()
    )
    return [
        {
            "technique_id":   str(p.technique_id),
            "technique_name": t.name,
            "technique_tid":  t.mitre_id,
            "tactic":         t.tactic,
            "risk_score":     p.risk_score,
            "risk_level":     p.risk_level,
            "likelihood":     p.likelihood,
            "impact":         p.impact,
            "detection_gap":  p.detection_gap,
        }
        for p, t in rows
    ]


def get_recent_activity(db: Session, limit: int = 20) -> list:
    """Combine recent events from tests, attack-path executions, queue, and OSINT."""
    events: list = []

    # Recent test executions (use execution_date, fall back to created_at)
    recent_tests = (
        db.query(Test)
        .filter(Test.result.isnot(None))
        .order_by(Test.created_at.desc())
        .limit(limit)
        .all()
    )
    for t in recent_tests:
        ts = t.execution_date or t.created_at
        events.append({
            "ts":       ts,
            "category": "test",
            "title":    f"Test executed — result: {t.result.value if t.result else 'pending'}",
            "detail":   str(t.id),
        })

    # Recent completed attack-path executions
    recent_execs = (
        db.query(AttackPathExecution)
        .filter(
            AttackPathExecution.status == ExecutionStatus.completed,
            AttackPathExecution.completed_at.isnot(None),
        )
        .order_by(AttackPathExecution.completed_at.desc())
        .limit(limit // 2)
        .all()
    )
    for e in recent_execs:
        dr = f"{e.detection_rate * 100:.0f}%" if e.detection_rate is not None else "n/a"
        events.append({
            "ts":       e.completed_at,
            "category": "attack_path",
            "title":    f"Attack path completed — detection: {dr}",
            "detail":   str(e.id),
        })

    # Recent OSINT items
    recent_osint = (
        db.query(OsintItem)
        .order_by(OsintItem.discovered_at.desc())
        .limit(limit // 4)
        .all()
    )
    for o in recent_osint:
        events.append({
            "ts":       o.discovered_at,
            "category": "osint",
            "title":    f"OSINT signal: {o.title or 'unknown'}",
            "detail":   str(o.id),
        })

    # Sort all events descending by timestamp, return top `limit`
    events.sort(key=lambda x: x["ts"] or datetime.min, reverse=True)
    return events[:limit]


def get_executive_summary(db: Session) -> dict:
    """Full executive view — live KPIs + snapshot + trends + top risks + activity."""
    # Take (or update) today's snapshot
    snap = take_posture_snapshot(db)

    # 30-day trend
    history = get_posture_history(db, days=30)
    coverage_trend = [
        {"date": str(s.snapshot_date), "value": s.coverage_pct}
        for s in history
    ]
    risk_trend = [
        {"date": str(s.snapshot_date), "value": s.avg_risk_score}
        for s in history
    ]

    return {
        "snapshot":           snap,
        "coverage_trend":     coverage_trend,
        "risk_trend":         risk_trend,
        "top_risks":          get_top_risks(db),
        "coverage_by_tactic": get_coverage_by_tactic(db),
        "recent_activity":    get_recent_activity(db),
    }
