#!/usr/bin/env python3
"""Centralized AWS client factory for runbooks automation.

Eliminates duplication of boto3 client creation across 30+ finops files.
Implements session caching and consistent error handling.

Strategic Achievement: DRY principle enforcement for AWS client management
Business Impact: Reduces maintenance overhead and ensures consistency
Technical Foundation: Centralized session management with LRU caching

Author: Runbooks Team
Version: 1.0.0
"""

import os

import boto3
from enum import Enum
from functools import lru_cache
from typing import Any, Optional

from botocore.config import Config


class AWSService(Enum):
    """Canonical AWS service names for use with AWSClientFactory.

    Use these constants instead of bare strings to prevent typos and enable
    IDE autocompletion.

    Example:
        client = AWSClientFactory.create_client(AWSService.EC2.value)
        ce = create_cost_explorer_client()  # convenience function preferred
    """

    CE = "ce"
    EC2 = "ec2"
    RDS = "rds"
    S3 = "s3"
    CLOUDWATCH = "cloudwatch"
    LAMBDA = "lambda"
    WORKSPACES = "workspaces"
    STS = "sts"
    ORGANIZATIONS = "organizations"
    IAM = "iam"
    CONFIG = "config"
    ECS = "ecs"
    ELBV2 = "elbv2"
    PRICING = "pricing"
    SSO = "sso"
    SSO_OIDC = "sso-oidc"
    SNS = "sns"
    GUARDDUTY = "guardduty"
    SECURITYHUB = "securityhub"
    ACM = "acm"


class AWSClientFactory:
    """Factory for creating AWS service clients with session caching."""

    @staticmethod
    def _get_config() -> Config:
        """Return standard botocore Config for all clients."""
        return Config(
            retries={"max_attempts": 3, "mode": "standard"},
            max_pool_connections=50,
        )

    @staticmethod
    @lru_cache(maxsize=128)
    def create_cached_session(profile: Optional[str] = None, region: str = "ap-southeast-2") -> boto3.Session:
        """Create and cache boto3 session for reuse.

        Args:
            profile: AWS profile name (None = default credential chain)
            region: AWS region (default: ap-southeast-2)

        Returns:
            Cached boto3.Session instance

        Example:
            >>> session = AWSClientFactory.create_cached_session("my-profile")
            >>> session.region_name
            'ap-southeast-2'
            >>> default_session = AWSClientFactory.create_cached_session()
        """
        if profile:
            return boto3.Session(profile_name=profile, region_name=region)
        return boto3.Session(region_name=region)

    @staticmethod
    def create_client(
        service: str,
        profile: Optional[str] = None,
        region: str = "ap-southeast-2",
        endpoint_url: Optional[str] = None,
        **kwargs,
    ) -> Any:
        """Create AWS service client using cached session.

        Args:
            service: AWS service name ('ce', 'ec2', 'rds', etc.)
            profile: AWS profile name (None = default credential chain)
            region: AWS region
            endpoint_url: Override endpoint URL (LocalStack, VPC endpoints, etc.).
                Priority: explicit > RUNBOOKS_ENDPOINT_URL env > AWS_ENDPOINT_URL env > None
            **kwargs: Additional boto3.client() parameters

        Returns:
            boto3 service client

        Example:
            >>> ce_client = AWSClientFactory.create_client('ce', 'my-profile')
            >>> ec2_client = AWSClientFactory.create_client('ec2', region='us-east-1')
            >>> ls_client = AWSClientFactory.create_client('s3', endpoint_url='http://localhost:4566')
        """
        session = AWSClientFactory.create_cached_session(profile, region)

        # Endpoint override: explicit > RUNBOOKS_ENDPOINT_URL > AWS_ENDPOINT_URL > None
        _endpoint_url = endpoint_url or os.getenv("RUNBOOKS_ENDPOINT_URL") or os.getenv("AWS_ENDPOINT_URL")

        # Apply default config if not provided
        if "config" not in kwargs:
            kwargs["config"] = AWSClientFactory._get_config()

        if _endpoint_url:
            kwargs["endpoint_url"] = _endpoint_url

        return session.client(service, **kwargs)


