"""LLM provider implementations."""

from abc import ABC, abstractmethod
from typing import Any, Optional

import httpx

from ..config import ModelConfig


class ActionPlan:
    """Represents a plan of actions to execute."""
    def __init__(self, actions: list[dict[str, Any]]):
        self.actions = actions
    
    def __repr__(self) -> str:
        return f"ActionPlan({self.actions})"


class LLMProvider(ABC):
    """Abstract base class for LLM providers."""
    
    def __init__(self, config: ModelConfig):
        self.config = config
        self.client = httpx.AsyncClient(timeout=60.0)
    
    @abstractmethod
    async def understand(self, instruction: str) -> ActionPlan:
        """
        Convert natural language instruction to an action plan.
        
        Args:
            instruction: Natural language instruction from user
            
        Returns:
            ActionPlan containing list of actions to execute
        """
        pass
    
    @abstractmethod
    async def chat(self, messages: list[dict[str, str]]) -> str:
        """
        Send chat messages to the LLM.
        
        Args:
            messages: List of message dicts with 'role' and 'content'
            
        Returns:
            LLM response text
        """
        pass
    
    @abstractmethod
    async def extract_from_page(self, html_content: str, instruction: str) -> str:
        """
        Extract data from HTML based on natural language instruction.
        
        Args:
            html_content: HTML content of the page
            instruction: What to extract (e.g., "get all prices")
            
        Returns:
            Extracted data as string
        """
        pass
    
    async def close(self) -> None:
        """Close the HTTP client."""
        await self.client.aclose()


class KimiProvider(LLMProvider):
    """Moonshot Kimi LLM provider."""
    
    DEFAULT_BASE_URL = "https://api.moonshot.cn/v1"
    DEFAULT_MODEL = "moonshot-v1-8k"
    
    def __init__(self, config: ModelConfig):
        super().__init__(config)
        self.base_url = config.base_url or self.DEFAULT_BASE_URL
        self.model = config.model_name or self.DEFAULT_MODEL
    
    async def understand(self, instruction: str) -> ActionPlan:
        """Convert instruction to action plan."""
        system_prompt = """You are a browser automation assistant. Convert user instructions into a JSON action plan.

Available actions:
- {"type": "goto", "url": "https://example.com"} - Navigate to URL
- {"type": "click", "selector": "button.submit"} - Click element
- {"type": "type", "selector": "input.search", "text": "query"} - Type text
- {"type": "extract", "selector": ".price", "attribute": "text"} - Extract data
- {"type": "screenshot", "path": "screenshot.png"} - Take screenshot
- {"type": "wait", "seconds": 2} - Wait

Respond with ONLY a JSON array of actions. Example:
[
  {"type": "goto", "url": "https://github.com/openclaw/openclaw"},
  {"type": "extract", "selector": "[href='/openclaw/openclaw/stargazers']", "attribute": "text"}
]"""

        response = await self._chat_completion([
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": instruction}
        ])
        
        # Parse JSON response
        import json
        try:
            actions = json.loads(response)
            return ActionPlan(actions)
        except json.JSONDecodeError:
            # Fallback: wrap in a simple action
            return ActionPlan([{"type": "goto", "url": instruction}])
    
    async def chat(self, messages: list[dict[str, str]]) -> str:
        """Send chat messages."""
        return await self._chat_completion(messages)
    
    async def extract_from_page(self, html_content: str, instruction: str) -> str:
        """Extract data from HTML."""
        system_prompt = f"""You are a data extraction assistant. Extract information from the provided HTML based on the user's instruction.

Instruction: {instruction}

Respond with ONLY the extracted data, no explanations."""

        # Truncate HTML if too long
        max_length = 10000
        truncated_html = html_content[:max_length] if len(html_content) > max_length else html_content
        
        return await self._chat_completion([
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"HTML:\n{truncated_html}"}
        ])
    
    async def _chat_completion(self, messages: list[dict[str, str]]) -> str:
        """Make chat completion API call."""
        response = await self.client.post(
            f"{self.base_url}/chat/completions",
            headers={"Authorization": f"Bearer {self.config.api_key}"},
            json={
                "model": self.model,
                "messages": messages,
                "temperature": 0.3,
            }
        )
        response.raise_for_status()
        data = response.json()
        return data["choices"][0]["message"]["content"]


