"""AWS IAM Access Analyzer ListFindingsV2 client.

Uses boto3 accessanalyzer client to query unused access findings.
"""

from __future__ import annotations

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

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

__all__ = ["AWSIAMAnalyzer"]

_SAFETY_CAP = 500
_FINDING_TYPES = [
    "UnusedIAMRole",
    "UnusedIAMUserAccessKey",
    "UnusedIAMUserPassword",
    "UnusedPermission",
]


class AWSIAMAnalyzer(CloudIntelligenceAnalyzer):
    """AWS IAM Access Analyzer ListFindingsV2 client.

    Pre-requisite: an UNUSED_ACCESS type analyzer must exist.
    Queries active findings of types: UnusedIAMRole, UnusedIAMUserAccessKey,
    UnusedIAMUserPassword, UnusedPermission.
    """

    def analyze(self) -> IAMAnalysisResult:
        """Query Access Analyzer 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="aws",
                success=False,
                error=str(exc),
                duration_ms=duration,
                timestamp=datetime.now(tz=UTC).isoformat(),
            )

    def _do_analyze(self, start: float) -> IAMAnalysisResult:
        """Internal analysis implementation."""
        import boto3  # type: ignore

        session = self.credentials if self.credentials else boto3.Session()
        client = session.client("accessanalyzer")  # type: ignore[union-attr]

        # Find unused access analyzer.
        analyzer_arn = self._find_analyzer(client)
        if analyzer_arn is None:
            duration = int((time.monotonic() - start) * 1000)
            return IAMAnalysisResult(
                feature="iam_analysis",
                cloud="aws",
                success=False,
                error=(
                    "No unused access analyzer found. Create one: "
                    "aws accessanalyzer create-analyzer "
                    "--analyzer-name complyform --type ACCOUNT_UNUSED_ACCESS"
                ),
                duration_ms=duration,
                timestamp=datetime.now(tz=UTC).isoformat(),
            )

        raw_findings = self._fetch_findings(client, analyzer_arn)
        findings = self._parse_findings(raw_findings)

        duration = int((time.monotonic() - start) * 1000)
        principals_seen: set[str] = set()
        for f in findings:
            principals_seen.add(f.principal)

        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="aws",
            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 _find_analyzer(self, client: Any) -> str | None:
        """Find an UNUSED_ACCESS type analyzer, return its ARN or None."""
        response = client.list_analyzers(type="ACCOUNT")
        analyzers = response.get("analyzers", [])
        for analyzer in analyzers:
            if analyzer.get("type") == "UNUSED_ACCESS":
                return str(analyzer["arn"])
        return None

    def _fetch_findings(
        self,
        client: Any,
        analyzer_arn: str,
    ) -> list[dict[str, Any]]:
        """Fetch active unused access findings with pagination and retry."""
        results: list[dict[str, Any]] = []
        kwargs: dict[str, Any] = {
            "analyzerArn": analyzer_arn,
            "filter": {
                "findingType": {"eq": _FINDING_TYPES},
                "status": {"eq": ["ACTIVE"]},
            },
            "maxResults": 100,
            "sort": {"attributeName": "UpdatedAt", "orderBy": "DESC"},
        }

        while len(results) < _SAFETY_CAP:
            try:
                response = self._call_with_backoff(client, kwargs)
            except Exception:
                break
            items = response.get("findings", [])
            results.extend(items)
            token = response.get("nextToken")
            if not token or len(results) >= _SAFETY_CAP:
                break
            kwargs["nextToken"] = token

        return results[:_SAFETY_CAP]

    def _call_with_backoff(
        self,
        client: Any,
        kwargs: dict[str, Any],
    ) -> dict[str, Any]:
        """Call list_findings_v2 with exponential backoff for throttling."""
        delays = [1, 2, 4]
        last_exc: Exception | None = None
        for attempt in range(len(delays) + 1):
            try:
                return client.list_findings_v2(**kwargs)  # type: ignore[no-any-return]
            except Exception as exc:
                error_code = getattr(exc, "response", {})
                if isinstance(error_code, dict):
                    error_code = error_code.get("Error", {}).get("Code", "")
                else:
                    error_code = ""
                if error_code == "ThrottlingException" and attempt < len(delays):
                    time.sleep(delays[attempt])
                    last_exc = exc
                    continue
                raise
        if last_exc:
            raise last_exc
        msg = "Unexpected backoff exhaustion"
        raise RuntimeError(msg)

    def _parse_findings(
        self,
        raw_findings: list[dict[str, Any]],
    ) -> list[IAMFinding]:
        """Parse raw Access Analyzer findings into IAMFinding objects."""
        results: list[IAMFinding] = []
        for raw in raw_findings:
            finding_type = raw.get("findingType", "")
            resource = raw.get("resource", "")
            updated_at = raw.get("updatedAt")

            last_activity: str | None = None
            if updated_at:
                if isinstance(updated_at, datetime):
                    last_activity = updated_at.isoformat()
                else:
                    last_activity = str(updated_at)

            unused_permissions: list[str] = []
            current_role = resource
            recommended_role: str | None = None

            if finding_type == "UnusedPermission":
                details = raw.get("findingDetails", [])
                if details:
                    detail = details[0] if isinstance(details, list) else details
                    unused_details = detail.get("unusedPermissionDetails", {})
                    if isinstance(unused_details, dict):
                        ns = unused_details.get("serviceNamespace", "")
                        actions = unused_details.get("actions", [])
                        unused_permissions = (
                            [f"{ns}:{a}" for a in actions] if actions else [ns]
                        )
                recommended_role = "scoped_policy"
            elif finding_type == "UnusedIAMRole":
                last_activity = None  # Unused role — no activity
                recommended_role = None  # Remove
            elif finding_type in ("UnusedIAMUserAccessKey", "UnusedIAMUserPassword"):
                last_activity = None
                recommended_role = None

            principal_type = "role" if "role" in resource.lower() else "user"
            risk = classify_risk_level(current_role, last_activity, unused_permissions)

            results.append(
                IAMFinding(
                    cloud="aws",
                    principal=resource,
                    principal_type=principal_type,
                    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="access_analyzer",
                )
            )
        return results

    def check_prerequisites(self) -> list[str]:
        """Check if Access Analyzer is set up with an UNUSED_ACCESS analyzer."""
        issues: list[str] = []
        try:
            import boto3

            session = self.credentials if self.credentials else boto3.Session()
            client = session.client("accessanalyzer")  # type: ignore[union-attr]
            analyzer_arn = self._find_analyzer(client)
            if analyzer_arn is None:
                issues.append(
                    "No unused access analyzer found. Create one: "
                    "aws accessanalyzer create-analyzer "
                    "--analyzer-name complyform --type ACCOUNT_UNUSED_ACCESS"
                )
        except Exception as exc:
            error_code = ""
            response = getattr(exc, "response", None)
            if isinstance(response, dict):
                error_code = response.get("Error", {}).get("Code", "")
            if error_code == "AccessDeniedException":
                issues.append(
                    "Missing permission: access-analyzer:ListAnalyzers. Attach policy."
                )
            else:
                issues.append(f"Cannot reach Access Analyzer API: {exc}")
        return issues
