"""HTTP client for Platform-2Step API."""

import json
import logging
import sys
import time
import uuid
from typing import Any
from urllib.parse import urlencode

import httpx
from pydantic import BaseModel

from .auth import AuthClient, AuthError

logger = logging.getLogger(__name__)


def _is_debug_http_enabled() -> bool:
    """Check if DEBUG_HTTP is enabled via environment variable."""
    import os
    return os.environ.get("DEBUG_HTTP", "").lower() in ("true", "1", "yes")

# Separate logger for HTTP debug output (cURL-style logs)
http_logger = logging.getLogger(f"{__name__}.http")


def _sanitize_header_value(name: str, value: str) -> str:
    """Sanitize sensitive header values for logging."""
    sensitive_headers = {"authorization", "x-api-key", "cookie", "set-cookie"}
    if name.lower() in sensitive_headers:
        if name.lower() == "authorization" and value.startswith("Bearer "):
            # Show partial token for debugging
            token = value[7:]  # Remove "Bearer "
            if len(token) > 20:
                return f"Bearer {token[:10]}...{token[-5:]}"
            return "Bearer [REDACTED]"
        return "[REDACTED]"
    return value


def _sanitize_body(body: Any) -> str:
    """Sanitize request/response body for logging."""
    if body is None:
        return ""
    if isinstance(body, bytes):
        try:
            body = body.decode("utf-8")
        except UnicodeDecodeError:
            return "[binary data]"
    if isinstance(body, str):
        try:
            parsed = json.loads(body)
            body = parsed
        except json.JSONDecodeError:
            # Not JSON, return as-is (truncated if too long)
            if len(body) > 2000:
                return body[:2000] + "... [truncated]"
            return body

    # Sanitize sensitive fields in JSON
    if isinstance(body, dict):
        sanitized = body.copy()
        sensitive_fields = {
            "password",
            "token",
            "access_token",
            "refresh_token",
            "secret",
            "api_key",
            "completion_code",
            "device_code",
        }
        for field in sensitive_fields:
            if field in sanitized:
                value = sanitized[field]
                if isinstance(value, str) and len(value) > 20:
                    sanitized[field] = f"{value[:10]}...{value[-5:]}"
                else:
                    sanitized[field] = "[REDACTED]"
        return json.dumps(sanitized, indent=2)

    return str(body)


def format_curl_request(
    method: str,
    url: str,
    headers: dict[str, str],
    params: dict[str, Any] | None = None,
    json_body: dict[str, Any] | None = None,
) -> str:
    """Format an HTTP request as a cURL command for logging.

    Args:
        method: HTTP method (GET, POST, etc.)
        url: Full URL
        headers: Request headers
        params: Query parameters
        json_body: JSON request body

    Returns:
        cURL command string
    """
    # Build URL with query params
    if params:
        separator = "&" if "?" in url else "?"
        url = f"{url}{separator}{urlencode(params)}"

    parts = [f"curl -X {method} '{url}'"]

    # Add headers
    for name, value in headers.items():
        sanitized = _sanitize_header_value(name, value)
        parts.append(f"-H '{name}: {sanitized}'")

    # Add body
    if json_body:
        sanitized_body = _sanitize_body(json_body)
        # Escape single quotes for shell
        sanitized_body = sanitized_body.replace("'", "'\"'\"'")
        parts.append(f"--data '{sanitized_body}'")

    return " \\\n  ".join(parts)


def format_curl_response(
    status_code: int,
    headers: httpx.Headers,
    body: str | bytes,
    elapsed_ms: float,
) -> str:
    """Format an HTTP response for logging.

    Args:
        status_code: HTTP status code
        headers: Response headers
        body: Response body
        elapsed_ms: Request duration in milliseconds

    Returns:
        Formatted response string
    """
    lines = [
        f"# Response: {status_code} ({elapsed_ms:.0f}ms)",
        "# Headers:",
    ]

    # Log key response headers
    interesting_headers = {
        "content-type",
        "content-length",
        "x-request-id",
        "x-correlation-id",
        "retry-after",
    }
    for name, value in headers.items():
        if name.lower() in interesting_headers:
            lines.append(f"#   {name}: {value}")

    lines.append("# Body:")
    sanitized = _sanitize_body(body)
    # Indent body lines
    for line in sanitized.split("\n"):
        lines.append(f"#   {line}")

    return "\n".join(lines)


