"""HTTP client for Redis API."""

import asyncio
import httpx
from typing import Callable, Optional, Dict, Any


class RedisAPIClient:
    """Client for redis.ayga.tech API."""
    
    def __init__(
        self,
        base_url: str = "https://redis.ayga.tech",
        username: Optional[str] = None,
        password: Optional[str] = None,
        api_key: Optional[str] = None,
    ):
        self.base_url = base_url.rstrip("/")
        self.username = username
        self.password = password
        self.api_key = api_key
        self._token: Optional[str] = None
        self._client: Optional[httpx.AsyncClient] = None
    
    async def _get_client(self) -> httpx.AsyncClient:
        """Get or create HTTP client with auth."""
        if self._client is None:
            self._client = httpx.AsyncClient(timeout=120.0)
            
            # Authenticate if credentials provided
            if self.username and self.password:
                await self._login()
            elif self.api_key:
                await self._exchange_api_key()
        
        return self._client
    
    async def _login(self):
        """Login with username/password."""
        response = await self._client.post(
            f"{self.base_url}/auth/login",
            json={"username": self.username, "password": self.password},
        )
        response.raise_for_status()
        self._token = response.json()["access_token"]
    
    async def _exchange_api_key(self):
        """Exchange API key for JWT token."""
        response = await self._client.post(
            f"{self.base_url}/auth/exchange",
            headers={"X-API-Key": self.api_key},
        )
        response.raise_for_status()
        self._token = response.json()["access_token"]
    
    async def health_check(self) -> Dict[str, Any]:
        """Check API health."""
        client = await self._get_client()
        response = await client.get(f"{self.base_url}/health")
        response.raise_for_status()
        return response.json()
    
    async def list_parsers(self) -> Dict[str, Any]:
        """Get list of available parsers."""
        client = await self._get_client()
        response = await client.get(
            f"{self.base_url}/parsers",
            headers={"Authorization": f"Bearer {self._token}"} if self._token else {},
        )
        response.raise_for_status()
        return response.json()
    
    async def get_parser_info(self, parser_id: str) -> Dict[str, Any]:
        """Get parser details."""
        client = await self._get_client()
        response = await client.get(
            f"{self.base_url}/parsers/{parser_id}",
            headers={"Authorization": f"Bearer {self._token}"} if self._token else {},
        )
        response.raise_for_status()
        return response.json()
    
    async def submit_parser_task(self, parser_id: str, query: str, options: Optional[Dict] = None) -> Dict[str, Any]:
        """Submit parser task via Redis queue.
        
        Args:
            parser_id: Parser ID (e.g., 'perplexity', 'chatgpt')
            query: Query string
            options: Optional parser options
            
        Returns:
            Dict with 'task_id' key
        """
        import uuid
        import json as stdlib_json
        
        client = await self._get_client()
        
        # Generate task ID
        task_id = str(uuid.uuid4())
        
        # Map parser_id to A-Parser format
        parser_map = {
            "perplexity": "FreeAI::Perplexity",
            "chatgpt": "FreeAI::ChatGPT",
            "claude": "FreeAI::Claude",
            "gemini": "FreeAI::Gemini",
            "copilot": "FreeAI::Copilot",
            "grok": "FreeAI::Grok",
            "deepseek": "FreeAI::DeepSeek",
            "kimi": "FreeAI::Kimi",
            "deepai": "FreeAI::DeepAI",
            "net_http": "Net::HTTP",
        }
        
        aparser_name = parser_map.get(parser_id, f"FreeAI::{parser_id.title()}")
        preset = options.get("preset", "default") if options else "default"
        
        # A-Parser task format: [taskId, parser, preset, query, {}, {}]
        task_data = [task_id, aparser_name, preset, query, {}, {}]
        task_json = stdlib_json.dumps(task_data)
        
        # Submit to Redis list via structures API
        queue_key = "aparser_redis_api"
        response = await client.post(
            f"{self.base_url}/structures/list/{queue_key}/lpush",
            json={"value": task_json},
            headers={"Authorization": f"Bearer {self._token}"},
        )
        response.raise_for_status()
        
        return {"task_id": task_id}
    
    async def get_task_result(self, task_id: str) -> Optional[Dict[str, Any]]:
        """Get task result from Redis KV.
        
        Args:
            task_id: Task ID from submit_parser_task
            
        Returns:
            Parsed result dict if available, None if not ready yet
        """
        import json as stdlib_json
        
        client = await self._get_client()
        result_key = f"aparser_redis_api:{task_id}"
        
        response = await client.get(
            f"{self.base_url}/kv/{result_key}",
            headers={"Authorization": f"Bearer {self._token}"},
        )
        
        if response.status_code == 404:
            # Task not ready yet
            return None
        
        response.raise_for_status()
        
        # Parse result (comes as string value from Redis)
        result_json = response.json()
        if not result_json or "value" not in result_json:
            return None
            
        # Parse value string
        try:
            result_data = stdlib_json.loads(result_json["value"])
            
            # Check if it's A-Parser array format: [taskId, status, errorCode, errorMsg, data, ...]
            if isinstance(result_data, list) and len(result_data) >= 5:
                task_id_ret, status, error_code, error_msg, data, *rest = result_data
                
                # Check for errors
                if error_code:
                    raise ValueError(f"Parser error {error_code}: {error_msg}")
                
                if status != "success":
                    return None  # Still processing
                
                # Return parsed data
                return {"data": data, "task_id": task_id_ret}
            
            # Otherwise, it's already a dict (new API format)
            elif isinstance(result_data, dict):
                # Check for success
                if result_data.get("success") == 1:
                    return {"data": result_data, "task_id": task_id}
                elif result_data.get("error"):
                    raise ValueError(f"Parser error: {result_data.get('error')}")
                else:
                    return None  # Still processing
            
            else:
                raise ValueError(f"Unexpected result format: {type(result_data)}")
            
        except (stdlib_json.JSONDecodeError, ValueError) as e:
            raise ValueError(f"Failed to parse result: {e}")
    
    async def wait_for_result(
        self,
        task_id: str,
        timeout: int = 90,
        progress_callback: Optional[Callable[[float], None]] = None,
    ) -> Dict[str, Any]:
        """Poll for task result with progressive backoff."""
        start = asyncio.get_event_loop().time()
        delay = 1.0
        
        while True:
            elapsed = asyncio.get_event_loop().time() - start
            if elapsed > timeout:
                raise TimeoutError(f"Task {task_id} timed out after {timeout}s")
            
            result = await self.get_task_result(task_id)
            
            if result:
                return result
            
            # Report progress
            if progress_callback:
                progress_callback(min(elapsed / timeout, 0.95))
            
            # Progressive backoff: 1s, 1.5s, 2s, 2.5s, max 3s
            await asyncio.sleep(delay)
            delay = min(delay * 1.2, 3.0)
    
    async def close(self):
        """Close HTTP client."""
        if self._client:
            await self._client.aclose()
