"""
AIPTX AWS Key Validator
=======================

Validates AWS Access Keys and enumerates IAM permissions.

Supports:
- AWS Access Key ID + Secret Access Key
- Session tokens (temporary credentials)
- IAM permission enumeration

Uses AWS STS GetCallerIdentity for validation and IAM simulation
for permission enumeration.
"""

from __future__ import annotations

import re
import time
import hmac
import hashlib
import logging
from datetime import datetime
from typing import Any, Optional
from urllib.parse import quote

import httpx

from ..base import (
    APIKeyInfo,
    ValidationResult,
    BaseKeyValidator,
    KeyType,
    RiskLevel,
)

logger = logging.getLogger(__name__)


class AWSKeyValidator(BaseKeyValidator):
    """
    Validate AWS credentials and enumerate IAM permissions.

    Uses AWS STS GetCallerIdentity to validate credentials and
    attempts to enumerate permissions through IAM policy simulation.
    """

    STS_HOST = "sts.amazonaws.com"
    IAM_HOST = "iam.amazonaws.com"

    def get_key_type(self) -> KeyType:
        return KeyType.AWS

    def get_key_patterns(self) -> dict[str, re.Pattern]:
        return {
            "aws_access_key_id": re.compile(r'(?:AKIA|ASIA)[0-9A-Z]{16}'),
            "aws_secret_access_key": re.compile(r'[A-Za-z0-9/+=]{40}'),
            "aws_session_token": re.compile(r'FwoGZXIvYXdzE[A-Za-z0-9/+=]+'),
        }

    async def validate(self, key_info: APIKeyInfo) -> ValidationResult:
        """
        Validate AWS credentials using STS GetCallerIdentity.

        Requires both Access Key ID and Secret Access Key.
        """
        start_time = time.time()

        # AWS requires key pair
        access_key_id = key_info.key_id or key_info.key_value
        secret_access_key = key_info.secret_value

        if not access_key_id or not access_key_id.startswith(("AKIA", "ASIA")):
            duration_ms = (time.time() - start_time) * 1000
            return self._create_error_result(
                key_info,
                error="Invalid AWS Access Key ID format (must start with AKIA or ASIA)",
                error_code="INVALID_KEY_FORMAT",
                duration_ms=duration_ms,
            )

        if not secret_access_key:
            duration_ms = (time.time() - start_time) * 1000
            return self._create_error_result(
                key_info,
                error="Missing AWS Secret Access Key",
                error_code="MISSING_SECRET",
                duration_ms=duration_ms,
            )

        session_token = key_info.metadata.get("session_token")

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                # Call STS GetCallerIdentity
                identity_data, error = await self._get_caller_identity(
                    client, access_key_id, secret_access_key, session_token
                )

                duration_ms = (time.time() - start_time) * 1000

                if error:
                    return self._create_error_result(
                        key_info,
                        error=error,
                        error_code="STS_ERROR",
                        duration_ms=duration_ms,
                    )

                # Parse identity
                account_id = identity_data.get("Account")
                arn = identity_data.get("Arn")
                user_id = identity_data.get("UserId")

                identity_info = {
                    "account_id": account_id,
                    "arn": arn,
                    "user_id": user_id,
                    "is_root": ":root" in arn if arn else False,
                    "is_temporary": access_key_id.startswith("ASIA"),
                }

                # Parse user/role name from ARN
                if arn:
                    arn_parts = arn.split("/")
                    if len(arn_parts) > 1:
                        identity_info["principal_name"] = arn_parts[-1]

                    if ":user/" in arn:
                        identity_info["principal_type"] = "IAM User"
                    elif ":role/" in arn:
                        identity_info["principal_type"] = "IAM Role"
                    elif ":assumed-role/" in arn:
                        identity_info["principal_type"] = "Assumed Role"
                    elif ":root" in arn:
                        identity_info["principal_type"] = "Root Account"

                # Enumerate permissions
                permissions, resources = await self._enumerate_permissions(
                    client, access_key_id, secret_access_key, session_token, arn
                )

                result = ValidationResult(
                    key_info=key_info,
                    is_valid=True,
                    validation_method="STS GetCallerIdentity",
                    validation_endpoint=f"https://{self.STS_HOST}",
                    identity_info=identity_info,
                    scopes=[],  # AWS doesn't use scopes
                    permissions=permissions,
                    resources_accessible=resources,
                    raw_response=identity_data,
                    validated_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                    validation_duration_ms=duration_ms,
                )

                # Assess risk
                result.risk_level = self._assess_aws_risk(permissions, identity_info)
                result.exploitation_potential = self._get_exploitation_potential(result)
                result.attack_vectors = self._get_attack_vectors(permissions, identity_info)

                return result

        except httpx.TimeoutException:
            duration_ms = (time.time() - start_time) * 1000
            return self._create_error_result(
                key_info,
                error="Request timeout",
                error_code="TIMEOUT",
                duration_ms=duration_ms,
            )
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            logger.error(f"AWS validation error: {e}")
            return self._create_error_result(
                key_info,
                error=str(e),
                error_code="ERROR",
                duration_ms=duration_ms,
            )

    async def _get_caller_identity(
        self,
        client: httpx.AsyncClient,
        access_key_id: str,
        secret_access_key: str,
        session_token: Optional[str] = None,
    ) -> tuple[dict, Optional[str]]:
        """Call STS GetCallerIdentity with AWS Sig V4."""
        try:
            # Build request
            method = "POST"
            service = "sts"
            region = "us-east-1"
            host = self.STS_HOST
            endpoint = f"https://{host}"

            # Request parameters
            payload = "Action=GetCallerIdentity&Version=2011-06-15"
            content_type = "application/x-www-form-urlencoded"

            # Create signed request headers
            headers = self._sign_request(
                method=method,
                service=service,
                region=region,
                host=host,
                payload=payload,
                access_key_id=access_key_id,
                secret_access_key=secret_access_key,
                session_token=session_token,
                content_type=content_type,
            )

            response = await client.post(
                endpoint,
                content=payload,
                headers=headers,
            )

            if response.status_code == 200:
                # Parse XML response
                return self._parse_sts_response(response.text), None
            else:
                # Parse error
                error_msg = self._parse_error_response(response.text)
                return {}, error_msg

        except Exception as e:
            return {}, str(e)

    def _sign_request(
        self,
        method: str,
        service: str,
        region: str,
        host: str,
        payload: str,
        access_key_id: str,
        secret_access_key: str,
        session_token: Optional[str] = None,
        content_type: str = "application/x-www-form-urlencoded",
    ) -> dict:
        """Create AWS Signature Version 4 signed headers."""
        # Timestamps
        t = datetime.utcnow()
        amz_date = t.strftime('%Y%m%dT%H%M%SZ')
        date_stamp = t.strftime('%Y%m%d')

        # Canonical request elements
        canonical_uri = "/"
        canonical_querystring = ""
        payload_hash = hashlib.sha256(payload.encode('utf-8')).hexdigest()

        # Headers
        headers_dict = {
            'host': host,
            'content-type': content_type,
            'x-amz-date': amz_date,
        }
        if session_token:
            headers_dict['x-amz-security-token'] = session_token

        # Signed headers
        signed_headers = ';'.join(sorted(headers_dict.keys()))

        # Canonical headers
        canonical_headers = ''
        for key in sorted(headers_dict.keys()):
            canonical_headers += f"{key}:{headers_dict[key]}\n"

        # Canonical request
        canonical_request = f"{method}\n{canonical_uri}\n{canonical_querystring}\n{canonical_headers}\n{signed_headers}\n{payload_hash}"
        canonical_request_hash = hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()

        # String to sign
        algorithm = 'AWS4-HMAC-SHA256'
        credential_scope = f"{date_stamp}/{region}/{service}/aws4_request"
        string_to_sign = f"{algorithm}\n{amz_date}\n{credential_scope}\n{canonical_request_hash}"

        # Signing key
        def sign(key, msg):
            return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()

        k_date = sign(('AWS4' + secret_access_key).encode('utf-8'), date_stamp)
        k_region = sign(k_date, region)
        k_service = sign(k_region, service)
        k_signing = sign(k_service, 'aws4_request')
        signature = hmac.new(k_signing, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()

        # Authorization header
        authorization = f"{algorithm} Credential={access_key_id}/{credential_scope}, SignedHeaders={signed_headers}, Signature={signature}"

        # Final headers
        final_headers = {
            'Host': host,
            'Content-Type': content_type,
            'X-Amz-Date': amz_date,
            'Authorization': authorization,
        }
        if session_token:
            final_headers['X-Amz-Security-Token'] = session_token

        return final_headers

    def _parse_sts_response(self, xml_response: str) -> dict:
        """Parse STS GetCallerIdentity XML response."""
        result = {}

        # Simple XML parsing for expected fields
        import re

        account_match = re.search(r'<Account>([^<]+)</Account>', xml_response)
        if account_match:
            result['Account'] = account_match.group(1)

        arn_match = re.search(r'<Arn>([^<]+)</Arn>', xml_response)
        if arn_match:
            result['Arn'] = arn_match.group(1)

        user_id_match = re.search(r'<UserId>([^<]+)</UserId>', xml_response)
        if user_id_match:
            result['UserId'] = user_id_match.group(1)

        return result

    def _parse_error_response(self, xml_response: str) -> str:
        """Parse AWS error response."""
        import re

        code_match = re.search(r'<Code>([^<]+)</Code>', xml_response)
        message_match = re.search(r'<Message>([^<]+)</Message>', xml_response)

        if code_match and message_match:
            return f"{code_match.group(1)}: {message_match.group(1)}"
        elif message_match:
            return message_match.group(1)

        return "Unknown AWS error"

    async def _enumerate_permissions(
        self,
        client: httpx.AsyncClient,
        access_key_id: str,
        secret_access_key: str,
        session_token: Optional[str],
        arn: str,
    ) -> tuple[list[str], list[str]]:
        """Enumerate AWS permissions using IAM simulation."""
        permissions = []
        resources = []

        # Common actions to test
        test_actions = [
            ("sts", "GetCallerIdentity", "*"),
            ("s3", "ListAllMyBuckets", "*"),
            ("s3", "ListBucket", "*"),
            ("s3", "GetObject", "*"),
            ("s3", "PutObject", "*"),
            ("ec2", "DescribeInstances", "*"),
            ("ec2", "RunInstances", "*"),
            ("ec2", "TerminateInstances", "*"),
            ("iam", "GetUser", "*"),
            ("iam", "ListUsers", "*"),
            ("iam", "CreateUser", "*"),
            ("iam", "AttachUserPolicy", "*"),
            ("lambda", "ListFunctions", "*"),
            ("lambda", "InvokeFunction", "*"),
            ("rds", "DescribeDBInstances", "*"),
            ("secretsmanager", "ListSecrets", "*"),
            ("secretsmanager", "GetSecretValue", "*"),
            ("ssm", "GetParameter", "*"),
            ("kms", "ListKeys", "*"),
            ("kms", "Decrypt", "*"),
        ]

        try:
            # Try to list S3 buckets as a simple permission check
            s3_headers = self._sign_request(
                method="GET",
                service="s3",
                region="us-east-1",
                host="s3.amazonaws.com",
                payload="",
                access_key_id=access_key_id,
                secret_access_key=secret_access_key,
                session_token=session_token,
            )

            response = await client.get(
                "https://s3.amazonaws.com/",
                headers=s3_headers,
            )

            if response.status_code == 200:
                permissions.append("s3:ListAllMyBuckets")
                # Parse bucket names
                bucket_matches = re.findall(r'<Name>([^<]+)</Name>', response.text)
                resources.extend([f"s3://{b}" for b in bucket_matches[:20]])

        except Exception as e:
            logger.debug(f"S3 enumeration failed: {e}")

        # Infer permissions based on principal type
        if ":root" in arn:
            permissions.extend([
                "iam:*",
                "s3:*",
                "ec2:*",
                "lambda:*",
                "rds:*",
                "secretsmanager:*",
                "kms:*",
            ])
            resources.append("WARNING: Root account - full AWS access")

        return permissions, resources

    def _assess_aws_risk(
        self,
        permissions: list[str],
        identity_info: dict,
    ) -> RiskLevel:
        """Assess risk level based on AWS credentials."""
        # Critical: Root account or IAM admin
        if identity_info.get("is_root"):
            return RiskLevel.CRITICAL

        if any("iam:" in p and ("*" in p or "Create" in p or "Attach" in p) for p in permissions):
            return RiskLevel.CRITICAL

        # High: Can access secrets or has broad S3 access
        high_risk_permissions = ["secretsmanager:GetSecretValue", "ssm:GetParameter", "kms:Decrypt"]
        if any(p in permissions for p in high_risk_permissions):
            return RiskLevel.HIGH

        if any("s3:Put" in p or "s3:*" in p for p in permissions):
            return RiskLevel.HIGH

        # Medium: Can read data or describe resources
        if any("s3:" in p or "ec2:Describe" in p for p in permissions):
            return RiskLevel.MEDIUM

        # Low: Limited or unknown permissions
        if permissions:
            return RiskLevel.LOW

        return RiskLevel.INFO

    def _get_exploitation_potential(self, result: ValidationResult) -> str:
        """Get exploitation potential description."""
        if result.risk_level == RiskLevel.CRITICAL:
            return "Critical - Root account or IAM admin access"
        elif result.risk_level == RiskLevel.HIGH:
            return "High - Can access secrets or modify S3 data"
        elif result.risk_level == RiskLevel.MEDIUM:
            return "Medium - Can list/read AWS resources"
        elif result.risk_level == RiskLevel.LOW:
            return "Low - Limited AWS permissions"
        return "Minimal - Could not enumerate permissions"

    def _get_attack_vectors(
        self,
        permissions: list[str],
        identity_info: dict,
    ) -> list[str]:
        """Identify potential attack vectors."""
        vectors = []

        if identity_info.get("is_root"):
            vectors.append("Full AWS account access (root credentials)")
            vectors.append("Create IAM users/roles for persistence")
            vectors.append("Access all services and data")
            return vectors

        if any("s3:" in p for p in permissions):
            vectors.append("Access/exfiltrate S3 bucket data")
            if any("Put" in p or "*" in p for p in permissions):
                vectors.append("Upload malicious files to S3")

        if any("ec2:" in p for p in permissions):
            vectors.append("Enumerate EC2 instances and VPCs")
            if any("Run" in p or "*" in p for p in permissions):
                vectors.append("Launch EC2 instances for crypto mining")

        if any("iam:" in p for p in permissions):
            vectors.append("Enumerate IAM users, roles, and policies")
            if any("Create" in p or "Attach" in p for p in permissions):
                vectors.append("Create backdoor IAM users/roles")
                vectors.append("Privilege escalation via policy attachment")

        if any("secretsmanager:" in p or "ssm:" in p for p in permissions):
            vectors.append("Access secrets and parameter store values")

        if any("lambda:" in p for p in permissions):
            vectors.append("List and potentially invoke Lambda functions")
            if any("Create" in p or "Update" in p for p in permissions):
                vectors.append("Inject malicious code into Lambda functions")

        if any("kms:" in p for p in permissions):
            vectors.append("Access KMS keys for decryption")

        return vectors
