"""
AIOps KPI Service — ADR-090 + MASTER §7.1
==========================================
Router 層呼叫本 Service 取得 KPI 全景,Router 禁直接存取 DB (leWOOOgo 積木化鐵律).

2026-04-19 ogt + Claude Opus 4.7 (1M context) Asia/Taipei
"""
from __future__ import annotations

import math
from typing import Any

from sqlalchemy import text as _sql

from src.db.base import get_db_context
from src.utils.timezone import now_taipei


class AiopsKpiService:
    """組裝 AI 自主化成熟度 KPI 全景."""

    async def get_snapshot(self) -> dict[str, Any]:
        """一次回傳 6 section + 自主化總分."""
        async with get_db_context() as db:
            inventory = await self._fetch_asset_inventory(db)
            coverage = await self._fetch_coverage_kpi(db)
            rule_quality = await self._fetch_rule_quality(db)
            capacity = await self._fetch_capacity_health(db)
            aol_flow = await self._fetch_automation_flow_24h(db)
        autonomy = self._compute_autonomy_score(
            inventory, coverage, rule_quality, capacity, aol_flow,
        )
        return {
            "generated_at": now_taipei().isoformat(),
            "asset_inventory": inventory,
            "coverage_kpi": coverage,
            "rule_quality": rule_quality,
            "capacity_health": capacity,
            "automation_flow_24h": aol_flow,
            "ai_autonomy_score": autonomy,
        }

    async def _fetch_asset_inventory(self, db) -> dict[str, Any]:
        rows = await db.execute(_sql("""
            SELECT asset_type, count(*) AS cnt
            FROM asset_inventory
            WHERE lifecycle_state = 'active'
            GROUP BY asset_type
            ORDER BY cnt DESC
        """))
        by_type = {r.asset_type: int(r.cnt) for r in rows.fetchall()}

        run_row = await db.execute(_sql("""
            SELECT run_id, ended_at, total_assets, new_assets, modified_assets, duration_ms
            FROM asset_discovery_run
            WHERE status = 'success'
            ORDER BY ended_at DESC LIMIT 1
        """))
        run = run_row.one_or_none()
        last_run: dict[str, Any] | None = None
        if run:
            last_run = {
                "run_id": str(run.run_id),
                "ended_at": run.ended_at.isoformat() if run.ended_at else None,
                "total_assets": run.total_assets,
                "new_assets": run.new_assets,
                "modified_assets": run.modified_assets,
                "duration_ms": run.duration_ms,
            }
        return {
            "by_type": by_type,
            "total": sum(by_type.values()),
            "last_scan": last_run,
        }

    async def _fetch_coverage_kpi(self, db) -> dict[str, Any]:
        rows = await db.execute(_sql("""
            SELECT dimension, coverage_status, count(*) AS cnt
            FROM asset_coverage_snapshot
            WHERE run_id = (
                SELECT run_id FROM asset_discovery_run
                WHERE status = 'success' ORDER BY ended_at DESC LIMIT 1
            )
            GROUP BY dimension, coverage_status
            ORDER BY dimension, coverage_status
        """))
        by_dim: dict[str, dict[str, int]] = {}
        for r in rows.fetchall():
            by_dim.setdefault(r.dimension, {})[r.coverage_status] = int(r.cnt)

        slo_per_dim: dict[str, float] = {}
        for dim, statuses in by_dim.items():
            total = sum(statuses.values())
            green = statuses.get("green", 0)
            slo_per_dim[dim] = round(green / total, 4) if total else 0.0

        return {
            "by_dimension": by_dim,
            "green_ratio_per_dim": slo_per_dim,
            "overall_green_ratio": round(
                sum(slo_per_dim.values()) / len(slo_per_dim), 4
            ) if slo_per_dim else 0.0,
        }

    async def _fetch_rule_quality(self, db) -> dict[str, Any]:
        summary = await db.execute(_sql("""
            SELECT
                count(*) AS total,
                count(*) FILTER (WHERE last_fired_at IS NOT NULL) AS with_fires,
                count(*) FILTER (WHERE noise_rate > 0.5) AS noisy,
                count(*) FILTER (WHERE review_status = 'deprecated') AS deprecated,
                count(*) FILTER (WHERE source = 'ai_generated') AS ai_generated
            FROM alert_rule_catalog
        """))
        s = summary.one()

        noisy_rows = await db.execute(_sql("""
            SELECT rule_name, severity, true_positive_count AS tp, false_positive_count AS fp,
                   noise_rate, last_fired_at
            FROM alert_rule_catalog
            WHERE noise_rate IS NOT NULL
              AND review_status IS DISTINCT FROM 'deprecated'
            ORDER BY noise_rate DESC, true_positive_count + false_positive_count DESC
            LIMIT 5
        """))
        top_noisy = [
            {
                "rule_name": r.rule_name,
                "severity": r.severity,
                "tp": int(r.tp or 0),
                "fp": int(r.fp or 0),
                "noise_rate": float(r.noise_rate) if r.noise_rate else 0.0,
                "last_fired_at": r.last_fired_at.isoformat() if r.last_fired_at else None,
            }
            for r in noisy_rows.fetchall()
        ]
        return {
            "total": int(s.total or 0),
            "with_fires": int(s.with_fires or 0),
            "noisy_above_0_5": int(s.noisy or 0),
            "deprecated": int(s.deprecated or 0),
            "ai_generated": int(s.ai_generated or 0),
            "top_noisy": top_noisy,
        }

    async def _fetch_capacity_health(self, db) -> dict[str, Any]:
        rows = await db.execute(_sql("""
            SELECT DISTINCT ON (host)
                host, ai_verdict, cpu_used_pct, mem_used_pct, swap_used_pct,
                captured_at, ai_reasoning
            FROM host_capacity_snapshot
            ORDER BY host, captured_at DESC
        """))
        hosts = [
            {
                "host": r.host,
                "ai_verdict": r.ai_verdict,
                "cpu_used_pct": float(r.cpu_used_pct) if r.cpu_used_pct else None,
                "mem_used_pct": float(r.mem_used_pct) if r.mem_used_pct else None,
                "swap_used_pct": float(r.swap_used_pct) if r.swap_used_pct else None,
                "captured_at": r.captured_at.isoformat() if r.captured_at else None,
                "reasoning": r.ai_reasoning,
            }
            for r in rows.fetchall()
        ]
        by_verdict: dict[str, int] = {}
        for h in hosts:
            key = h["ai_verdict"] or "unknown"
            by_verdict[key] = by_verdict.get(key, 0) + 1

        violations = await db.execute(_sql("""
            SELECT count(*) AS cnt FROM capacity_violation_event
            WHERE detected_at > NOW() - INTERVAL '7 days'
        """))
        return {
            "hosts": hosts,
            "by_verdict": by_verdict,
            "violations_7d": int(violations.scalar() or 0),
        }

    async def _fetch_automation_flow_24h(self, db) -> dict[str, Any]:
        rows = await db.execute(_sql("""
            SELECT operation_type, actor, status, count(*) AS cnt
            FROM automation_operation_log
            WHERE created_at > NOW() - INTERVAL '24 hours'
            GROUP BY operation_type, actor, status
            ORDER BY cnt DESC
        """))
        flows = [
            {"operation_type": r.operation_type, "actor": r.actor, "status": r.status, "count": int(r.cnt)}
            for r in rows.fetchall()
        ]

        by_actor: dict[str, int] = {}
        by_type: dict[str, int] = {}
        for f in flows:
            by_actor[f["actor"]] = by_actor.get(f["actor"], 0) + f["count"]
            by_type[f["operation_type"]] = by_type.get(f["operation_type"], 0) + f["count"]

        return {
            "detail": flows,
            "by_actor": by_actor,
            "by_operation_type": by_type,
            "total": sum(by_type.values()),
        }

    def _compute_autonomy_score(
        self,
        inventory: dict[str, Any],
        coverage: dict[str, Any],
        rule_quality: dict[str, Any],
        capacity: dict[str, Any],
        aol_flow: dict[str, Any],
    ) -> dict[str, Any]:
        """AI 自主化總分 (0-100),5 子項各 20 分."""
        score_coverage = min(20.0, coverage.get("overall_green_ratio", 0.0) * 20)

        total_rules = rule_quality.get("total", 1) or 1
        noisy = rule_quality.get("noisy_above_0_5", 0)
        score_rule = max(0.0, 20 - (noisy / total_rules * 20))

        by_verdict = capacity.get("by_verdict", {})
        critical = by_verdict.get("critical", 0)
        warning = by_verdict.get("warning", 0)
        deductions = min(20, critical * 10 + warning * 3)
        score_capacity = max(0.0, 20 - deductions)

        total_ops_24h = aol_flow.get("total", 0)
        if total_ops_24h > 0:
            score_flow = min(20.0, math.log10(total_ops_24h + 1) / math.log10(101) * 20)
        else:
            score_flow = 0.0

        ai_rules = rule_quality.get("ai_generated", 0)
        op_types = len(aol_flow.get("by_operation_type", {}))
        score_diversity = min(20.0, ai_rules * 1.0 + min(op_types, 10))

        total = score_coverage + score_rule + score_capacity + score_flow + score_diversity

        if total >= 90:
            grade = "mature"
        elif total >= 70:
            grade = "in_progress"
        elif total >= 50:
            grade = "starter"
        else:
            grade = "initial"

        return {
            "total": round(total, 1),
            "grade": grade,
            "breakdown": {
                "asset_coverage": round(score_coverage, 2),
                "rule_quality": round(score_rule, 2),
                "capacity_health": round(score_capacity, 2),
                "automation_flow": round(score_flow, 2),
                "ai_diversity": round(score_diversity, 2),
            },
            "max": 100.0,
        }


_singleton: AiopsKpiService | None = None


def get_aiops_kpi_service() -> AiopsKpiService:
    global _singleton
    if _singleton is None:
        _singleton = AiopsKpiService()
    return _singleton