# Convenience functions for common services
def create_cost_explorer_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create Cost Explorer client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        Cost Explorer boto3 client

    Example:
        >>> ce_client = create_cost_explorer_client('my-profile')
        >>> ce_client.get_cost_and_usage(...)
    """
    return AWSClientFactory.create_client("ce", profile, region, endpoint_url=endpoint_url)


def create_ec2_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create EC2 client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        EC2 boto3 client

    Example:
        >>> ec2_client = create_ec2_client('my-profile')
        >>> ec2_client.describe_instances(...)
    """
    return AWSClientFactory.create_client("ec2", profile, region, endpoint_url=endpoint_url)


def create_rds_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create RDS client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        RDS boto3 client

    Example:
        >>> rds_client = create_rds_client('my-profile')
        >>> rds_client.describe_db_instances(...)
    """
    return AWSClientFactory.create_client("rds", profile, region, endpoint_url=endpoint_url)


def create_s3_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create S3 client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        S3 boto3 client

    Example:
        >>> s3_client = create_s3_client('my-profile')
        >>> s3_client.list_buckets()
    """
    return AWSClientFactory.create_client("s3", profile, region, endpoint_url=endpoint_url)


def create_cloudwatch_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create CloudWatch client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        CloudWatch boto3 client

    Example:
        >>> cw_client = create_cloudwatch_client('my-profile')
        >>> cw_client.get_metric_statistics(...)
    """
    return AWSClientFactory.create_client("cloudwatch", profile, region, endpoint_url=endpoint_url)


def create_lambda_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create Lambda client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        Lambda boto3 client

    Example:
        >>> lambda_client = create_lambda_client('my-profile')
        >>> lambda_client.list_functions()
    """
    return AWSClientFactory.create_client("lambda", profile, region, endpoint_url=endpoint_url)


def create_workspaces_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create WorkSpaces client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        WorkSpaces boto3 client

    Example:
        >>> ws_client = create_workspaces_client('my-profile')
        >>> ws_client.describe_workspaces()
    """
    return AWSClientFactory.create_client("workspaces", profile, region, endpoint_url=endpoint_url)


def create_sts_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create STS client."""
    return AWSClientFactory.create_client("sts", profile, region, endpoint_url=endpoint_url)


