"""GCP service account authentication for MCA SDK.

This module provides GCP authentication via service accounts with support for:
- Service account JSON files
- JSON credentials as environment variables
- Application Default Credentials (ADC) fallback
- Automatic token refresh
"""

import json
import logging
import re
from dataclasses import dataclass
from typing import Optional, Dict, Any, List, TYPE_CHECKING

if TYPE_CHECKING:
    from google.auth.credentials import Credentials as GoogleCredentials

logger = logging.getLogger(__name__)


@dataclass
class GCPCredentials:
    """GCP service account credentials.

    SECURITY NOTE: This object stores private_key in plaintext in heap memory
    for the application lifetime. This is standard Python practice for credential
    handling and necessary for google.auth integration. The private key will be
    accessible in memory dumps or debugger inspection. For higher security needs:
    - Use short-lived credentials and rotation
    - Consider hardware security modules (HSM)
    - Avoid long-running processes with credentials

    Attributes:
        type: Account type ("service_account" or "adc")
        project_id: GCP project ID
        private_key_id: Private key identifier
        private_key: Private key for authentication (plaintext, stored in memory)
        client_email: Service account email
        client_id: Client ID
        auth_uri: OAuth2 auth URI
        token_uri: OAuth2 token URI
        auth_provider_x509_cert_url: Auth provider cert URL
        client_x509_cert_url: Client cert URL
        google_credentials: Underlying google.auth credentials object
    """

    type: str
    project_id: str
    private_key_id: str
    private_key: str
    client_email: str
    client_id: str
    auth_uri: str
    token_uri: str
    auth_provider_x509_cert_url: str
    client_x509_cert_url: str
    google_credentials: Optional["GoogleCredentials"] = None

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "GCPCredentials":
        """Create GCPCredentials from dictionary.

        Args:
            data: Service account JSON as dictionary

        Returns:
            GCPCredentials instance

        Raises:
            ConfigurationError: If required fields are missing
        """
        from ..utils.exceptions import ConfigurationError

        required_fields = [
            "type",
            "project_id",
            "private_key_id",
            "private_key",
            "client_email",
            "client_id",
            "auth_uri",
            "token_uri",
            "auth_provider_x509_cert_url",
            "client_x509_cert_url",
        ]

        missing_fields = [f for f in required_fields if f not in data]
        if missing_fields:
            raise ConfigurationError(
                f"Invalid service account JSON: missing required fields: {', '.join(missing_fields)}"
            )

        return cls(
            type=data["type"],
            project_id=data["project_id"],
            private_key_id=data["private_key_id"],
            private_key=data["private_key"],
            client_email=data["client_email"],
            client_id=data["client_id"],
            auth_uri=data["auth_uri"],
            token_uri=data["token_uri"],
            auth_provider_x509_cert_url=data["auth_provider_x509_cert_url"],
            client_x509_cert_url=data["client_x509_cert_url"],
        )

    def to_safe_dict(self) -> Dict[str, Any]:
        """Convert to dictionary with sensitive data masked.

        For logging and display purposes only. Not suitable for serialization.
        Keeps project_id and client_email visible for debugging.

        Returns:
            Dictionary representation with private key redacted
        """
        return {
            "type": self.type,
            "project_id": self.project_id,
            "private_key_id": "[REDACTED]",
            "private_key": "[REDACTED]",
            "client_email": self.client_email,
            "client_id": "[REDACTED]",
            "auth_uri": self.auth_uri,
            "token_uri": self.token_uri,
            "auth_provider_x509_cert_url": self.auth_provider_x509_cert_url,
            "client_x509_cert_url": "[REDACTED]",
        }

    def __repr__(self) -> str:
        """Mask sensitive information in repr (keeps identifiers for debugging)."""
        return f"GCPCredentials(project_id={self.project_id!r}, client_email={self.client_email!r}, type={self.type!r})"


def _create_credentials_from_json_string(json_string: str, scopes: List[str]) -> GCPCredentials:
    """Create GCPCredentials from JSON string (internal helper).

    This function must be used with SensitiveString callback pattern to ensure
    the parsed dict (containing private_key) never escapes to outer scope.

    Args:
        json_string: Service account JSON as string
        scopes: OAuth scopes to request

    Returns:
        Fully initialized GCPCredentials

    Raises:
        ConfigurationError: If JSON is invalid or credentials cannot be created
    """
    from ..utils.exceptions import ConfigurationError

    # Parse JSON
    try:
        data = json.loads(json_string)
    except json.JSONDecodeError as e:
        raise ConfigurationError(f"Invalid JSON in GCP credentials string. Error: {e}")

    # Create credentials object
    creds = GCPCredentials.from_dict(data)
    validate_credentials(creds)

    # Create google.auth credentials
    try:
        from google.oauth2 import service_account

        google_creds = service_account.Credentials.from_service_account_info(data, scopes=scopes)
        creds.google_credentials = google_creds
    except ImportError as e:
        if "google.oauth2" in str(e) or "google-auth" in str(e):
            raise ConfigurationError(
                "google-auth library not installed. "
                "Install with: pip install mca-sdk[gcp] or pip install google-auth"
            )
        else:
            raise ConfigurationError(f"Failed to import google-auth: {e}")

    return creds