class PendingOperationResponse(BaseModel):
    """Response when a mutation creates a pending operation."""

    confirmation_id: str
    operation: str
    resource: str
    resource_id: int | None = None
    preview: dict[str, Any] | None = None
    expires_at: str
    confirm_url: str

    def get_confirm_url(self, bff_url: str) -> str:
        """Build the full browser-accessible confirmation URL."""
        return f"{bff_url}/v1/confirm/{self.confirmation_id}"


class BatchResponse(BaseModel):
    """Response when creating or querying a batch."""

    batch_id: str
    status: str
    description: str | None = None
    operations_count: int
    executed_count: int = 0
    failed_count: int = 0
    expires_at: str
    confirmable: bool = True
    confirm_url: str | None = None
    operations: list[dict[str, Any]] | None = None

    def get_confirm_url(self, bff_url: str) -> str:
        """Build the full browser-accessible batch confirmation URL."""
        return f"{bff_url}/v1/confirm-batch/{self.batch_id}"


class Platform2StepClient:
    """Async HTTP client for Platform-2Step API.

    Uses device flow authentication via AuthClient for OAuth 2.0 device authorization.

    Example:
        auth = AuthClient(bff_url="https://ap-api.agendaprodev.com/platform-2steps-bff")
        client = Platform2StepClient(
            base_url="https://ap-api.agendaprodev.com/platform-2steps-bff",
            auth_client=auth,
        )
    """

    def __init__(
        self,
        base_url: str,
        auth_client: AuthClient,
    ):
        """Initialize the client.

        Args:
            base_url: Base URL for Platform-2Step API (typically the BFF URL).
            auth_client: AuthClient for device flow authentication.
        """
        self._base_url = base_url.rstrip("/")
        self._auth_client = auth_client
        self._client: httpx.AsyncClient | None = None

    @property
    def bff_url(self) -> str:
        """Get the BFF base URL for composing confirmation URLs."""
        return self._base_url

    async def _get_client(self) -> httpx.AsyncClient:
        """Get or create the HTTP client."""
        if self._client is None:
            self._client = httpx.AsyncClient(
                base_url=self._base_url,
                timeout=30.0,
            )
        return self._client

    def _get_token(self) -> str:
        """Get the current ID token from the auth client for API requests.

        Note: We use the ID token (not access token) because API Gateway's
        CognitoUserPoolsAuthorizer validates ID tokens in the Authorization header.
        """
        return self._auth_client.get_id_token() or self._auth_client.get_valid_token()

    def _build_headers(self) -> dict[str, str]:
        """Build request headers with authentication and required metadata.

        Note: The Authorization header contains the ID token (not access token)
        because API Gateway's CognitoUserPoolsAuthorizer validates ID tokens.

        Returns:
            Dictionary of headers.
        """
        return {
            "Authorization": f"Bearer {self._get_token()}",
            "Content-Type": "application/json",
            "X-Client-Name": "platform-2step-mcp",
            "X-Request-Id": str(uuid.uuid4()),
        }

    async def _request(
        self,
        method: str,
        path: str,
        retry_on_401: bool = True,
        **kwargs: Any,
    ) -> httpx.Response:
        """Make an authenticated request with automatic 401 retry.

        Args:
            method: HTTP method.
            path: API path.
            retry_on_401: Whether to retry on 401 (default True).
            **kwargs: Additional arguments for httpx.request.

        Returns:
            HTTP response.
        """
        client = await self._get_client()
        headers = self._build_headers()

        # Check if DEBUG_HTTP is enabled for cURL-style logging
        debug_http = _is_debug_http_enabled()

        # Debug logging for troubleshooting
        logger.debug(f"Request: {method} {self._base_url}{path}")
        logger.debug(f"Authorization={'Bearer ...' if 'Authorization' in headers else 'MISSING'}")
        if kwargs.get('params'):
            logger.debug(f"Params: {kwargs.get('params')}")

        # Log cURL-style request if DEBUG_HTTP is enabled
        if debug_http:
            curl_request = format_curl_request(
                method=method,
                url=f"{self._base_url}{path}",
                headers=headers,
                params=kwargs.get("params"),
                json_body=kwargs.get("json"),
            )
            http_logger.debug(f">>> HTTP Request:\n{curl_request}")
            sys.stderr.flush()

        start_time = time.time()

        response = await client.request(
            method=method,
            url=path,
            headers=headers,
            **kwargs,
        )

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

        # Log cURL-style response if DEBUG_HTTP is enabled
        if debug_http:
            curl_response = format_curl_response(
                status_code=response.status_code,
                headers=response.headers,
                body=response.text,
                elapsed_ms=elapsed_ms,
            )
            http_logger.debug(f"<<< HTTP Response:\n{curl_response}")
            sys.stderr.flush()

        # Log response status for debugging (always log errors)
        if response.status_code >= 400:
            logger.error(f"Response: {response.status_code} - {response.text[:500]}")

        # Handle 401 with token refresh
        if response.status_code == 401 and retry_on_401 and self._auth_client:
            logger.info("Received 401, attempting token refresh")
            try:
                self._auth_client.authenticate()
                headers = self._build_headers()

                # Log retry request
                if debug_http:
                    curl_request = format_curl_request(
                        method=method,
                        url=f"{self._base_url}{path}",
                        headers=headers,
                        params=kwargs.get("params"),
                        json_body=kwargs.get("json"),
                    )
                    http_logger.debug(f">>> HTTP Request (retry after token refresh):\n{curl_request}")

                start_time = time.time()

                response = await client.request(
                    method=method,
                    url=path,
                    headers=headers,
                    **kwargs,
                )

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

                # Log retry response
                if debug_http:
                    curl_response = format_curl_response(
                        status_code=response.status_code,
                        headers=response.headers,
                        body=response.text,
                        elapsed_ms=elapsed_ms,
                    )
                    http_logger.debug(f"<<< HTTP Response (retry):\n{curl_response}")

            except AuthError as e:
                logger.error(f"Authentication failed after 401 retry: {e}")
                raise

        return response

    async def close(self) -> None:
        """Close the HTTP client."""
        if self._client:
            await self._client.aclose()
            self._client = None
        if self._auth_client:
            self._auth_client.close()

    # =========================================================================
    # Categories - Read Operations
    # =========================================================================

    async def list_categories(
        self,
        company_id: int,
        page: int = 1,
        per_page: int = 25,
    ) -> dict[str, Any]:
        """List categories for a company.

        Args:
            company_id: Company ID to filter categories.
            page: Page number (default 1).
            per_page: Results per page (default 25).

        Returns:
            API response with categories data.
        """
        response = await self._request(
            "GET",
            "/api/v1/categories",
            params={
                "company_id": company_id,
                "page": page,
                "per_page": per_page,
            },
        )
        response.raise_for_status()
        return response.json()

    async def get_category(self, category_id: int) -> dict[str, Any]:
        """Get a specific category.

        Args:
            category_id: Category ID.

        Returns:
            Category data.
        """
        response = await self._request(
            "GET",
            f"/api/v1/categories/{category_id}",
        )
        response.raise_for_status()
        return response.json()

    async def list_services_by_category(
        self,
        company_id: int,
        active: bool | None = None,
    ) -> dict[str, Any]:
        """List all services grouped by category.

        This endpoint returns categories with their associated services,
        useful for building service menus or catalogs.

        Args:
            company_id: Company ID.
            active: Filter services by active status (optional).

        Returns:
            API response with categories and their services.
        """
        params: dict[str, Any] = {"company_id": company_id}
        if active is not None:
            params["active"] = active

        response = await self._request(
            "GET",
            "/api/v1/categories/services",
            params=params,
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Services - Read Operations
    # =========================================================================

    async def list_services(
        self,
        company_id: int,
        service_category_id: int | None = None,
        active: bool | None = None,
        page: int = 1,
        per_page: int = 25,
    ) -> dict[str, Any]:
        """List services for a company.

        Args:
            company_id: Company ID.
            service_category_id: Filter by service category (optional).
            active: Filter by active status (optional).
            page: Page number.
            per_page: Results per page.

        Returns:
            API response with services data.
        """
        params: dict[str, Any] = {
            "company_id": company_id,
            "page": page,
            "per_page": per_page,
        }
        if service_category_id is not None:
            params["service_category_id"] = service_category_id
        if active is not None:
            params["active"] = active

        response = await self._request(
            "GET",
            "/api/v1/services",
            params=params,
        )
        response.raise_for_status()
        return response.json()

    async def get_service(self, service_id: int) -> dict[str, Any]:
        """Get a specific service.

        Args:
            service_id: Service ID.

        Returns:
            Service data.
        """
        response = await self._request(
            "GET",
            f"/api/v1/services/{service_id}",
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Bookings - Read Operations
    # =========================================================================

    async def list_bookings(
        self,
        company_id: int,
        start_date: str,
        end_date: str,
        location_id: int | None = None,
        client_id: int | None = None,
        service_id: int | None = None,
        service_provider_id: int | None = None,
        status_id: int | None = None,
        page: int = 1,
        per_page: int = 25,
    ) -> dict[str, Any]:
        """List bookings for a company within a date range.

        Args:
            company_id: Company ID.
            start_date: Start date (YYYY-MM-DD).
            end_date: End date (YYYY-MM-DD).
            location_id: Filter by location (optional).
            client_id: Filter by client (optional).
            service_id: Filter by service (optional).
            service_provider_id: Filter by provider (optional).
            status_id: Filter by status ID: 1=Reservado, 2=Confirmado, 3=Cancelado (optional).
            page: Page number.
            per_page: Results per page.

        Returns:
            API response with bookings data.
        """
        params: dict[str, Any] = {
            "company_id": company_id,
            "start_date": start_date,
            "end_date": end_date,
            "page": page,
            "per_page": per_page,
        }
        if location_id is not None:
            params["location_id"] = location_id
        if client_id is not None:
            params["client_id"] = client_id
        if service_id is not None:
            params["service_id"] = service_id
        if service_provider_id is not None:
            params["service_provider_id"] = service_provider_id
        if status_id is not None:
            params["status_id"] = status_id

        response = await self._request(
            "GET",
            "/api/v1/bookings",
            params=params,
        )
        response.raise_for_status()
        return response.json()

    async def get_booking(self, booking_id: int) -> dict[str, Any]:
        """Get a specific booking.

        Args:
            booking_id: Booking ID.

        Returns:
            Booking data.
        """
        response = await self._request(
            "GET",
            f"/api/v1/bookings/{booking_id}",
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Bookings - Mutation Operations (create pending)
    # =========================================================================

    async def create_booking(
        self,
        service_id: int,
        provider_id: int,
        client_id: int,
        start_time: str,
        company_id: int,
        location_id: int,
        notes: str | None = None,
    ) -> PendingOperationResponse:
        """Create a pending operation to create a booking.

        NOTE: Booking mutation routes not yet implemented in platform-2steps.
        This method is retained for when routes are added.

        Args:
            service_id: Service ID.
            provider_id: Provider/professional ID.
            client_id: Client ID.
            start_time: Start datetime (ISO 8601).
            company_id: Company ID (included in request body).
            location_id: Location ID.
            notes: Additional notes (optional).

        Returns:
            Pending operation with confirmation_id.
        """
        payload: dict[str, Any] = {
            "service_id": service_id,
            "provider_id": provider_id,
            "client_id": client_id,
            "start_time": start_time,
            "company_id": company_id,
            "location_id": location_id,
        }
        if notes is not None:
            payload["notes"] = notes

        response = await self._request(
            "POST",
            "/api/v1/bookings",
            json=payload,
        )
        response.raise_for_status()
        return PendingOperationResponse(**response.json())

    async def cancel_booking(self, booking_id: int) -> PendingOperationResponse:
        """Create a pending operation to cancel a booking.

        NOTE: Booking mutation routes not yet implemented in platform-2steps.
        This method is retained for when routes are added.

        Args:
            booking_id: Booking ID to cancel.

        Returns:
            Pending operation with confirmation_id.
        """
        response = await self._request(
            "DELETE",
            f"/api/v1/bookings/{booking_id}",
        )
        response.raise_for_status()
        return PendingOperationResponse(**response.json())

    # =========================================================================
    # Batches
    # =========================================================================

    async def create_batch(
        self,
        operations: list[dict[str, Any]],
        company_id: int,
        description: str | None = None,
    ) -> BatchResponse:
        """Create a batch of pending operations.

        Args:
            operations: List of operations (max 50).
            company_id: Company ID (passed as query parameter).
            description: Batch description (optional).

        Returns:
            Batch response with batch_id.
        """
        payload: dict[str, Any] = {"operations": operations}
        if description is not None:
            payload["description"] = description

        response = await self._request(
            "POST",
            "/api/v1/batches",
            params={"company_id": company_id},
            json=payload,
        )
        response.raise_for_status()
        return BatchResponse(**response.json())

    async def get_batch_status(self, batch_id: str) -> BatchResponse:
        """Get the status of a batch.

        Args:
            batch_id: Batch ID (UUID).

        Returns:
            Batch status and operations.
        """
        response = await self._request(
            "GET",
            f"/api/v1/batches/{batch_id}",
        )
        response.raise_for_status()
        return BatchResponse(**response.json())

    # =========================================================================
    # Locations - Read Operations
    # =========================================================================

    async def list_locations(
        self,
        company_id: int,
        page: int = 1,
        per_page: int = 25,
    ) -> dict[str, Any]:
        """List locations for a company.

        Args:
            company_id: Company ID.
            page: Page number (default 1).
            per_page: Results per page (default 25).

        Returns:
            API response with locations data.
        """
        response = await self._request(
            "GET",
            "/api/v1/locations",
            params={
                "company_id": company_id,
                "page": page,
                "per_page": per_page,
            },
        )
        response.raise_for_status()
        return response.json()

    async def get_location(self, location_id: int) -> dict[str, Any]:
        """Get a specific location.

        Args:
            location_id: Location ID.

        Returns:
            Location data.
        """
        response = await self._request(
            "GET",
            f"/api/v1/locations/{location_id}",
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Memberships - Read Operations
    # =========================================================================

    async def list_memberships(
        self,
        company_id: int,
        client_ids: list[int],
        page: int = 1,
        per_page: int = 25,
    ) -> dict[str, Any]:
        """List memberships for a company.

        Args:
            company_id: Company ID.
            client_ids: Client IDs to filter memberships.
            page: Page number (default 1).
            per_page: Results per page (default 25).

        Returns:
            API response with memberships data.
        """
        params: dict[str, Any] = {
            "company_id": company_id,
            "page": page,
            "per_page": per_page,
        }
        params["client_ids"] = client_ids

        response = await self._request(
            "GET",
            "/api/v1/memberships",
            params=params,
        )
        response.raise_for_status()
        return response.json()

    async def get_membership(
        self,
        membership_id: int,
        date: str | None = None,
    ) -> dict[str, Any]:
        """Get a specific membership.

        Args:
            membership_id: Membership ID.
            date: Date for pricing (YYYY-MM-DD, optional).

        Returns:
            Membership data.
        """
        params: dict[str, Any] = {}
        if date is not None:
            params["date"] = date

        response = await self._request(
            "GET",
            f"/api/v1/memberships/{membership_id}",
            params=params if params else None,
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Users - Read Operations
    # =========================================================================

    async def get_user(self, user_id: int, company_id: int) -> dict[str, Any]:
        """Get a specific user.

        Note: Users are cross-company entities, so no company ownership
        validation is performed. The company_id is passed as a query
        parameter for JWT validation.

        Args:
            user_id: User ID.
            company_id: Company ID (for JWT validation).

        Returns:
            User data including email, first_name, last_name.
        """
        response = await self._request(
            "GET",
            f"/api/v1/users/{user_id}",
            params={"company_id": company_id},
        )
        response.raise_for_status()
        return response.json()

    # =========================================================================
    # Providers (Service Professionals) - Read Operations
    # =========================================================================

    async def list_providers(
        self,
        company_id: int,
        location_ids: list[int] | None = None,
        active: bool | None = None,
        public_name: str | None = None,
        service_ids: list[int] | None = None,
        page: int = 1,
        per_page: int = 25,
    ) -> dict[str, Any]:
        """List providers (service professionals) for a company.

        Args:
            company_id: Company ID.
            location_ids: Filter by location IDs (optional).
            active: Filter by active status (optional).
            public_name: Filter by public name (optional).
            service_ids: Filter by service IDs (optional).
            page: Page number (default 1).
            per_page: Results per page (default 25).

        Returns:
            API response with providers data.
        """
        params: dict[str, Any] = {
            "company_id": company_id,
            "page": page,
            "per_page": per_page,
        }
        if location_ids is not None:
            params["location_ids"] = location_ids
        if active is not None:
            params["active"] = active
        if public_name is not None:
            params["public_name"] = public_name
        if service_ids is not None:
            params["service_ids"] = service_ids

        response = await self._request(
            "GET",
            "/api/v1/providers",
            params=params,
        )
        response.raise_for_status()
        return response.json()

    async def get_provider(self, provider_id: int) -> dict[str, Any]:
        """Get a specific provider (service professional).

        Args:
            provider_id: Provider ID.

        Returns:
            Provider data.
        """
        response = await self._request(
            "GET",
            f"/api/v1/providers/{provider_id}",
        )
        response.raise_for_status()
        return response.json()

    # ── Analytics ──────────────────────────────────────────────

    async def query_analytics(
        self,
        company_id: int,
        sql: str,
        freshness_policy: str = "allow_stale",
    ) -> dict:
        """Execute a read-only SQL query against analytics data.

        Args:
            company_id: Company ID to query.
            sql: SQL SELECT query.
            freshness_policy: "allow_stale" or "require_fresh".

        Returns:
            Query result dict with columns, rows, metadata.
        """
        response = await self._request(
            "POST",
            "/api/v1/analytics/query",
            json={
                "sql": sql,
                "company_id": company_id,
                "freshness_policy": freshness_policy,
            },
        )
        # 202 is a valid response (warming up), don't raise
        if response.status_code not in (200, 202):
            response.raise_for_status()
        return response.json()

    async def get_analytics_status(self, company_id: int) -> dict:
        """Get analytics sync status for a company.

        Args:
            company_id: Company ID to check.

        Returns:
            Status dict with state, snapshot_version, etc.
        """
        response = await self._request(
            "GET",
            f"/api/v1/analytics/status/{company_id}",
        )
        response.raise_for_status()
        return response.json()

    async def refresh_analytics(self, company_id: int) -> dict:
        """Force an immediate refresh of analytics data for a company.

        Args:
            company_id: Company ID to refresh.

        Returns:
            Response dict with message. Accepts 202 and 409 without raising.
        """
        response = await self._request(
            "POST",
            f"/api/v1/analytics/refresh/{company_id}",
        )
        # Accept 202 (success/in-progress/queued) and 409 (cooldown) without raising
        if response.status_code not in (202, 409):
            response.raise_for_status()
        return response.json()
