"""Cloud intelligence ABC, shared result model, and credential resolution.

Contract:
- CloudIntelligenceAnalyzer is the ABC for all cloud intelligence features.
- analyze() NEVER raises — returns CloudIntelligenceResult with success=False on error.
- resolve_cloud_credentials() resolves credentials per cloud provider.
"""

from __future__ import annotations

import os
from abc import ABC, abstractmethod
from dataclasses import dataclass

from complyform.core.errors import CloudCredentialError

__all__ = [
    "CloudIntelligenceAnalyzer",
    "CloudIntelligenceResult",
    "resolve_cloud_credentials",
]


@dataclass
class CloudIntelligenceResult:
    """Base result for all cloud intelligence features."""

    feature: str  # "iam_analysis" | "cross_validation" | "cost_impact" | "landing_zone"
    cloud: str  # "gcp" | "aws" | "azure"
    success: bool  # True if completed (even partially)
    error: str | None  # None on success, human-readable on failure
    duration_ms: int  # Wall clock time for this feature
    timestamp: str  # ISO 8601


class CloudIntelligenceAnalyzer(ABC):
    """ABC for all cloud intelligence features.

    Contract:
    - __init__ accepts cloud, project_id, credentials (resolved by caller).
    - analyze() returns CloudIntelligenceResult subclass.
    - analyze() NEVER raises — errors caught internally, returned as
      result.success=False.
    - Timeout: 30s default per API call, 120s total per feature.
      Configurable via COMPLYFORM_CI_TIMEOUT env var.
    """

    DEFAULT_CALL_TIMEOUT: int = 30
    DEFAULT_TOTAL_TIMEOUT: int = 120

    def __init__(
        self,
        cloud: str,
        project_id: str,
        credentials: object | None = None,
    ) -> None:
        self.cloud = cloud
        self.project_id = project_id
        self.credentials = credentials
        timeout_env = os.environ.get("COMPLYFORM_CI_TIMEOUT")
        self.total_timeout = (
            int(timeout_env) if timeout_env else self.DEFAULT_TOTAL_TIMEOUT
        )

    @abstractmethod
    def analyze(self) -> CloudIntelligenceResult:
        """Run analysis. Must not raise."""
        ...

    @abstractmethod
    def check_prerequisites(self) -> list[str]:
        """Return list of unmet prerequisites (empty = ready).

        Checks: API enabled, permissions available, analyzer exists (AWS).
        Used by complyform doctor and pre-flight in assess_cmd.
        """
        ...


def resolve_cloud_credentials(
    cloud: str,
    credentials_flag: str | None = None,
) -> object:
    """Resolve credentials for cloud intelligence features.

    Resolution order (same as scanner):
    - GCP: --credentials flag -> GOOGLE_APPLICATION_CREDENTIALS env -> ADC
    - AWS: --credentials flag -> AWS_ACCESS_KEY_ID env ->
      ~/.aws/credentials -> EC2 metadata
    - Azure: --credentials flag -> AZURE_CLIENT_ID env -> DefaultAzureCredential

    Returns opaque credentials object passed to all analyzers.
    Raises CloudCredentialError (error #11) if no valid credentials found.
    """
    if cloud == "gcp":
        return _resolve_gcp_credentials(credentials_flag)
    if cloud == "aws":
        return _resolve_aws_credentials(credentials_flag)
    if cloud == "azure":
        return _resolve_azure_credentials(credentials_flag)
    raise CloudCredentialError(cloud)


def _resolve_gcp_credentials(credentials_flag: str | None) -> object:
    """Resolve GCP credentials via flag, env, or ADC."""
    try:
        if credentials_flag:
            from google.oauth2 import service_account  # type: ignore

            return service_account.Credentials.from_service_account_file(  # type: ignore[no-untyped-call]
                credentials_flag
            )

        import google.auth  # type: ignore

        credentials, _ = google.auth.default()
        return credentials
    except Exception:
        raise CloudCredentialError("gcp") from None


def _resolve_aws_credentials(credentials_flag: str | None) -> object:
    """Resolve AWS credentials via flag, env, or shared credentials."""
    try:
        import boto3  # type: ignore

        if credentials_flag:
            session = boto3.Session(profile_name=credentials_flag)
        else:
            session = boto3.Session()
        # Force credential resolution to detect missing creds early.
        creds = session.get_credentials()
        if creds is None:
            raise CloudCredentialError("aws")
        return session
    except CloudCredentialError:
        raise
    except Exception:
        raise CloudCredentialError("aws") from None


def _resolve_azure_credentials(credentials_flag: str | None) -> object:
    """Resolve Azure credentials via flag, env, or DefaultAzureCredential."""
    try:
        from azure.identity import (  # type: ignore
            ClientSecretCredential,
            DefaultAzureCredential,
        )

        if credentials_flag:
            # credentials_flag expected as "tenant_id:client_id:client_secret"
            parts = credentials_flag.split(":")
            if len(parts) != 3:  # noqa: PLR2004
                raise CloudCredentialError("azure")
            return ClientSecretCredential(
                tenant_id=parts[0],
                client_id=parts[1],
                client_secret=parts[2],
            )
        return DefaultAzureCredential()
    except CloudCredentialError:
        raise
    except Exception:
        raise CloudCredentialError("azure") from None
