"""
AIPTX Stripe Key Validator
==========================

Validates Stripe API keys (secret keys and restricted keys).

Supports:
- Secret keys (sk_live_, sk_test_)
- Restricted keys (rk_live_, rk_test_)
- Publishable keys (pk_live_, pk_test_) - limited validation
"""

from __future__ import annotations

import re
import time
import logging
import base64
from typing import Any

import httpx

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

logger = logging.getLogger(__name__)


class StripeKeyValidator(BaseKeyValidator):
    """
    Validate Stripe API keys and enumerate permissions.

    Uses Stripe REST API to validate keys and discover:
    - Account information
    - Available capabilities
    - Accessible resources (customers, charges, etc.)
    """

    API_BASE = "https://api.stripe.com/v1"

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

    def get_key_patterns(self) -> dict[str, re.Pattern]:
        return {
            "stripe_secret_live": re.compile(r'sk_live_[A-Za-z0-9]{24,}'),
            "stripe_secret_test": re.compile(r'sk_test_[A-Za-z0-9]{24,}'),
            "stripe_restricted_live": re.compile(r'rk_live_[A-Za-z0-9]{24,}'),
            "stripe_restricted_test": re.compile(r'rk_test_[A-Za-z0-9]{24,}'),
            "stripe_publishable_live": re.compile(r'pk_live_[A-Za-z0-9]{24,}'),
            "stripe_publishable_test": re.compile(r'pk_test_[A-Za-z0-9]{24,}'),
        }

    async def validate(self, key_info: APIKeyInfo) -> ValidationResult:
        """
        Validate Stripe key against API.

        Calls GET /balance to validate and check permissions.
        """
        start_time = time.time()

        # Publishable keys have very limited validation
        if key_info.key_value.startswith("pk_"):
            return self._validate_publishable_key(key_info, start_time)

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                # Use Basic auth with API key
                auth_header = base64.b64encode(
                    f"{key_info.key_value}:".encode()
                ).decode()

                headers = {
                    "Authorization": f"Basic {auth_header}",
                    "Content-Type": "application/x-www-form-urlencoded",
                }

                # Check balance endpoint (requires balance:read)
                response = await client.get(
                    f"{self.API_BASE}/balance",
                    headers=headers,
                )

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

                if response.status_code == 200:
                    balance_data = response.json()

                    # Get account info
                    account_info = await self._get_account_info(client, headers)

                    # Enumerate accessible resources
                    resources, permissions = await self._enumerate_access(
                        client, headers
                    )

                    # Build identity info
                    identity_info = {
                        "account_id": account_info.get("id"),
                        "business_name": account_info.get("business_profile", {}).get("name"),
                        "email": account_info.get("email"),
                        "country": account_info.get("country"),
                        "livemode": balance_data.get("livemode", False),
                        "available_balance": self._format_balance(balance_data.get("available", [])),
                        "pending_balance": self._format_balance(balance_data.get("pending", [])),
                    }

                    # Determine if live or test key
                    is_live = key_info.key_value.startswith("sk_live") or key_info.key_value.startswith("rk_live")

                    result = ValidationResult(
                        key_info=key_info,
                        is_valid=True,
                        validation_method="GET /balance",
                        validation_endpoint=f"{self.API_BASE}/balance",
                        identity_info=identity_info,
                        scopes=[],  # Stripe doesn't use OAuth scopes
                        permissions=permissions,
                        resources_accessible=resources,
                        raw_response=balance_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_stripe_risk(is_live, permissions, identity_info)
                    result.exploitation_potential = self._get_exploitation_potential(result, is_live)
                    result.attack_vectors = self._get_attack_vectors(is_live, permissions)

                    return result

                elif response.status_code == 401:
                    return self._create_error_result(
                        key_info,
                        error="Invalid API key",
                        error_code="401_UNAUTHORIZED",
                        duration_ms=duration_ms,
                    )
                elif response.status_code == 403:
                    # Key is valid but lacks balance:read permission
                    # Try alternative endpoint
                    return await self._validate_restricted_key(
                        client, key_info, headers, start_time
                    )
                else:
                    error_data = response.json()
                    return self._create_error_result(
                        key_info,
                        error=error_data.get("error", {}).get("message", "Unknown error"),
                        error_code=str(response.status_code),
                        duration_ms=duration_ms,
                    )

        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"Stripe validation error: {e}")
            return self._create_error_result(
                key_info,
                error=str(e),
                error_code="ERROR",
                duration_ms=duration_ms,
            )

    def _validate_publishable_key(
        self,
        key_info: APIKeyInfo,
        start_time: float,
    ) -> ValidationResult:
        """Validate publishable key (limited validation)."""
        duration_ms = (time.time() - start_time) * 1000

        # Publishable keys can't be validated directly via API
        # They're client-safe and have minimal permissions
        is_live = key_info.key_value.startswith("pk_live")

        return ValidationResult(
            key_info=key_info,
            is_valid=True,  # Assume valid based on format
            validation_method="format_check",
            validation_endpoint="N/A",
            identity_info={
                "key_type": "publishable",
                "livemode": is_live,
            },
            scopes=[],
            permissions=["Create tokens (client-side only)"],
            resources_accessible=[],
            validated_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
            validation_duration_ms=duration_ms,
            risk_level=RiskLevel.INFO,
            exploitation_potential="Minimal - Publishable keys are client-safe",
            attack_vectors=[],
        )

    async def _validate_restricted_key(
        self,
        client: httpx.AsyncClient,
        key_info: APIKeyInfo,
        headers: dict,
        start_time: float,
    ) -> ValidationResult:
        """Validate restricted API key with limited permissions."""
        duration_ms = (time.time() - start_time) * 1000

        # Try to list customers (common permission)
        response = await client.get(
            f"{self.API_BASE}/customers",
            headers=headers,
            params={"limit": 1},
        )

        if response.status_code == 200:
            is_live = key_info.key_value.startswith("rk_live") or key_info.key_value.startswith("sk_live")

            return ValidationResult(
                key_info=key_info,
                is_valid=True,
                validation_method="GET /customers",
                validation_endpoint=f"{self.API_BASE}/customers",
                identity_info={
                    "key_type": "restricted",
                    "livemode": is_live,
                },
                scopes=[],
                permissions=["customers:read"],
                resources_accessible=["customers"],
                validated_at=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
                validation_duration_ms=duration_ms,
                risk_level=RiskLevel.MEDIUM if is_live else RiskLevel.LOW,
                exploitation_potential="Limited - Restricted key with specific permissions",
            )

        return self._create_error_result(
            key_info,
            error="Restricted key with unknown permissions",
            error_code="LIMITED_ACCESS",
            duration_ms=duration_ms,
        )

    async def _get_account_info(
        self,
        client: httpx.AsyncClient,
        headers: dict,
    ) -> dict:
        """Get Stripe account information."""
        try:
            response = await client.get(
                f"{self.API_BASE}/account",
                headers=headers,
            )
            if response.status_code == 200:
                return response.json()
        except Exception as e:
            logger.debug(f"Failed to get account info: {e}")
        return {}

    async def _enumerate_access(
        self,
        client: httpx.AsyncClient,
        headers: dict,
    ) -> tuple[list[str], list[str]]:
        """Enumerate accessible resources and permissions."""
        resources = []
        permissions = []

        # Test various endpoints
        endpoints = [
            ("customers", "customers:read", "GET /customers"),
            ("charges", "charges:read", "GET /charges"),
            ("invoices", "invoices:read", "GET /invoices"),
            ("subscriptions", "subscriptions:read", "GET /subscriptions"),
            ("products", "products:read", "GET /products"),
            ("prices", "prices:read", "GET /prices"),
            ("payment_intents", "payment_intents:read", "GET /payment_intents"),
            ("payouts", "payouts:read", "GET /payouts"),
            ("refunds", "refunds:read", "GET /refunds"),
            ("disputes", "disputes:read", "GET /disputes"),
        ]

        for endpoint, permission, desc in endpoints:
            try:
                response = await client.get(
                    f"{self.API_BASE}/{endpoint}",
                    headers=headers,
                    params={"limit": 1},
                )
                if response.status_code == 200:
                    data = response.json()
                    count = len(data.get("data", []))
                    has_more = data.get("has_more", False)
                    resources.append(f"{endpoint}:{count}{'+ more' if has_more else ''}")
                    permissions.append(permission)
            except Exception:
                pass

        # Check if we can write
        try:
            # Test customer creation with minimal data (won't actually create)
            response = await client.post(
                f"{self.API_BASE}/customers",
                headers=headers,
                data={"description": "AIPTX validation test"},
            )
            if response.status_code == 200:
                # Delete the test customer
                customer_id = response.json().get("id")
                if customer_id:
                    await client.delete(
                        f"{self.API_BASE}/customers/{customer_id}",
                        headers=headers,
                    )
                permissions.append("customers:write")
        except Exception:
            pass

        return resources, permissions

    def _format_balance(self, balance_items: list) -> str:
        """Format balance amount for display."""
        if not balance_items:
            return "N/A"
        total = sum(item.get("amount", 0) for item in balance_items)
        currency = balance_items[0].get("currency", "usd").upper() if balance_items else "USD"
        return f"{total / 100:.2f} {currency}"

    def _assess_stripe_risk(
        self,
        is_live: bool,
        permissions: list[str],
        identity_info: dict,
    ) -> RiskLevel:
        """Assess risk level based on key capabilities."""
        # Critical: Live key with write access and balance
        if is_live:
            if any("write" in p for p in permissions):
                return RiskLevel.CRITICAL
            if identity_info.get("available_balance"):
                return RiskLevel.CRITICAL

        # High: Live key with read access
        if is_live:
            return RiskLevel.HIGH

        # Medium: Test key with write access
        if any("write" in p for p in permissions):
            return RiskLevel.MEDIUM

        # Low: Test key with read-only
        return RiskLevel.LOW

    def _get_exploitation_potential(
        self,
        result: ValidationResult,
        is_live: bool,
    ) -> str:
        """Get exploitation potential description."""
        if result.risk_level == RiskLevel.CRITICAL:
            return "Critical - Live key with financial access"
        elif result.risk_level == RiskLevel.HIGH:
            return "High - Live key can access customer/payment data"
        elif result.risk_level == RiskLevel.MEDIUM:
            return "Medium - Test key with write permissions"
        elif result.risk_level == RiskLevel.LOW:
            return "Low - Test key with read-only access"
        return "Minimal - Limited permissions"

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

        if is_live:
            vectors.append("Access production customer PII")
            vectors.append("View payment card details (last 4 digits)")

            if "customers:write" in permissions:
                vectors.append("Modify customer records")
                vectors.append("Create fraudulent customers")

            if "charges:read" in permissions:
                vectors.append("View transaction history")

            if "payouts:read" in permissions:
                vectors.append("View payout destinations (bank accounts)")

            if "refunds:write" in permissions or any("write" in p for p in permissions):
                vectors.append("Issue unauthorized refunds")

        else:
            vectors.append("Access test environment data")
            if any("write" in p for p in permissions):
                vectors.append("Create test resources for reconnaissance")

        return vectors
