"""Metrics query service.

Extracts query and aggregation logic from the metrics router so that
the router remains a thin HTTP adapter.  Provides aggregated views
of MITRE ATT&CK technique coverage for dashboards and reporting.

This module is framework-agnostic: no FastAPI imports.
"""

from __future__ import annotations

from collections import defaultdict

from sqlalchemy import func
from sqlalchemy.orm import Session, joinedload

from app.models.enums import TechniqueStatus, TestState
from app.models.technique import Technique
from app.models.test import Test
from app.schemas.metrics import (
    CoverageSummary,
    RecentTestItem,
    TacticCoverage,
    TeamActivity,
    TestPipelineCounts,
    ValidationRate,
)


def get_coverage_summary(db: Session) -> CoverageSummary:
    """Return a global coverage summary across all techniques."""
    rows = (
        db.query(
            Technique.status_global,
            func.count(Technique.id).label("cnt"),
        )
        .group_by(Technique.status_global)
        .all()
    )

    counts: dict[str, int] = {s.value: 0 for s in TechniqueStatus}
    for status, cnt in rows:
        counts[status.value] = cnt

    total = sum(counts.values())
    validated = counts["validated"]
    partial = counts["partial"]

    coverage_pct = (
        round((validated + partial) / total * 100, 2) if total > 0 else 0.0
    )

    return CoverageSummary(
        total_techniques=total,
        validated=validated,
        partial=partial,
        not_covered=counts["not_covered"],
        in_progress=counts["in_progress"],
        not_evaluated=counts["not_evaluated"],
        coverage_percentage=coverage_pct,
    )


def get_coverage_by_tactic(db: Session) -> list[TacticCoverage]:
    """Return coverage breakdown grouped by tactic.

    Since a technique can belong to multiple tactics (stored as a
    comma-separated string), the technique is counted once per tactic
    it belongs to.
    """
    techniques = db.query(
        Technique.tactic, Technique.status_global
    ).all()

    # Accumulate per-tactic counters.  A technique with tactic
    # "persistence, privilege-escalation" is counted in both.
    tactic_data: dict[str, dict[str, int]] = defaultdict(
        lambda: {s.value: 0 for s in TechniqueStatus}
    )

    for tactic_str, status in techniques:
        if not tactic_str:
            tactics = ["unknown"]
        else:
            tactics = [t.strip() for t in tactic_str.split(",")]

        for tactic in tactics:
            tactic_data[tactic][status.value] += 1

    result = []
    for tactic in sorted(tactic_data):
        counts = tactic_data[tactic]
        total = sum(counts.values())
        result.append(
            TacticCoverage(
                tactic=tactic,
                total=total,
                validated=counts["validated"],
                partial=counts["partial"],
                not_covered=counts["not_covered"],
                not_evaluated=counts["not_evaluated"],
                in_progress=counts["in_progress"],
            )
        )

    return result


def get_test_pipeline_counts(db: Session) -> TestPipelineCounts:
    """Return how many tests are in each pipeline state."""
    rows = (
        db.query(Test.state, func.count(Test.id).label("cnt"))
        .group_by(Test.state)
        .all()
    )

    state_counts: dict[str, int] = {s.value: 0 for s in TestState}
    for state, cnt in rows:
        state_counts[state.value] = cnt

    total = sum(state_counts.values())

    return TestPipelineCounts(
        draft=state_counts["draft"],
        red_executing=state_counts["red_executing"],
        blue_evaluating=state_counts["blue_evaluating"],
        in_review=state_counts["in_review"],
        validated=state_counts["validated"],
        rejected=state_counts["rejected"],
        total=total,
    )


def get_team_activity(db: Session) -> list[TeamActivity]:
    """Return activity summary for Red and Blue teams."""
    # Red Team: completed = tests past red_executing; pending = draft + red_executing
    red_completed = (
        db.query(func.count(Test.id))
        .filter(Test.state.in_([
            TestState.blue_evaluating,
            TestState.in_review,
            TestState.validated,
            TestState.rejected,
        ]))
        .scalar()
    ) or 0

    red_pending = (
        db.query(func.count(Test.id))
        .filter(Test.state.in_([TestState.draft, TestState.red_executing]))
        .scalar()
    ) or 0

    # Blue Team: completed = tests past blue_evaluating; pending = blue_evaluating
    blue_completed = (
        db.query(func.count(Test.id))
        .filter(Test.state.in_([
            TestState.in_review,
            TestState.validated,
            TestState.rejected,
        ]))
        .scalar()
    ) or 0

    blue_pending = (
        db.query(func.count(Test.id))
        .filter(Test.state == TestState.blue_evaluating)
        .scalar()
    ) or 0

    return [
        TeamActivity(
            team="Red Team",
            tests_completed=red_completed,
            tests_pending=red_pending,
        ),
        TeamActivity(
            team="Blue Team",
            tests_completed=blue_completed,
            tests_pending=blue_pending,
        ),
    ]


def get_validation_rate(db: Session) -> list[ValidationRate]:
    """Return approval and rejection rates for Red Lead and Blue Lead."""
    # Red Lead validations
    red_approved = (
        db.query(func.count(Test.id))
        .filter(Test.red_validation_status == "approved")
        .scalar()
    ) or 0
    red_rejected = (
        db.query(func.count(Test.id))
        .filter(Test.red_validation_status == "rejected")
        .scalar()
    ) or 0
    red_total = red_approved + red_rejected
    red_rate = round(red_approved / red_total * 100, 1) if red_total > 0 else 0.0

    # Blue Lead validations
    blue_approved = (
        db.query(func.count(Test.id))
        .filter(Test.blue_validation_status == "approved")
        .scalar()
    ) or 0
    blue_rejected = (
        db.query(func.count(Test.id))
        .filter(Test.blue_validation_status == "rejected")
        .scalar()
    ) or 0
    blue_total = blue_approved + blue_rejected
    blue_rate = round(blue_approved / blue_total * 100, 1) if blue_total > 0 else 0.0

    return [
        ValidationRate(
            role="red_lead",
            total_reviewed=red_total,
            approved=red_approved,
            rejected=red_rejected,
            approval_rate=red_rate,
        ),
        ValidationRate(
            role="blue_lead",
            total_reviewed=blue_total,
            approved=blue_approved,
            rejected=blue_rejected,
            approval_rate=blue_rate,
        ),
    ]


def get_recent_tests(db: Session, *, limit: int = 10) -> list[RecentTestItem]:
    """Return the most recently created tests."""
    from sqlalchemy import nullslast
    tests = (
        db.query(Test)
        .options(joinedload(Test.technique))
        .order_by(nullslast(Test.created_at.desc()))
        .limit(limit)
        .all()
    )

    return [
        RecentTestItem(
            id=str(t.id),
            name=t.name,
            state=t.state.value,
            technique_mitre_id=t.technique.mitre_id if t.technique else None,
            technique_name=t.technique.name if t.technique else None,
            created_at=t.created_at,
        )
        for t in tests
    ]
