"""
Base LLM Client Interface

Based on C# ILlmClient.cs:
/Users/alper/vibe-code-analyzer/src/Warden.LLM/ILlmClient.cs

All provider implementations must inherit from this interface
"""

import json
from abc import ABC, abstractmethod

from warden.shared.infrastructure.exceptions import ExternalServiceError

from ..types import LlmProvider, LlmRequest, LlmResponse


class ILlmClient(ABC):
    """
    Interface for LLM providers

    Matches C# ILlmClient interface
    All providers (Anthropic, DeepSeek, QwenCode, etc.) must implement this
    """

    @property
    @abstractmethod
    def provider(self) -> LlmProvider:
        """
        The provider type

        Returns:
            LlmProvider enum value
        """
        pass

    @abstractmethod
    async def send_async(self, request: LlmRequest) -> LlmResponse:
        """
        Send a request to the LLM provider

        Args:
            request: The LLM request parameters

        Returns:
            LLM response with content or error

        Raises:
            Should NOT raise exceptions - return LlmResponse with success=False instead
        """
        pass

    @abstractmethod
    async def is_available_async(self) -> bool:
        """
        Check if the provider is available/configured

        Returns:
            True if the provider is ready to use, False otherwise

        Note:
            Should NOT raise exceptions - return False on any error
        """
        pass

    async def complete_async(
        self,
        prompt: str,
        system_prompt: str = "You are a helpful coding assistant.",
        model: str | None = None,
        use_fast_tier: bool = False,
    ) -> LlmResponse:
        """
        Simple completion method for non-streaming requests.

        Args:
            prompt: User prompt
            system_prompt: System prompt (optional)
            model: Model override (optional)
            use_fast_tier: If True, request fast (local) tier

        Returns:
            LlmResponse with content and token usage

        Raises:
            Exception: If request fails
        """
        # Default implementation using send_async
        request = LlmRequest(
            user_message=prompt,
            system_prompt=system_prompt,
            model=model,  # Use provider default or override
            temperature=0.0,  # Idempotency
            max_tokens=2000,
            timeout_seconds=120.0,
            use_fast_tier=use_fast_tier,
        )

        response = await self.send_async(request)

        if not response.success:
            raise ExternalServiceError(f"LLM request failed: {response.error_message}")

        return response

    async def analyze_security_async(self, code_content: str, language: str, use_fast_tier: bool = False) -> dict:
        """
        Analyze code for security vulnerabilities using LLM.

        Default implementation uses complete_async with a standard prompt.
        Providers may override this for specialized models or parameters.

        Args:
            code_content: Source code to analyze
            language: Language of the code

        Returns:
            Dict containing findings list
        """
        from warden.shared.utils.json_parser import parse_json_from_llm
        from warden.shared.utils.token_utils import truncate_content_for_llm

        # Truncate code using token-aware truncation (not character-based)
        truncated_code = truncate_content_for_llm(
            code_content,
            max_tokens=1800,  # Reserve tokens for prompt and response
            preserve_start_lines=30,
            preserve_end_lines=15,
        )

        prompt = f"""
        You are a senior security researcher. Analyze this {language} code for critical vulnerabilities.
        Target vulnerabilities: SSRF, CSRF, XXE, Insecure Deserialization, Path Traversal, and Command Injection.

        Ignore stylistic issues. Focus only on exploitable security flaws.

        Return a JSON object in this exact format:
        {{
            "findings": [
                {{
                    "severity": "critical|high|medium",
                    "message": "Short description",
                    "line_number": 1,
                    "detail": "Detailed explanation of the exploit vector",
                    "source": "Where tainted data enters, e.g. request.args['id'] (line 14)",
                    "sink": "Where tainted data is consumed unsafely, e.g. cursor.execute() (line 45)",
                    "data_flow": ["function_or_variable_names", "showing", "the path"]
                }}
            ]
        }}

        The source, sink, and data_flow fields are optional but highly valuable for triage.
        If no issues found, return {{ "findings": [] }}.

        Code:
        ```{language}
        {truncated_code}
        ```
        """

        try:
            response = await self.complete_async(
                prompt,
                system_prompt="You are a strict security auditor. Output valid JSON only.",
                use_fast_tier=use_fast_tier,
            )
            if not response.success:
                return {"findings": []}

            parsed = parse_json_from_llm(response.content)
            if not parsed:
                return {"findings": []}

            # Enrich findings with MachineContext from structured LLM output
            self._enrich_findings_from_llm(parsed)
            return parsed
        except (json.JSONDecodeError, ValueError, KeyError):
            # Log but don't crash - return safe default
            return {"findings": []}

    @staticmethod
    def _enrich_findings_from_llm(parsed: dict) -> None:
        """Attach machine_context to parsed findings when source/sink/data_flow present."""
        import html

        from warden.shared.utils.prompt_sanitizer import PromptSanitizer

        for item in parsed.get("findings", []):
            has_structured = item.get("source") or item.get("sink") or item.get("data_flow")
            if not has_structured:
                continue
            # Validate types before assignment (LLM output is untrusted)
            source = item.get("source")
            sink = item.get("sink")
            data_flow = item.get("data_flow")
            if source and not isinstance(source, str):
                source = str(source)
            if source:
                source = html.escape(PromptSanitizer.escape_prompt_injection(source))

            if sink and not isinstance(sink, str):
                sink = str(sink)
            if sink:
                sink = html.escape(PromptSanitizer.escape_prompt_injection(sink))

            if data_flow and not isinstance(data_flow, list):
                data_flow = []
            else:
                data_flow = [html.escape(PromptSanitizer.escape_prompt_injection(str(x))) for x in (data_flow or [])]
            item["_machine_context"] = {
                "source": source,
                "sink": sink,
                "data_flow_path": data_flow,
            }