def create_organizations_client(
    profile: Optional[str] = None,
    region: str = "us-east-1",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create Organizations client (always us-east-1)."""
    return AWSClientFactory.create_client("organizations", profile, region, endpoint_url=endpoint_url)


def create_iam_client(
    profile: Optional[str] = None,
    region: str = "us-east-1",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create IAM client.

    Note: IAM is a global AWS service. The API endpoint is in us-east-1 regardless
    of where resources are managed. Default region is us-east-1 per AWS documentation.

    Args:
        profile: AWS profile name
        region: AWS region (default: us-east-1 — IAM global service endpoint)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        IAM boto3 client
    """
    return AWSClientFactory.create_client("iam", profile, region, endpoint_url=endpoint_url)


def create_config_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create AWS Config client."""
    return AWSClientFactory.create_client("config", profile, region, endpoint_url=endpoint_url)


def create_ecs_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create ECS client."""
    return AWSClientFactory.create_client("ecs", profile, region, endpoint_url=endpoint_url)


def create_elbv2_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create ELBv2 client."""
    return AWSClientFactory.create_client("elbv2", profile, region, endpoint_url=endpoint_url)


def create_pricing_client(
    profile: Optional[str] = None,
    region: str = "us-east-1",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create Pricing client.

    Note: AWS Pricing API is only available in us-east-1 and ap-southeast-1.
    Default region is us-east-1 per AWS documentation.

    Args:
        profile: AWS profile name
        region: AWS region (default: us-east-1)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        Pricing boto3 client
    """
    return AWSClientFactory.create_client("pricing", profile, region, endpoint_url=endpoint_url)


def create_sso_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create SSO client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        SSO boto3 client
    """
    return AWSClientFactory.create_client("sso", profile, region, endpoint_url=endpoint_url)


def create_sso_oidc_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create SSO OIDC client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        SSO OIDC boto3 client
    """
    return AWSClientFactory.create_client("sso-oidc", profile, region, endpoint_url=endpoint_url)


def create_sns_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create SNS client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        SNS boto3 client
    """
    return AWSClientFactory.create_client("sns", profile, region, endpoint_url=endpoint_url)


def create_guardduty_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create GuardDuty client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        GuardDuty boto3 client
    """
    return AWSClientFactory.create_client("guardduty", profile, region, endpoint_url=endpoint_url)


def create_securityhub_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create Security Hub client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        Security Hub boto3 client
    """
    return AWSClientFactory.create_client("securityhub", profile, region, endpoint_url=endpoint_url)


def create_cloudformation_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create a CloudFormation client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        CloudFormation boto3 client

    Example:
        >>> cf_client = create_cloudformation_client('my-profile')
        >>> cf_client.describe_stacks()
    """
    return AWSClientFactory.create_client("cloudformation", profile, region, endpoint_url=endpoint_url)


def create_cloudtrail_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create a CloudTrail client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        CloudTrail boto3 client

    Example:
        >>> ct_client = create_cloudtrail_client('my-profile')
        >>> ct_client.describe_trails()
    """
    return AWSClientFactory.create_client("cloudtrail", profile, region, endpoint_url=endpoint_url)


def create_logs_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create a CloudWatch Logs client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        CloudWatch Logs boto3 client

    Example:
        >>> logs_client = create_logs_client('my-profile')
        >>> logs_client.describe_log_groups()
    """
    return AWSClientFactory.create_client("logs", profile, region, endpoint_url=endpoint_url)


def create_route53_client(
    profile: Optional[str] = None,
    region: str = "us-east-1",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create a Route 53 client.

    Note: Route 53 is a global AWS DNS service. The API endpoint is in us-east-1
    regardless of where resources are managed. Default region is us-east-1 per
    AWS documentation.

    Args:
        profile: AWS profile name
        region: AWS region (default: us-east-1 — Route 53 global service endpoint)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        Route 53 boto3 client

    Example:
        >>> r53_client = create_route53_client('my-profile')
        >>> r53_client.list_hosted_zones()
    """
    return AWSClientFactory.create_client("route53", profile, region, endpoint_url=endpoint_url)


def create_budgets_client(
    profile: Optional[str] = None,
    region: str = "us-east-1",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create an AWS Budgets client.

    Note: AWS Budgets is a global billing service. The API endpoint is in us-east-1
    regardless of where resources are managed. Default region is us-east-1 per
    AWS documentation.

    Args:
        profile: AWS profile name
        region: AWS region (default: us-east-1 — Budgets global billing service endpoint)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        AWS Budgets boto3 client

    Example:
        >>> budgets_client = create_budgets_client('my-profile')
        >>> budgets_client.describe_budgets(AccountId='123456789012')
    """
    return AWSClientFactory.create_client("budgets", profile, region, endpoint_url=endpoint_url)


def create_dynamodb_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create a DynamoDB client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        DynamoDB boto3 client

    Example:
        >>> ddb_client = create_dynamodb_client('my-profile')
        >>> ddb_client.list_tables()
    """
    return AWSClientFactory.create_client("dynamodb", profile, region, endpoint_url=endpoint_url)


def create_autoscaling_client(
    profile: Optional[str] = None,
    region: str = "ap-southeast-2",
    endpoint_url: Optional[str] = None,
) -> Any:
    """Create an Auto Scaling client.

    Args:
        profile: AWS profile name
        region: AWS region (default: ap-southeast-2)
        endpoint_url: Optional endpoint override (e.g. LocalStack)

    Returns:
        Auto Scaling boto3 client

    Example:
        >>> asg_client = create_autoscaling_client('my-profile')
        >>> asg_client.describe_auto_scaling_groups()
    """
    return AWSClientFactory.create_client("autoscaling", profile, region, endpoint_url=endpoint_url)