class DeepSeekProvider(LLMProvider):
    """DeepSeek LLM provider."""
    
    DEFAULT_BASE_URL = "https://api.deepseek.com/v1"
    DEFAULT_MODEL = "deepseek-chat"
    
    def __init__(self, config: ModelConfig):
        super().__init__(config)
        self.base_url = config.base_url or self.DEFAULT_BASE_URL
        self.model = config.model_name or self.DEFAULT_MODEL
    
    async def understand(self, instruction: str) -> ActionPlan:
        """Convert instruction to action plan."""
        system_prompt = """You are a browser automation assistant. Convert user instructions into a JSON action plan.

Available actions:
- {"type": "goto", "url": "https://example.com"} - Navigate to URL
- {"type": "click", "selector": "button.submit"} - Click element
- {"type": "type", "selector": "input.search", "text": "query"} - Type text
- {"type": "extract", "selector": ".price", "attribute": "text"} - Extract data
- {"type": "screenshot", "path": "screenshot.png"} - Take screenshot
- {"type": "wait", "seconds": 2} - Wait

Respond with ONLY a JSON array of actions."""

        response = await self._chat_completion([
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": instruction}
        ])
        
        import json
        try:
            actions = json.loads(response)
            return ActionPlan(actions)
        except json.JSONDecodeError:
            return ActionPlan([{"type": "goto", "url": instruction}])
    
    async def chat(self, messages: list[dict[str, str]]) -> str:
        """Send chat messages."""
        return await self._chat_completion(messages)
    
    async def extract_from_page(self, html_content: str, instruction: str) -> str:
        """Extract data from HTML."""
        system_prompt = f"""Extract information from the provided HTML based on: {instruction}

Respond with ONLY the extracted data."""

        max_length = 10000
        truncated_html = html_content[:max_length] if len(html_content) > max_length else html_content
        
        return await self._chat_completion([
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"HTML:\n{truncated_html}"}
        ])
    
    async def _chat_completion(self, messages: list[dict[str, str]]) -> str:
        """Make chat completion API call."""
        response = await self.client.post(
            f"{self.base_url}/chat/completions",
            headers={"Authorization": f"Bearer {self.config.api_key}"},
            json={
                "model": self.model,
                "messages": messages,
                "temperature": 0.3,
            }
        )
        response.raise_for_status()
        data = response.json()
        return data["choices"][0]["message"]["content"]


class OpenAIProvider(LLMProvider):
    """OpenAI LLM provider."""
    
    DEFAULT_BASE_URL = "https://api.openai.com/v1"
    DEFAULT_MODEL = "gpt-4o-mini"
    
    def __init__(self, config: ModelConfig):
        super().__init__(config)
        self.base_url = config.base_url or self.DEFAULT_BASE_URL
        self.model = config.model_name or self.DEFAULT_MODEL
    
    async def understand(self, instruction: str) -> ActionPlan:
        """Convert instruction to action plan."""
        system_prompt = """You are a browser automation assistant. Convert user instructions into a JSON action plan.

Available actions:
- {"type": "goto", "url": "https://example.com"}
- {"type": "click", "selector": "button.submit"}
- {"type": "type", "selector": "input.search", "text": "query"}
- {"type": "extract", "selector": ".price", "attribute": "text"}
- {"type": "screenshot", "path": "screenshot.png"}
- {"type": "wait", "seconds": 2}

Respond with ONLY a JSON array of actions."""

        response = await self._chat_completion([
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": instruction}
        ])
        
        import json
        try:
            actions = json.loads(response)
            return ActionPlan(actions)
        except json.JSONDecodeError:
            return ActionPlan([{"type": "goto", "url": instruction}])
    
    async def chat(self, messages: list[dict[str, str]]) -> str:
        """Send chat messages."""
        return await self._chat_completion(messages)
    
    async def extract_from_page(self, html_content: str, instruction: str) -> str:
        """Extract data from HTML."""
        system_prompt = f"""Extract information from HTML based on: {instruction}

Respond with ONLY the extracted data."""

        max_length = 10000
        truncated_html = html_content[:max_length] if len(html_content) > max_length else html_content
        
        return await self._chat_completion([
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"HTML:\n{truncated_html}"}
        ])
    
    async def _chat_completion(self, messages: list[dict[str, str]]) -> str:
        """Make chat completion API call."""
        response = await self.client.post(
            f"{self.base_url}/chat/completions",
            headers={"Authorization": f"Bearer {self.config.api_key}"},
            json={
                "model": self.model,
                "messages": messages,
                "temperature": 0.3,
            }
        )
        response.raise_for_status()
        data = response.json()
        return data["choices"][0]["message"]["content"]


