"""Cost guardrail detectors (Budgets + Anomaly Detection).

Privacy-first: only aggregate counts are emitted.
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional


def _classify_access_error(e: Exception) -> str:
    msg = str(e).lower()
    if "accessdenied" in msg or "access denied" in msg or "not authorized" in msg:
        return "not_authorized"
    return "other"


def _get_account_id(session) -> Optional[str]:
    try:
        sts = session.client("sts")
        return sts.get_caller_identity().get("Account")
    except Exception:
        return None


def _guardrail_finding(
    *,
    ftype: str,
    summary: str,
    severity: str,
    evidence: Dict[str, Any],
) -> Dict[str, Any]:
    return {
        "type": ftype,
        "resource_type": "account",
        "id": "account",
        "region": None,
        "severity": severity,
        "confidence": 0.9,
        "estimated_monthly_savings_usd": 0,
        "estimated_monthly_cost_usd": None,
        "recommended_action": "enable-cost-guardrails",
        "summary": summary,
        "reason_codes": ["cost_guardrail"],
        "evidence": evidence,
    }


def _guardrail_skipped(
    *,
    check: str,
    service: str,
    explanation: str,
    error: Optional[str] = None,
    error_code: Optional[str] = None,
    error_message: Optional[str] = None,
) -> Dict[str, Any]:
    evidence: Dict[str, Any] = {
        "guardrail": {
            "check": check,
            "service": service,
            "status": "skipped",
            "error": error,
        }
    }
    if error_code:
        evidence["guardrail"]["error_code"] = error_code
    if error_message:
        evidence["guardrail"]["error_message"] = error_message

    return {
        "type": "cost_guardrail_check_skipped",
        "resource_type": "account",
        "id": "account",
        "region": None,
        "severity": "info",
        "confidence": 0.5,
        "estimated_monthly_savings_usd": 0,
        "estimated_monthly_cost_usd": None,
        "recommended_action": "grant-readonly-permissions",
        "summary": explanation,
        "reason_codes": (
            ["cost_guardrail", "skipped", error]
            if error
            else ["cost_guardrail", "skipped"]
        ),
        "evidence": evidence,
    }


def _read_client_error(e: Exception) -> tuple[str, str]:
    try:
        from botocore.exceptions import ClientError

        if isinstance(e, ClientError) and isinstance(e.response, dict):
            err_obj = e.response.get("Error", {}) or {}
            return (err_obj.get("Code") or "", err_obj.get("Message") or "")
    except Exception:
        pass
    return ("", "")


def detect_budgets_guardrail(
    session, *, account_id: Optional[str] = None
) -> List[Dict[str, Any]]:
    if session is None:
        return []
    acct = account_id or _get_account_id(session)
    if not acct:
        return [
            _guardrail_skipped(
                check="budgets",
                service="budgets",
                explanation="Could not determine account id for Budgets check.",
                error="other",
            )
        ]

    budgets: List[Dict[str, Any]] = []
    next_token: Optional[str] = None
    try:
        client = session.client("budgets", region_name="us-east-1")
        while True:
            kwargs: Dict[str, Any] = {"AccountId": acct, "MaxResults": 100}
            if next_token:
                kwargs["NextToken"] = next_token
            resp = client.describe_budgets(**kwargs)
            budgets.extend(resp.get("Budgets", []) or [])
            next_token = resp.get("NextToken")
            if not next_token:
                break
    except Exception as e:
        err = _classify_access_error(e)
        code, message = _read_client_error(e)
        return [
            _guardrail_skipped(
                check="budgets",
                service="budgets",
                explanation="Could not evaluate AWS Budgets configuration.",
                error=err,
                error_code=code or None,
                error_message=message or None,
            )
        ]

    if len(budgets) == 0:
        return [
            _guardrail_finding(
                ftype="cost_guardrail_missing_budgets",
                summary="No AWS Budgets detected. Set a monthly spend budget and alerts.",
                severity="medium",
                evidence={"budgets": {"count": 0}},
            )
        ]

    return []


def detect_anomaly_detection_guardrail(session) -> List[Dict[str, Any]]:
    if session is None:
        return []
    monitors: List[Dict[str, Any]] = []
    subscriptions: List[Dict[str, Any]] = []
    next_token: Optional[str] = None
    try:
        client = session.client("ce", region_name="us-east-1")
        while True:
            kwargs: Dict[str, Any] = {"MaxResults": 100}
            if next_token:
                kwargs["NextPageToken"] = next_token
            resp = client.get_anomaly_monitors(**kwargs)
            monitors.extend(resp.get("AnomalyMonitors", []) or [])
            next_token = resp.get("NextPageToken")
            if not next_token:
                break

        next_token = None
        while True:
            kwargs = {"MaxResults": 100}
            if next_token:
                kwargs["NextPageToken"] = next_token
            resp = client.get_anomaly_subscriptions(**kwargs)
            subscriptions.extend(resp.get("AnomalySubscriptions", []) or [])
            next_token = resp.get("NextPageToken")
            if not next_token:
                break
    except Exception as e:
        err = _classify_access_error(e)
        code, message = _read_client_error(e)
        return [
            _guardrail_skipped(
                check="cost_anomaly_detection",
                service="ce",
                explanation="Could not evaluate Cost Anomaly Detection configuration.",
                error=err,
                error_code=code or None,
                error_message=message or None,
            )
        ]

    monitor_count = len(monitors)
    subscription_count = len(subscriptions)

    if monitor_count == 0 and subscription_count == 0:
        return [
            _guardrail_finding(
                ftype="cost_guardrail_missing_anomaly_detection",
                summary="Cost Anomaly Detection is not configured.",
                severity="medium",
                evidence={
                    "anomaly_detection": {
                        "monitor_count": monitor_count,
                        "subscription_count": subscription_count,
                    }
                },
            )
        ]

    if monitor_count > 0 and subscription_count == 0:
        return [
            _guardrail_finding(
                ftype="cost_guardrail_missing_anomaly_subscriptions",
                summary="Anomaly monitors exist but no notification subscriptions are configured.",
                severity="low",
                evidence={
                    "anomaly_detection": {
                        "monitor_count": monitor_count,
                        "subscription_count": subscription_count,
                    }
                },
            )
        ]

    return []
