from typing import Any, Dict, Optional import httpx from ..models.token import AuthenticationToken class ApiClient: """API client for communicating with backend""" def __init__(self, base_url: str = "https://api.garmin.com"): self.base_url = base_url self.client = httpx.AsyncClient() self.token: Optional[AuthenticationToken] = None async def set_token(self, token: AuthenticationToken) -> None: """Set the authentication token for API requests""" self.token = token self.client.headers["Authorization"] = ( f"{token.token_type} {token.access_token}" ) async def authenticate_user( self, username: str, password: str, mfa_code: Optional[str] = None ) -> Dict[str, Any]: """Authenticate user via CLI with optional MFA""" url = f"{self.base_url}/api/auth/cli/login" payload = {"username": username, "password": password} if mfa_code: payload["mfa_code"] = mfa_code try: response = await self.client.post(url, json=payload) response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: # Handle HTTP errors (4xx, 5xx) error_detail = await self._extract_error_detail(response) raise Exception(f"API Error: {e.response.status_code} - {error_detail}") except httpx.RequestError as e: # Handle request errors (network, timeout, etc.) raise Exception(f"Request Error: {str(e)}") async def trigger_sync( self, sync_type: str, date_range: Optional[Dict[str, str]] = None, force_full_sync: bool = False, ) -> Dict[str, Any]: """Trigger a sync operation""" url = f"{self.base_url}/api/sync/cli/trigger" payload = {"sync_type": sync_type, "force_full_sync": force_full_sync} if date_range: payload["date_range"] = date_range try: response = await self.client.post(url, json=payload) response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: # Handle HTTP errors (4xx, 5xx) including 409 conflict error_detail = await self._extract_error_detail(response) raise Exception(f"API Error: {e.response.status_code} - {error_detail}") except httpx.RequestError as e: # Handle request errors (network, timeout, etc.) raise Exception(f"Request Error: {str(e)}") async def get_sync_status(self, job_id: Optional[str] = None) -> Dict[str, Any]: """Get sync status for all jobs or a specific job""" if job_id: url = f"{self.base_url}/api/sync/cli/status/{job_id}" else: url = f"{self.base_url}/api/sync/cli/status" try: response = await self.client.get(url) response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: # Handle HTTP errors (4xx, 5xx) error_detail = await self._extract_error_detail(response) raise Exception(f"API Error: {e.response.status_code} - {error_detail}") except httpx.RequestError as e: # Handle request errors (network, timeout, etc.) raise Exception(f"Request Error: {str(e)}") async def _extract_error_detail(self, response: httpx.Response) -> str: """Extract error details from response""" try: error_json = response.json() return error_json.get("error", "Unknown error") except Exception: return response.text[:200] # Return first 200 chars if not JSON async def close(self) -> None: """Close the HTTP client""" await self.client.aclose()