class OllamaProvider(LLMProvider):
    """Ollama local LLM provider."""

    DEFAULT_BASE_URL = "http://localhost:11434"
    DEFAULT_MODEL = "qwen2.5:3b"

    def __init__(self, config: ModelConfig):
        super().__init__(config)
        self.base_url = config.base_url or self.DEFAULT_BASE_URL
        self.model = config.model_name or self.DEFAULT_MODEL
        # Increase timeout for local CPU inference
        self.client = httpx.AsyncClient(timeout=120.0)
    
    async def understand(self, instruction: str) -> ActionPlan:
        """Convert instruction to action plan."""
        system_prompt = """You are a browser automation assistant. Convert user instructions into a JSON action plan.

Available actions:
- {"type": "goto", "url": "https://example.com"} - Navigate to URL
- {"type": "click", "selector": "button.submit"} - Click element
- {"type": "type", "selector": "input.search", "text": "query"} - Type text
- {"type": "extract", "selector": ".price", "attribute": "text"} - Extract data
- {"type": "screenshot", "path": "screenshot.png"} - Take screenshot
- {"type": "wait", "seconds": 2} - Wait

Respond with ONLY a JSON array of actions. Example:
[
  {"type": "goto", "url": "https://github.com/openclaw/openclaw"},
  {"type": "extract", "selector": "[href='/openclaw/openclaw/stargazers']", "attribute": "text"}
]"""

        response = await self._chat_completion([
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": instruction}
        ])
        
        import json
        try:
            # Try to extract JSON from response
            response = response.strip()
            # Find JSON array in response
            start = response.find('[')
            end = response.rfind(']')
            if start != -1 and end != -1:
                actions = json.loads(response[start:end+1])
            else:
                actions = json.loads(response)
            return ActionPlan(actions)
        except (json.JSONDecodeError, ValueError):
            # Fallback: simple goto
            return ActionPlan([{"type": "goto", "url": instruction}])
    
    async def chat(self, messages: list[dict[str, str]]) -> str:
        """Send chat messages."""
        return await self._chat_completion(messages)
    
    async def extract_from_page(self, html_content: str, instruction: str) -> str:
        """Extract data from HTML."""
        system_prompt = f"""Extract information from the provided HTML based on the user's instruction.

Instruction: {instruction}

Respond with ONLY the extracted data, no explanations."""

        # Truncate HTML if too long
        max_length = 8000
        truncated_html = html_content[:max_length] if len(html_content) > max_length else html_content
        
        return await self._chat_completion([
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"HTML:\n{truncated_html}"}
        ])
    
    async def _chat_completion(self, messages: list[dict[str, str]]) -> str:
        """Make chat completion API call to Ollama."""
        # Convert messages to Ollama format
        prompt = ""
        for msg in messages:
            if msg["role"] == "system":
                prompt += f"System: {msg['content']}\n\n"
            elif msg["role"] == "user":
                prompt += f"User: {msg['content']}\n\n"
            elif msg["role"] == "assistant":
                prompt += f"Assistant: {msg['content']}\n\n"

        # Try /api/chat first (newer Ollama versions)
        try:
            response = await self.client.post(
                f"{self.base_url}/api/chat",
                json={
                    "model": self.model,
                    "messages": messages,
                    "stream": False,
                    "options": {
                        "temperature": 0.3,
                        "num_predict": 500,  # Limit response length for faster inference
                    }
                }
            )
            response.raise_for_status()
            data = response.json()
            return data.get("message", {}).get("content", "")
        except httpx.TimeoutError:
            # Return fallback on timeout
            return '[{"type": "goto", "url": "https://example.com"}]'
        except Exception:
            # Fallback to /api/generate (older Ollama versions)
            try:
                response = await self.client.post(
                    f"{self.base_url}/api/generate",
                    json={
                        "model": self.model,
                        "prompt": prompt,
                        "stream": False,
                        "options": {
                            "temperature": 0.3,
                            "num_predict": 500,
                        }
                    }
                )
                response.raise_for_status()
                data = response.json()
                return data.get("response", "")
            except httpx.TimeoutError:
                return '[{"type": "goto", "url": "https://example.com"}]'
