"""
HTTP Service Executor

Receives credential-less execution specs from the backend,
resolves credentials from the local vault, and fires HTTP requests.
"""

import json
import logging
import time
from typing import Dict, Any, Optional

import httpx

from .credential_vault import CredentialVault
from .protocol import ExecutionSpec
from .tools.base import ToolResult

logger = logging.getLogger(__name__)


class HttpExecutor:
    """
    Generic HTTP executor for service tool calls.
    
    The backend sends a fully-built request spec with everything
    EXCEPT credentials. This executor resolves credentials from
    the local vault and fires the request.
    """

    def __init__(self, vault: CredentialVault):
        self._vault = vault
        self._client = httpx.Client(
            timeout=httpx.Timeout(30.0, connect=10.0),
            follow_redirects=True,
            limits=httpx.Limits(max_connections=20, max_keepalive_connections=5),
        )

    def execute(self, spec: ExecutionSpec) -> ToolResult:
        """
        Execute an HTTP request from an execution spec.
        
        Flow:
        1. Start with spec headers
        2. Resolve credentials from vault
        3. Inject auth headers/params
        4. Fire request
        5. Return result
        """
        start_time = time.time()

        # Build headers from spec
        headers = dict(spec.headers)

        # Resolve and inject credentials
        if spec.auth:
            cred_headers = self._vault.resolve_headers(spec.auth.service_slug)
            if not cred_headers:
                return ToolResult.fail(
                    f"No credentials found for service '{spec.auth.service_slug}'. "
                    f"Please add credentials in the workspace settings."
                )
            headers.update(cred_headers)

            # Query params (rare, but some APIs use them)
            query_params = self._vault.resolve_query_params(spec.auth.service_slug)
            if query_params:
                # Append to URL
                separator = "&" if "?" in spec.url else "?"
                param_str = "&".join(f"{k}={v}" for k, v in query_params.items())
                url = f"{spec.url}{separator}{param_str}"
            else:
                url = spec.url
        else:
            url = spec.url

        # Log (mask auth headers)
        masked_headers = {
            k: (v[:10] + "***" if k.lower() in ("authorization", "x-api-key",
                 "x-shopify-access-token") and len(v) > 10 else v)
            for k, v in headers.items()
        }
        logger.info(f"🌐 HTTP {spec.method} {url}")
        logger.debug(f"  Headers: {json.dumps(masked_headers)}")

        try:
            # Build and fire request
            response = self._client.request(
                method=spec.method,
                url=url,
                headers=headers,
                content=spec.body.encode("utf-8") if spec.body else None,
                timeout=spec.timeout,
            )

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

            # Read response with size limit
            content = response.content[:spec.max_response_bytes]
            if len(response.content) > spec.max_response_bytes:
                logger.warning(f"Response truncated: {len(response.content)} > {spec.max_response_bytes}")

            # Try to parse as JSON
            try:
                result_data = json.loads(content.decode("utf-8", errors="replace"))
                output = json.dumps(result_data, indent=2)
            except (json.JSONDecodeError, UnicodeDecodeError):
                output = content.decode("utf-8", errors="replace")

            # Truncate output for LLM consumption
            if len(output) > 50_000:
                output = output[:50_000] + f"\n... (truncated, total {len(output)} chars)"

            # Check for HTTP errors
            if response.status_code >= 400:
                return ToolResult(
                    success=False,
                    output=output,
                    error=f"HTTP {response.status_code}: {response.reason_phrase}",
                    metadata={
                        "status_code": response.status_code,
                        "duration_ms": duration_ms,
                    }
                )

            return ToolResult(
                success=True,
                output=output,
                metadata={
                    "status_code": response.status_code,
                    "duration_ms": duration_ms,
                    "content_length": len(content),
                }
            )

        except httpx.TimeoutException:
            duration_ms = int((time.time() - start_time) * 1000)
            return ToolResult.fail(
                f"Request timed out after {spec.timeout}s",
                duration_ms=duration_ms,
            )
        except httpx.ConnectError as e:
            return ToolResult.fail(f"Connection failed: {e}")
        except Exception as e:
            return ToolResult.fail(f"HTTP request error: {e}")

    def close(self):
        """Close the underlying HTTP client."""
        self._client.close()
