"""GCP IAM Recommender API v1 client.

Uses httpx + google-auth for lightweight API access.
Queries recommendations and paired insights for permission usage data.
"""

from __future__ import annotations

import time
from datetime import UTC, datetime
from typing import Any

import httpx

from complyform.core.cloud_intelligence import (
    CloudIntelligenceAnalyzer,
)
from complyform.core.cloud_intelligence.iam_analyzer import (
    IAMAnalysisResult,
    IAMFinding,
    classify_risk_level,
)

__all__ = ["GCPIAMAnalyzer"]

_RECOMMENDER_BASE = "https://recommender.googleapis.com/v1"
_SAFETY_CAP = 500
_PAGE_SIZE = 100


class GCPIAMAnalyzer(CloudIntelligenceAnalyzer):
    """GCP IAM Recommender API v1 client.

    Endpoints:
    - GET .../recommenders/google.iam.policy.Recommender/recommendations
    - GET .../insightTypes/google.iam.policy.Insight/insights

    Pagination: follow nextPageToken until exhausted or 500 principals (safety cap).
    Rate limits: 600 req/min per project. No throttling needed.
    """

    def analyze(self) -> IAMAnalysisResult:
        """Query IAM Recommender and return findings. Never raises."""
        start = time.monotonic()
        try:
            return self._do_analyze(start)
        except Exception as exc:
            duration = int((time.monotonic() - start) * 1000)
            return IAMAnalysisResult(
                feature="iam_analysis",
                cloud="gcp",
                success=False,
                error=str(exc),
                duration_ms=duration,
                timestamp=datetime.now(tz=UTC).isoformat(),
            )

    def _do_analyze(self, start: float) -> IAMAnalysisResult:
        """Internal analysis implementation."""
        recommendations = self._fetch_recommendations()
        insights = self._fetch_insights()
        insight_map = self._build_insight_map(insights)

        findings: list[IAMFinding] = []
        principals_seen: set[str] = set()

        for rec in recommendations:
            parsed = self._parse_recommendation(rec, insight_map)
            if parsed:
                findings.append(parsed)
                principals_seen.add(parsed.principal)

        duration = int((time.monotonic() - start) * 1000)
        over_provisioned = sum(1 for f in findings if f.recommended_role is not None)
        unused = sum(1 for f in findings if f.recommended_role is None)

        return IAMAnalysisResult(
            feature="iam_analysis",
            cloud="gcp",
            success=True,
            error=None,
            duration_ms=duration,
            timestamp=datetime.now(tz=UTC).isoformat(),
            findings=findings,
            principals_scanned=len(principals_seen),
            over_provisioned_count=over_provisioned,
            unused_role_count=unused,
        )

    def _get_auth_headers(self) -> dict[str, str]:
        """Get authorization headers from credentials."""
        if self.credentials is None:
            return {}
        # google-auth credentials have a token property after refresh
        import google.auth.transport.requests  # type: ignore

        request = google.auth.transport.requests.Request()
        self.credentials.refresh(request)  # type: ignore[attr-defined]
        return {"Authorization": f"Bearer {self.credentials.token}"}  # type: ignore[attr-defined]

    def _fetch_recommendations(self) -> list[dict[str, Any]]:
        """Fetch all active IAM policy recommendations with pagination."""
        url = (
            f"{_RECOMMENDER_BASE}/projects/{self.project_id}"
            f"/locations/-/recommenders/google.iam.policy.Recommender/recommendations"
        )
        params: dict[str, Any] = {
            "filter": "stateInfo.state=ACTIVE",
            "pageSize": _PAGE_SIZE,
        }
        headers = self._get_auth_headers()
        results: list[dict[str, Any]] = []

        with httpx.Client(timeout=self.DEFAULT_CALL_TIMEOUT) as client:
            while len(results) < _SAFETY_CAP:
                response = self._request_with_retry(client, url, params, headers)
                data = response.json()
                recs = data.get("recommendations", [])
                results.extend(recs)
                token = data.get("nextPageToken")
                if not token or len(results) >= _SAFETY_CAP:
                    break
                params["pageToken"] = token

        return results[:_SAFETY_CAP]

    def _fetch_insights(self) -> list[dict[str, Any]]:
        """Fetch IAM policy insights for permission usage data."""
        url = (
            f"{_RECOMMENDER_BASE}/projects/{self.project_id}"
            f"/locations/-/insightTypes/google.iam.policy.Insight/insights"
        )
        params: dict[str, Any] = {"pageSize": _PAGE_SIZE}
        headers = self._get_auth_headers()
        results: list[dict[str, Any]] = []

        with httpx.Client(timeout=self.DEFAULT_CALL_TIMEOUT) as client:
            while len(results) < _SAFETY_CAP:
                response = self._request_with_retry(client, url, params, headers)
                data = response.json()
                items = data.get("insights", [])
                results.extend(items)
                token = data.get("nextPageToken")
                if not token or len(results) >= _SAFETY_CAP:
                    break
                params["pageToken"] = token

        return results[:_SAFETY_CAP]

    def _request_with_retry(
        self,
        client: httpx.Client,
        url: str,
        params: dict[str, Any],
        headers: dict[str, str],
    ) -> httpx.Response:
        """Make HTTP request with retry for 429 and 5xx."""
        response = client.get(url, params=params, headers=headers)
        if response.status_code == 429:  # noqa: PLR2004
            time.sleep(5)
            response = client.get(url, params=params, headers=headers)
        elif response.status_code >= 500:  # noqa: PLR2004
            time.sleep(2)
            response = client.get(url, params=params, headers=headers)
        response.raise_for_status()
        return response

    def _build_insight_map(
        self,
        insights: list[dict[str, Any]],
    ) -> dict[str, dict[str, Any]]:
        """Build map of insight name → insight data for lookup."""
        result: dict[str, dict[str, Any]] = {}
        for insight in insights:
            name = insight.get("name", "")
            result[name] = insight
        return result

    def _parse_recommendation(
        self,
        rec: dict[str, Any],
        insight_map: dict[str, dict[str, Any]],
    ) -> IAMFinding | None:
        """Parse a single recommendation into an IAMFinding."""
        subtype = rec.get("recommenderSubtype", "")
        content = rec.get("content", {})
        op_groups = content.get("operationGroups", [])

        principal = ""
        current_role = ""
        recommended_role: str | None = None

        for group in op_groups:
            for op in group.get("operations", []):
                resource = op.get("resource", "")
                path_filters = op.get("pathFilters", {})
                value = op.get("value")

                if not principal and resource:
                    principal = resource

                for key, val in path_filters.items():
                    if "role" in key.lower() and isinstance(val, str):
                        current_role = val

                if (
                    subtype == "REPLACE_ROLE"
                    and value
                    or subtype == "REPLACE_ROLE_CUSTOMIZABLE"
                    and value
                ):
                    recommended_role = str(value)
                # REMOVE_ROLE → recommended_role stays None

        if not principal:
            return None

        # Get insight data for unused permissions and last activity.
        unused_permissions: list[str] = []
        last_activity: str | None = None
        associated_insights = rec.get("associatedInsights", [])
        for assoc in associated_insights:
            insight_name = assoc.get("insight", "")
            insight = insight_map.get(insight_name, {})
            insight_content = insight.get("content", {})
            # Unused permissions from condition expression.
            condition = insight_content.get("condition", {})
            expr = condition.get("expression", "")
            if expr:
                unused_permissions = [p.strip() for p in expr.split(",") if p.strip()]
            # Last activity from state metadata.
            state_info = insight.get("stateInfo", {})
            state_meta = state_info.get("stateMetadata", {})
            refresh_time = state_meta.get("lastRefreshTime")
            if refresh_time:
                last_activity = refresh_time

        risk = classify_risk_level(current_role, last_activity, unused_permissions)

        return IAMFinding(
            cloud="gcp",
            principal=principal,
            principal_type="service_account",
            current_role=current_role,
            recommended_role=recommended_role,
            unused_permissions=unused_permissions,
            last_activity=last_activity,
            risk_level=risk,
            framework_controls=[],
            remediation_terraform=None,
            source_api="iam_recommender",
        )

    def check_prerequisites(self) -> list[str]:
        """Check if IAM Recommender API is enabled and accessible."""
        url = (
            f"{_RECOMMENDER_BASE}/projects/{self.project_id}"
            f"/locations/-/recommenders/google.iam.policy.Recommender/recommendations"
        )
        params = {"pageSize": 1}
        headers = self._get_auth_headers()

        try:
            with httpx.Client(timeout=self.DEFAULT_CALL_TIMEOUT) as client:
                response = client.get(url, params=params, headers=headers)
        except Exception as exc:
            return [f"Cannot reach IAM Recommender API: {exc}"]

        if response.status_code == 200:  # noqa: PLR2004
            return []

        try:
            body = response.json()
        except Exception:
            return [f"IAM Recommender API returned HTTP {response.status_code}"]

        error = body.get("error", {})
        status = error.get("status", "")
        message = error.get("message", "")

        msg_lower = message.lower()
        body_lower = str(body).lower()
        api_not_enabled = (
            "not enabled" in msg_lower
            or "not been enabled" in msg_lower
            or "not enabled" in body_lower
            or "not been enabled" in body_lower
        )
        if api_not_enabled:
            return [
                f"Recommender API not enabled. Run: "
                f"gcloud services enable recommender.googleapis.com "
                f"--project={self.project_id}"
            ]
        if status == "PERMISSION_DENIED" or response.status_code == 403:  # noqa: PLR2004
            return [f"Missing roles/recommender.iamViewer on project {self.project_id}"]

        return [f"IAM Recommender API returned HTTP {response.status_code}: {message}"]
