"""Prometheus / Alertmanager alert-rule analyzer — 15 rules."""

from __future__ import annotations

import re
from collections import Counter
from typing import List

import yaml

from sre_beacon.models import AlertAuditReport, AlertIssue, AlertIssueSeverity, AlertRule


# ---------------------------------------------------------------------------
# Rule registry (for `rules` command)
# ---------------------------------------------------------------------------

ALERT_RULES = [
    {"id": "SRE-ALT-001", "severity": "HIGH",     "description": "Alert without severity label"},
    {"id": "SRE-ALT-002", "severity": "HIGH",     "description": "Alert without runbook_url annotation"},
    {"id": "SRE-ALT-003", "severity": "MEDIUM",   "description": "Alert without description annotation"},
    {"id": "SRE-ALT-004", "severity": "HIGH",     "description": "Alert without 'for' duration (fires instantly)"},
    {"id": "SRE-ALT-005", "severity": "MEDIUM",   "description": "Critical alert with excessive 'for' duration (>5m)"},
    {"id": "SRE-ALT-006", "severity": "LOW",      "description": "Alert name doesn't follow CamelCase convention"},
    {"id": "SRE-ALT-007", "severity": "INFO",     "description": "Alert expression uses nested functions (complexity)"},
    {"id": "SRE-ALT-008", "severity": "MEDIUM",   "description": "Alert without summary annotation"},
    {"id": "SRE-ALT-009", "severity": "MEDIUM",   "description": "Duplicate alert name detected"},
    {"id": "SRE-ALT-010", "severity": "LOW",      "description": "Alert without team/owner label"},
    {"id": "SRE-ALT-011", "severity": "HIGH",     "description": "Warning alert configured to page (should ticket)"},
    {"id": "SRE-ALT-012", "severity": "CRITICAL",  "description": "Alert expression is empty"},
    {"id": "SRE-ALT-013", "severity": "MEDIUM",   "description": "Alert with very short 'for' duration (<1m)"},
    {"id": "SRE-ALT-014", "severity": "LOW",      "description": "Alert missing 'dashboard_url' annotation"},
    {"id": "SRE-ALT-015", "severity": "INFO",     "description": "Large number of alerts in single group (>15)"},
]


# ---------------------------------------------------------------------------
# Loader
# ---------------------------------------------------------------------------

def load_alert_rules(path: str) -> List[AlertRule]:
    """Load Prometheus alert rules from a YAML file.

    Supports the standard Prometheus rule-file format::

        groups:
          - name: my-group
            rules:
              - alert: HighErrorRate
                expr: rate(http_errors_total[5m]) > 0.05
                for: 5m
                labels:
                  severity: critical
                annotations:
                  summary: High error rate
                  runbook_url: https://...
    """
    with open(path, "r", encoding="utf-8") as fh:
        data = yaml.safe_load(fh)

    rules: List[AlertRule] = []
    for group in data.get("groups", []):
        group_name = group.get("name", "")
        for rule in group.get("rules", []):
            if "alert" not in rule:
                continue  # skip recording rules
            rules.append(
                AlertRule(
                    name=rule["alert"],
                    expr=str(rule.get("expr", "")),
                    for_duration=str(rule.get("for", "")),
                    severity=rule.get("labels", {}).get("severity", ""),
                    labels=rule.get("labels", {}),
                    annotations=rule.get("annotations", {}),
                    file_path=path,
                    group=group_name,
                )
            )
    return rules


# ---------------------------------------------------------------------------
# Analyzer
# ---------------------------------------------------------------------------