def load_credentials(
    credentials_path: Optional[str] = None,
    credentials_json: Optional[str] = None,
    scopes: Optional[List[str]] = None,
) -> Optional[GCPCredentials]:
    """Load GCP credentials from file, JSON string, or ADC.

    Priority order:
    1. credentials_path (service account JSON file)
    2. credentials_json (service account JSON as string)
    3. Application Default Credentials (ADC) fallback

    Args:
        credentials_path: Path to service account JSON file
        credentials_json: Service account JSON as string
        scopes: OAuth scopes to request. Defaults to monitoring, logging, trace.

    Returns:
        GCPCredentials instance or None if ADC unavailable

    Raises:
        ConfigurationError: If credentials are invalid or cannot be loaded
    """
    if scopes is None:
        scopes = [
            "https://www.googleapis.com/auth/monitoring.write",
            "https://www.googleapis.com/auth/logging.write",
            "https://www.googleapis.com/auth/trace.append",
        ]
    from ..utils.exceptions import ConfigurationError

    # Priority 1: Load from file
    if credentials_path:
        try:
            with open(credentials_path, "r") as f:
                data = json.load(f)
        except FileNotFoundError:
            raise ConfigurationError(f"GCP credentials file not found: {credentials_path}")
        except PermissionError:
            raise ConfigurationError(
                f"Permission denied reading credentials file: {credentials_path}"
            )
        except json.JSONDecodeError as e:
            raise ConfigurationError(f"Invalid JSON in credentials file {credentials_path}: {e}")
        except IOError as e:
            raise ConfigurationError(f"I/O error reading credentials file {credentials_path}: {e}")

        creds = GCPCredentials.from_dict(data)
        validate_credentials(creds)

        # Create google.auth credentials
        try:
            from google.oauth2 import service_account

            google_creds = service_account.Credentials.from_service_account_info(
                data, scopes=scopes
            )
            creds.google_credentials = google_creds
        except ImportError as e:
            if "google.oauth2" in str(e) or "google-auth" in str(e):
                raise ConfigurationError(
                    "google-auth library not installed. "
                    "Install with: pip install mca-sdk[gcp] or pip install google-auth"
                )
            else:
                raise ConfigurationError(f"Failed to import google-auth: {e}")

        logger.info(
            f"Loaded GCP credentials from file: {credentials_path} "
            f"(project: {creds.project_id})"
        )
        return creds

    # Priority 2: Load from JSON string
    if credentials_json:
        # Handle SensitiveString wrapper (auto-applied by MCAConfig)
        from ..config.settings import SensitiveString

        if isinstance(credentials_json, SensitiveString):
            # CRITICAL: All credential processing happens inside lambda
            # The parsed dict (containing private_key) never escapes to outer scope
            creds = credentials_json._SensitiveString__unsafe_process_value(
                lambda raw_json: _create_credentials_from_json_string(raw_json, scopes)
            )
        else:
            # Non-sensitive path - standard processing
            creds = _create_credentials_from_json_string(credentials_json, scopes)

        logger.info(f"Loaded GCP credentials from JSON string (project: {creds.project_id})")
        return creds

    # Priority 3: Fall back to Application Default Credentials
    try:
        import google.auth

        google_creds, project_id = google.auth.default(scopes=scopes)

        if not project_id:
            raise ConfigurationError(
                "Could not determine GCP project_id from Application Default Credentials. "
                "Set GOOGLE_CLOUD_PROJECT environment variable or use explicit credentials."
            )

        logger.info(f"Using Application Default Credentials (ADC) for project: {project_id}")

        # Return minimal credentials object with ADC
        # NOTE: ADC credentials have incomplete fields (empty strings for
        # client_email, private_key, etc.) because ADC doesn't expose these.
        # The type="adc" distinguishes this from full service_account credentials.
        # Only google_credentials and project_id are valid; other fields should
        # not be used when type="adc".
        return GCPCredentials(
            type="adc",
            project_id=project_id,
            private_key_id="",
            private_key="",
            client_email="",
            client_id="",
            auth_uri="",
            token_uri="",
            auth_provider_x509_cert_url="",
            client_x509_cert_url="",
            google_credentials=google_creds,
        )
    except ImportError:
        raise ConfigurationError(
            "google-auth library not installed. "
            "Install with: pip install mca-sdk[gcp] or pip install google-auth"
        )
    except ConfigurationError:
        # Re-raise ConfigurationError (project_id validation, etc.)
        raise
    except Exception as e:
        logger.debug(
            f"Application Default Credentials (ADC) not available: {e}. "
            "Continuing without GCP authentication."
        )
        return None


