"""Azure IAM analyzer using Activity Log + Microsoft Graph.

Two-phase query:
Phase 1: List role assignments + check Activity Log for recent activity (90 days).
Phase 2: Query service principal app role assignments via Graph (optional).
"""

from __future__ import annotations

import time
from datetime import UTC, datetime, timedelta
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__ = ["AzureIAMAnalyzer"]

_AZURE_ROLE_MAP: dict[str, str] = {
    "Owner": "Owner",
    "Contributor": "Contributor",
    "Reader": "Reader",
}


class AzureIAMAnalyzer(CloudIntelligenceAnalyzer):
    """Azure IAM analyzer using Activity Log + Microsoft Graph.

    SDKs: azure-identity, azure-mgmt-authorization, azure-mgmt-monitor.
    msgraph-sdk-python is optional — Phase 2 skipped if not installed.
    """

    def analyze(self) -> IAMAnalysisResult:
        """Query Activity Log and Graph for IAM 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="azure",
                success=False,
                error=str(exc),
                duration_ms=duration,
                timestamp=datetime.now(tz=UTC).isoformat(),
            )

    def _do_analyze(self, start: float) -> IAMAnalysisResult:
        """Internal analysis implementation."""
        from azure.mgmt.authorization import (  # type: ignore
            AuthorizationManagementClient,
        )
        from azure.mgmt.monitor import (  # type: ignore
            MonitorManagementClient,
        )

        auth_client = AuthorizationManagementClient(
            credential=self.credentials,  # type: ignore[arg-type]
            subscription_id=self.project_id,
        )
        monitor_client = MonitorManagementClient(
            credential=self.credentials,  # type: ignore[arg-type]
            subscription_id=self.project_id,
        )

        # Phase 1: role assignments + activity log
        findings = self._phase1_role_assignments(auth_client, monitor_client)

        # Phase 2: Graph API (optional)
        graph_findings = self._phase2_graph()
        findings.extend(graph_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="azure",
            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 _phase1_role_assignments(
        self,
        auth_client: Any,
        monitor_client: Any,
    ) -> list[IAMFinding]:
        """Phase 1: List role assignments and check Activity Log."""
        findings: list[IAMFinding] = []
        assignments = list(auth_client.role_assignments.list_for_subscription())

        ninety_days_ago = datetime.now(tz=UTC) - timedelta(days=90)
        filter_start = ninety_days_ago.strftime("%Y-%m-%dT%H:%M:%SZ")

        for assignment in assignments:
            principal_id = assignment.principal_id
            role_id = assignment.role_definition_id or ""
            role_name = self._resolve_role_name(role_id)
            principal_type = getattr(assignment, "principal_type", "user")
            principal_type = principal_type.lower() if principal_type else "user"

            # Query Activity Log for this principal.
            last_activity: str | None = None
            try:
                log_filter = (
                    f"eventTimestamp ge '{filter_start}' and caller eq '{principal_id}'"
                )
                logs = list(monitor_client.activity_logs.list(filter=log_filter))
                if logs:
                    latest = max(logs, key=lambda x: x.event_timestamp)
                    last_activity = latest.event_timestamp.isoformat()
            except Exception:
                pass  # Activity log query failure is non-fatal

            unused_permissions: list[str] = []
            recommended_role: str | None = None
            if last_activity is None and role_name in ("Owner", "Contributor"):
                recommended_role = None  # Remove — unused broad role
            elif role_name == "Owner":
                recommended_role = "Contributor"
            elif role_name == "Contributor":
                recommended_role = "Reader"

            risk = classify_risk_level(role_name, last_activity, unused_permissions)

            findings.append(
                IAMFinding(
                    cloud="azure",
                    principal=principal_id,
                    principal_type=principal_type,
                    current_role=role_name,
                    recommended_role=recommended_role,
                    unused_permissions=unused_permissions,
                    last_activity=last_activity,
                    risk_level=risk,
                    framework_controls=[],
                    remediation_terraform=None,
                    source_api="activity_log",
                )
            )

        return findings

    def _resolve_role_name(self, role_definition_id: str) -> str:
        """Extract role name from role definition ID."""
        # Role definition IDs end with the role name GUID; use known mappings.
        for name in _AZURE_ROLE_MAP:
            if name.lower() in role_definition_id.lower():
                return name
        # Return last segment as fallback.
        parts = role_definition_id.rsplit("/", 1)
        return parts[-1] if parts else role_definition_id

    def _phase2_graph(self) -> list[IAMFinding]:
        """Phase 2: Query Graph API for SP role assignments."""
        try:
            from msgraph import GraphServiceClient  # type: ignore
        except ImportError:
            return []

        try:
            graph_client = GraphServiceClient(credentials=self.credentials)  # type: ignore[arg-type]
            # Query service principal app role assignments.
            sp_assignments = graph_client.service_principals.get()
            findings: list[IAMFinding] = []
            if sp_assignments and hasattr(sp_assignments, "value"):
                for sp in sp_assignments.value:
                    sp_id = getattr(sp, "id", "")
                    display_name = getattr(sp, "display_name", sp_id)
                    app_roles = getattr(sp, "app_role_assignments", [])
                    if app_roles:
                        for role_assignment in app_roles:
                            role_name = getattr(
                                role_assignment, "resource_display_name", "Unknown"
                            )
                            findings.append(
                                IAMFinding(
                                    cloud="azure",
                                    principal=display_name,
                                    principal_type="service_account",
                                    current_role=role_name,
                                    recommended_role=None,
                                    unused_permissions=[],
                                    last_activity=None,
                                    risk_level=classify_risk_level(role_name, None, []),
                                    framework_controls=[],
                                    remediation_terraform=None,
                                    source_api="activity_log",
                                )
                            )
            return findings
        except Exception:
            return []

    def check_prerequisites(self) -> list[str]:
        """Check Azure authentication and permissions."""
        issues: list[str] = []
        try:
            from azure.identity import (  # type: ignore
                DefaultAzureCredential,
            )
            from azure.mgmt.authorization import (
                AuthorizationManagementClient,
            )
        except ImportError:
            issues.append("Azure SDK not installed. Run: pip install complyform[azure]")
            return issues

        try:
            cred = self.credentials or DefaultAzureCredential()
            auth_client = AuthorizationManagementClient(
                credential=cred,  # type: ignore[arg-type]
                subscription_id=self.project_id,
            )
            # Try listing role assignments to verify access.
            next(iter(auth_client.role_assignments.list_for_subscription()), None)
        except Exception as exc:
            exc_type = type(exc).__name__
            if "AuthenticationError" in exc_type:
                issues.append("Azure authentication failed. Run: az login")
            elif "HttpResponseError" in exc_type:
                status = getattr(exc, "status_code", 0)
                if status == 403:  # noqa: PLR2004
                    issues.append(
                        f"Missing Reader role on subscription {self.project_id}"
                    )
                elif status == 429:  # noqa: PLR2004
                    issues.append("Azure API rate limited. Try again later.")
                else:
                    issues.append(f"Azure API error: {exc}")
            else:
                issues.append(f"Azure API error: {exc}")
        return issues