def analyze_alert_rules(rules: List[AlertRule]) -> AlertAuditReport:
    """Run all alert-quality checks and return an audit report."""
    issues: List[AlertIssue] = []
    no_runbook = 0
    no_severity = 0
    no_for = 0

    name_counts = Counter(r.name for r in rules)
    duplicates = [name for name, count in name_counts.items() if count > 1]

    # Group size check
    group_counts: dict = {}
    for r in rules:
        group_counts.setdefault(r.group, 0)
        group_counts[r.group] += 1

    for rule in rules:
        # SRE-ALT-012: Empty expression
        if not rule.expr.strip():
            issues.append(_issue("SRE-ALT-012", AlertIssueSeverity.CRITICAL,
                                 f"Alert '{rule.name}' has an empty expression",
                                 rule, "Every alert must have a valid PromQL expression"))
            continue

        # SRE-ALT-001: No severity label
        if not rule.severity:
            no_severity += 1
            issues.append(_issue("SRE-ALT-001", AlertIssueSeverity.HIGH,
                                 f"Alert '{rule.name}' has no severity label",
                                 rule, "Add labels.severity (critical/warning/info)"))

        # SRE-ALT-002: No runbook_url
        if "runbook_url" not in rule.annotations:
            no_runbook += 1
            issues.append(_issue("SRE-ALT-002", AlertIssueSeverity.HIGH,
                                 f"Alert '{rule.name}' has no runbook_url annotation",
                                 rule, "Add annotations.runbook_url linking to the runbook"))

        # SRE-ALT-003: No description
        if "description" not in rule.annotations:
            issues.append(_issue("SRE-ALT-003", AlertIssueSeverity.MEDIUM,
                                 f"Alert '{rule.name}' has no description annotation",
                                 rule, "Add annotations.description explaining the alert"))

        # SRE-ALT-004: No 'for' duration
        if not rule.for_duration:
            no_for += 1
            issues.append(_issue("SRE-ALT-004", AlertIssueSeverity.HIGH,
                                 f"Alert '{rule.name}' has no 'for' duration — fires on first evaluation",
                                 rule, "Add a 'for' duration (e.g. 5m) to avoid flapping"))

        # SRE-ALT-005: Critical with excessive 'for'
        if rule.severity == "critical" and rule.for_duration:
            minutes = _parse_duration_minutes(rule.for_duration)
            if minutes is not None and minutes > 5:
                issues.append(_issue("SRE-ALT-005", AlertIssueSeverity.MEDIUM,
                                     f"Critical alert '{rule.name}' has 'for: {rule.for_duration}' (>5m delay)",
                                     rule, "Critical alerts should fire within 5 minutes"))

        # SRE-ALT-006: Name convention
        if not re.match(r'^[A-Z][a-zA-Z0-9]+$', rule.name):
            issues.append(_issue("SRE-ALT-006", AlertIssueSeverity.LOW,
                                 f"Alert name '{rule.name}' doesn't follow CamelCase convention",
                                 rule, "Use CamelCase for alert names (e.g. HighErrorRate)"))

        # SRE-ALT-007: Nested functions (complexity)
        nested_count = rule.expr.count("(") - rule.expr.count(")")
        func_count = len(re.findall(r'[a-z_]+\(', rule.expr))
        if func_count >= 4:
            issues.append(_issue("SRE-ALT-007", AlertIssueSeverity.INFO,
                                 f"Alert '{rule.name}' has complex expression ({func_count} functions)",
                                 rule, "Consider using recording rules for complex expressions"))

        # SRE-ALT-008: No summary
        if "summary" not in rule.annotations:
            issues.append(_issue("SRE-ALT-008", AlertIssueSeverity.MEDIUM,
                                 f"Alert '{rule.name}' has no summary annotation",
                                 rule, "Add annotations.summary for notification messages"))

        # SRE-ALT-009: Duplicate name
        if name_counts[rule.name] > 1:
            issues.append(_issue("SRE-ALT-009", AlertIssueSeverity.MEDIUM,
                                 f"Alert name '{rule.name}' is duplicated ({name_counts[rule.name]} times)",
                                 rule, "Each alert should have a unique name"))

        # SRE-ALT-010: No team/owner
        if "team" not in rule.labels and "owner" not in rule.labels:
            issues.append(_issue("SRE-ALT-010", AlertIssueSeverity.LOW,
                                 f"Alert '{rule.name}' has no team/owner label",
                                 rule, "Add labels.team or labels.owner for routing"))

        # SRE-ALT-011: Warning alert paging
        if rule.severity == "warning":
            action = rule.labels.get("action", "") or rule.annotations.get("action", "")
            if action in ("page", "pager"):
                issues.append(_issue("SRE-ALT-011", AlertIssueSeverity.HIGH,
                                     f"Warning alert '{rule.name}' is configured to page",
                                     rule, "Warning alerts should create tickets, not page"))

        # SRE-ALT-013: Very short 'for' duration
        if rule.for_duration:
            minutes = _parse_duration_minutes(rule.for_duration)
            if minutes is not None and 0 < minutes < 1:
                issues.append(_issue("SRE-ALT-013", AlertIssueSeverity.MEDIUM,
                                     f"Alert '{rule.name}' has very short 'for: {rule.for_duration}'",
                                     rule, "Very short durations may cause alert flapping"))

        # SRE-ALT-014: No dashboard_url
        if "dashboard_url" not in rule.annotations:
            issues.append(_issue("SRE-ALT-014", AlertIssueSeverity.LOW,
                                 f"Alert '{rule.name}' has no dashboard_url annotation",
                                 rule, "Add annotations.dashboard_url for quick debugging"))

    # SRE-ALT-015: Large groups
    for group_name, count in group_counts.items():
        if count > 15:
            issues.append(AlertIssue(
                rule_id="SRE-ALT-015",
                severity=AlertIssueSeverity.INFO,
                message=f"Alert group '{group_name}' has {count} rules (>15)",
                alert_name=f"group:{group_name}",
                suggestion="Split large groups for better organization",
            ))

    return AlertAuditReport(
        total_rules=len(rules),
        issues=issues,
        rules_without_runbook=no_runbook,
        rules_without_severity=no_severity,
        rules_without_for=no_for,
        duplicate_names=duplicates,
    )


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _issue(rule_id: str, severity: AlertIssueSeverity, message: str,
           rule: AlertRule, suggestion: str) -> AlertIssue:
    return AlertIssue(
        rule_id=rule_id,
        severity=severity,
        message=message,
        alert_name=rule.name,
        file_path=rule.file_path,
        suggestion=suggestion,
    )


def _parse_duration_minutes(duration_str: str) -> float | None:
    """Parse Prometheus duration string (e.g. '5m', '1h', '30s') to minutes."""
    match = re.match(r'^(\d+(?:\.\d+)?)(s|m|h|d)$', duration_str.strip())
    if not match:
        return None
    value = float(match.group(1))
    unit = match.group(2)
    if unit == "s":
        return value / 60
    if unit == "m":
        return value
    if unit == "h":
        return value * 60
    if unit == "d":
        return value * 60 * 24
    return None