def validate_credentials(creds: GCPCredentials) -> None:
    """Validate GCP credentials structure and format.

    Args:
        creds: GCPCredentials to validate

        Raises:
            ConfigurationError: If credentials are invalid
    """
    from ..utils.exceptions import ConfigurationError

    # Validate account type
    if creds.type not in ("service_account", "adc"):
        raise ConfigurationError(
            f"Invalid GCP account type: {creds.type}. "
            "Expected 'service_account'. Ensure you're using a service account JSON file."
        )

    # Skip detailed validation for ADC (minimal fields available)
    if creds.type == "adc":
        return

    # Validate email format
    email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    if not re.match(email_pattern, creds.client_email):
        raise ConfigurationError(
            f"Invalid client_email format: {creds.client_email}. "
            "Expected valid email address for service account."
        )

    # Validate private key format
    if not creds.private_key.startswith("-----BEGIN"):
        raise ConfigurationError(
            "Invalid private key format. "
            "Expected PEM-formatted private key starting with '-----BEGIN PRIVATE KEY-----'"
        )

    # Validate project ID exists
    if not creds.project_id:
        raise ConfigurationError("GCP project_id is required but not found in credentials")

    # Warn about suspicious project ID formats (but don't block legacy projects)
    # Modern format: 6-30 chars, lowercase, alphanumeric + hyphens
    # Legacy format: may contain colons (domain-based), longer names
    if not re.match(r"^[a-z][a-z0-9:-]{4,}[a-z0-9]$", creds.project_id):
        logger.warning(
            f"GCP project_id '{creds.project_id}' has unusual format. "
            "Modern project IDs should be 6-30 lowercase letters, numbers, and hyphens. "
            "Legacy project IDs may also contain colons or domain names. "
            "Continuing anyway."
        )


def get_credentials_for_config(
    config, scopes: Optional[List[str]] = None
) -> Optional[GCPCredentials]:
    """Get GCP credentials from MCAConfig.

    Args:
        config: MCAConfig instance
        scopes: OAuth scopes to request. Defaults to monitoring, logging, trace.

    Returns:
        GCPCredentials instance or None if no credentials configured

    Raises:
        ConfigurationError: If credentials are invalid
    """
    # Check if GCP credentials are configured
    credentials_path = getattr(config, "gcp_credentials_path", None)
    credentials_json = getattr(config, "gcp_credentials_json", None)

    if not credentials_path and not credentials_json:
        # Try ADC fallback
        logger.debug("No explicit GCP credentials configured, attempting ADC fallback")
        return load_credentials(scopes=scopes)

    return load_credentials(
        credentials_path=credentials_path,
        credentials_json=credentials_json,
        scopes=scopes,
    )


def get_google_credentials(config, scopes: Optional[List[str]] = None) -> Optional[Any]:
    """Get google.auth credentials object for use with GCP client libraries.

    This is a convenience function for GCP exporters to get credentials ready
    for use with GCP client libraries (Cloud Monitoring, Cloud Logging, etc.).

    Args:
        config: MCAConfig instance
        scopes: OAuth scopes to request. Defaults to monitoring, logging, trace.

    Returns:
        google.auth.credentials.Credentials instance or None if no credentials

    Raises:
        ConfigurationError: If credentials are invalid

    Example:
        >>> from mca_sdk.config import MCAConfig
        >>> from mca_sdk.core.gcp_auth import get_google_credentials
        >>>
        >>> config = MCAConfig(
        ...     service_name="my-service",
        ...     gcp_credentials_path="/path/to/service-account.json"
        ... )
        >>>
        >>> # For use with GCP Cloud Monitoring exporter (Story 6.1)
        >>> credentials = get_google_credentials(config)
        >>> if credentials:
        ...     from google.cloud import monitoring_v3
        ...     client = monitoring_v3.MetricServiceClient(credentials=credentials)
        >>>
        >>> # For use with GCP Cloud Logging exporter (Story 6.2)
        >>> credentials = get_google_credentials(config)
        >>> if credentials:
        ...     from google.cloud import logging
        ...     client = logging.Client(credentials=credentials)
        >>>
        >>> # For use with GCP Cloud Trace exporter (Story 6.3)
        >>> credentials = get_google_credentials(config)
        >>> if credentials:
        ...     from google.cloud import trace_v2
        ...     client = trace_v2.TraceServiceClient(credentials=credentials)
        >>>
        >>> # Custom scopes (e.g., for BigQuery)
        >>> credentials = get_google_credentials(
        ...     config,
        ...     scopes=["https://www.googleapis.com/auth/bigquery"]
        ... )
    """
    creds = get_credentials_for_config(config, scopes=scopes)
    if creds and creds.google_credentials:
        return creds.google_credentials

    logger.warning(
        "No GCP credentials available. GCP exporters will not be able to authenticate. "
        "Configure gcp_credentials_path or gcp_credentials_json, or ensure ADC is available."
    )
    return None
