"""Garmin Health API cloud connector implementation."""

import json
from typing import Any
import httpx

from synheart_cloud_connector import CloudConnectorBase, WebhookError
from synheart_cloud_connector.vendor_types import VendorType, WebhookEvent
from synheart_cloud_connector.webhooks import extract_signature_from_headers


class GarminConnector(CloudConnectorBase):
    """
    Garmin Health API cloud connector.

    Implements Garmin-specific OAuth, webhook verification, and data fetching.

    Garmin Health API Documentation:
    - Garmin Developer Program Start Guide
    - Garmin Health API Spec

    Supported data types:
    - dailies (Daily summaries)
    - sleeps (Sleep data)
    - activities (Workouts)
    - bodyComps (Body composition)
    - stressDetails (Stress data)
    - userMetrics (User stats)
    - pulseox (SpO2)
    - hrv (HRV data)
    """

    @property
    def vendor(self) -> VendorType:
        """Return Garmin vendor type."""
        return VendorType.GARMIN

    async def verify_webhook(self, headers: dict[str, Any], raw_body: bytes) -> bool:
        """
        Verify Garmin webhook signature.

        Garmin uses different verification depending on the service level.
        For evaluation mode, it might not use signatures, but for production,
        it uses a webhook secret if configured.

        Args:
            headers: HTTP headers
            raw_body: Raw request body

        Returns:
            True if valid
        """
        # Garmin pings/pushes often don't have a signature header in some configurations,
        # but if we have a webhook secret, we should verify it.
        if self.webhook_verifier and "Signature" in headers:
            # Garmin specific signature verification would go here
            # For now, we'll implement a basic check or return True if not configured
            pass
        
        return True

    async def parse_event(self, raw_body: bytes) -> dict[str, Any]:
        """
        Parse Garmin webhook payload.

        Garmin uses two main types of notifications:
        1. Ping Service (Callback URLs)
        2. Push Service (Data in payload)

        Format (Ping):
        {
          "dailies": [
            {
              "userId": "...",
              "callbackURL": "...",
              "startTimeInSeconds": ...,
              "summaryId": "..."
            }
          ]
        }

        Args:
            raw_body: Raw request body

        Returns:
            Parsed event data
        """
        try:
            data = json.loads(raw_body)
        except json.JSONDecodeError as e:
            raise WebhookError(f"Invalid JSON payload: {e}", vendor=self.vendor.value)

        # Garmin sends arrays of summaries. We want to normalize this.
        # For simplicity, we'll return the first one as the primary event for enqueueing
        # and include the full list in the payload.
        
        summary_types = [
            "dailies", "epochs", "sleeps", "bodyComps", "stressDetails",
            "userMetrics", "pulseox", "allDayRespiration", "healthSnapshot",
            "hrv", "bloodPressures", "skinTemp", "activities", "moveIQ"
        ]

        found_type = None
        for s_type in summary_types:
            if s_type in data and data[s_type]:
                found_type = s_type
                break

        if not found_type:
             raise WebhookError("No supported Garmin summary types found in payload", vendor=self.vendor.value)

        # Normalize to a single event format for consistent processing
        first_item = data[found_type][0]
        
        normalized = {
            "user_id": first_item.get("userId"),
            "type": f"garmin.{found_type}",
            "id": first_item.get("summaryId"),
            "callback_url": first_item.get("callbackURL"), # Only for Ping service
            "full_payload": data
        }

        return normalized

    async def fetch_data(
        self,
        user_id: str,
        resource_type: str,
        resource_id: str | None = None,
        **kwargs: Any
    ) -> dict[str, Any]:
        """
        Fetch data from Garmin Health API.

        Garmin endpoints vary by resource type.

        Args:
            user_id: User identifier
            resource_type: Resource type (dailies, sleeps, etc.)
            resource_id: Specific summary ID or callback URL

        Returns:
            Raw Garmin data
        """
        self.check_rate_limit(user_id)
        tokens = await self.refresh_if_needed(user_id)

        # Build request
        url = resource_id if resource_id and resource_id.startswith("http") else None
        
        if not url:
            # Fallback to manual date range if no callback/id provided
            start = kwargs.get("start_time_seconds")
            end = kwargs.get("end_time_seconds")
            if start and end:
                url = f"{self.config.base_url}/{resource_type}?uploadStartTimeInSeconds={start}&uploadEndTimeInSeconds={end}"
            else:
                 raise ValueError("resource_id (callbackURL) or start/end times required for Garmin fetch")

        async with httpx.AsyncClient() as client:
            response = await client.get(
                url,
                headers={"Authorization": f"Bearer {tokens.access_token}"}
            )

            if response.status_code != 200:
                from synheart_cloud_connector.exceptions import VendorAPIError
                raise VendorAPIError(
                    f"Garmin API error: {response.status_code} {response.text}",
                    vendor=self.vendor.value,
                    status_code=response.status_code
                )

            self.token_store.update_last_pull(self.vendor, user_id)
            return response.json()

    # Specialized fetchers
    async def fetch_dailies(self, user_id: str, start: int, end: int) -> list[dict[str, Any]]:
        """Fetch daily summaries for a time range (Unix epoch seconds)."""
        data = await self.fetch_data(user_id, "dailies", start_time_seconds=start, end_time_seconds=end)
        return data if isinstance(data, list) else [data]

    async def fetch_sleeps(self, user_id: str, start: int, end: int) -> list[dict[str, Any]]:
        """Fetch sleep data."""
        data = await self.fetch_data(user_id, "sleeps", start_time_seconds=start, end_time_seconds=end)
        return data if isinstance(data, list) else [data]

    async def fetch_activities(self, user_id: str, start: int | None = None, end: int | None = None) -> list[dict[str, Any]]:
        """Fetch activities."""
        data = await self.fetch_data(user_id, "activities", start_time_seconds=start, end_time_seconds=end)
        return data if isinstance(data, list) else [data]
