"""Dr-Prompt Python SDK Client."""

import requests
from typing import Optional, List, Dict, Any
from urllib.parse import urljoin

from .models import Prompt, PromptVersion, Collection, UsageStats
from .exceptions import DrPromptError, AuthenticationError, NotFoundError, APIError


class DrPrompt:
    """
    Dr-Prompt API Client.

    A Python client for interacting with the Dr-Prompt API to manage
    prompts, collections, and track usage.

    Example:
        >>> from drprompt import DrPrompt
        >>> client = DrPrompt("https://your-drprompt-instance.com", api_key="drp_...")
        >>> prompt = client.get_prompt("abc123def456")  # Using UID
        >>> print(prompt.render({"topic": "Python"}))
    """

    def __init__(
        self,
        base_url: str,
        api_key: Optional[str] = None,
        token: Optional[str] = None,
        timeout: int = 30,
    ):
        """
        Initialize the Dr-Prompt client.

        Args:
            base_url: The base URL of your Dr-Prompt instance.
            api_key: API key for authentication (recommended, starts with drp_).
            token: Optional JWT token for authentication.
            timeout: Request timeout in seconds.
        """
        self.base_url = base_url.rstrip("/")
        self.api_url = f"{self.base_url}/api"
        self.token = api_key or token
        self.timeout = timeout
        self._session = requests.Session()

    def _get_headers(self) -> Dict[str, str]:
        """Get request headers with authentication."""
        headers = {"Content-Type": "application/json"}
        if self.token:
            headers["Authorization"] = f"Bearer {self.token}"
        return headers

    def _request(
        self,
        method: str,
        endpoint: str,
        data: Dict = None,
        params: Dict = None,
    ) -> Dict:
        """Make an API request."""
        url = f"{self.api_url}{endpoint}"

        try:
            response = self._session.request(
                method=method,
                url=url,
                json=data,
                params=params,
                headers=self._get_headers(),
                timeout=self.timeout,
            )
        except requests.RequestException as e:
            raise APIError(f"Request failed: {str(e)}")

        if response.status_code == 401:
            raise AuthenticationError("Invalid or expired token")
        elif response.status_code == 404:
            raise NotFoundError("Resource not found")
        elif response.status_code >= 400:
            error_detail = "Unknown error"
            try:
                error_detail = response.json().get("detail", error_detail)
            except Exception:
                pass
            raise APIError(error_detail, status_code=response.status_code)

        if response.status_code == 204:
            return {}

        return response.json()

    # ==================== Authentication ====================

    def login(self, username: str, password: str) -> str:
        """
        Authenticate with username and password.

        Args:
            username: Your Dr-Prompt username.
            password: Your Dr-Prompt password.

        Returns:
            The JWT access token.
        """
        data = self._request(
            "POST",
            "/auth/login",
            data={"username": username, "password": password},
        )
        self.token = data["access_token"]
        return self.token

    def set_token(self, token: str) -> None:
        """
        Set the authentication token directly.

        Args:
            token: JWT access token.
        """
        self.token = token

    # ==================== Prompts ====================

    def list_prompts(self) -> List[Prompt]:
        """
        List all prompts.

        Returns:
            List of Prompt objects.
        """
        data = self._request("GET", "/prompts")
        return [Prompt.from_dict(p) for p in data]

    def get_prompt(self, prompt: str, track: bool = True) -> Prompt:
        """
        Get a prompt by UID or ID.

        Args:
            prompt: The prompt UID (preferred) or ID.
            track: Whether to track this access for usage statistics.

        Returns:
            The Prompt object.
        """
        params = {"track": "true"} if track else {}
        data = self._request("GET", f"/prompts/{prompt}", params=params)
        return Prompt.from_dict(data)

    def create_prompt(
        self,
        name: str,
        content: str,
        description: Optional[str] = None,
    ) -> Prompt:
        """
        Create a new prompt.

        Args:
            name: The prompt name.
            content: The prompt content (can include {{variables}}).
            description: Optional description.

        Returns:
            The created Prompt object.
        """
        data = self._request(
            "POST",
            "/prompts",
            data={
                "name": name,
                "content": content,
                "description": description,
            },
        )
        return Prompt.from_dict(data)

    def update_prompt(
        self,
        prompt: str,
        content: str,
        name: Optional[str] = None,
        description: Optional[str] = None,
    ) -> Prompt:
        """
        Update a prompt (creates a new version).

        Args:
            prompt: The prompt UID (preferred) or ID.
            content: The new prompt content.
            name: Optional new name.
            description: Optional new description.

        Returns:
            The updated Prompt object.
        """
        payload = {"content": content}
        if name is not None:
            payload["name"] = name
        if description is not None:
            payload["description"] = description

        data = self._request("PUT", f"/prompts/{prompt}", data=payload)
        return Prompt.from_dict(data)

    def delete_prompt(self, prompt: str) -> None:
        """
        Delete a prompt.

        Args:
            prompt: The prompt UID (preferred) or ID to delete.
        """
        self._request("DELETE", f"/prompts/{prompt}")

    def get_prompt_versions(self, prompt: str) -> List[PromptVersion]:
        """
        Get all versions of a prompt.

        Args:
            prompt: The prompt UID (preferred) or ID.

        Returns:
            List of PromptVersion objects.
        """
        data = self._request("GET", f"/prompts/{prompt}/versions")
        return [PromptVersion.from_dict(v) for v in data]

    def get_prompt_version(self, prompt: str, version: int) -> PromptVersion:
        """
        Get a specific version of a prompt.

        Args:
            prompt: The prompt UID (preferred) or ID.
            version: The version number.

        Returns:
            The PromptVersion object.
        """
        data = self._request("GET", f"/prompts/{prompt}/versions/{version}")
        return PromptVersion.from_dict(data)

    # ==================== Collections ====================

    def list_collections(self) -> List[Collection]:
        """
        List all collections.

        Returns:
            List of Collection objects.
        """
        data = self._request("GET", "/collections")
        return [Collection.from_dict(c) for c in data]

    def get_collection(self, collection_id: int) -> Collection:
        """
        Get a collection with its prompts.

        Args:
            collection_id: The collection ID.

        Returns:
            The Collection object with prompts.
        """
        data = self._request("GET", f"/collections/{collection_id}")
        return Collection.from_dict(data)

    def create_collection(
        self,
        name: str,
        description: Optional[str] = None,
        color: str = "#00ff88",
    ) -> Collection:
        """
        Create a new collection.

        Args:
            name: The collection name.
            description: Optional description.
            color: Hex color code for the collection.

        Returns:
            The created Collection object.
        """
        data = self._request(
            "POST",
            "/collections",
            data={
                "name": name,
                "description": description,
                "color": color,
            },
        )
        return Collection.from_dict(data)

    def delete_collection(self, collection_id: int) -> None:
        """
        Delete a collection (prompts are not deleted).

        Args:
            collection_id: The collection ID to delete.
        """
        self._request("DELETE", f"/collections/{collection_id}")

    def add_prompts_to_collection(
        self,
        collection_id: int,
        prompt_ids: List[int],
    ) -> Collection:
        """
        Add prompts to a collection.

        Args:
            collection_id: The collection ID.
            prompt_ids: List of prompt IDs to add.

        Returns:
            The updated Collection object.
        """
        data = self._request(
            "POST",
            f"/collections/{collection_id}/prompts",
            data={"prompt_ids": prompt_ids},
        )
        return Collection.from_dict(data)

    def remove_prompt_from_collection(
        self,
        collection_id: int,
        prompt_id: int,
    ) -> None:
        """
        Remove a prompt from a collection.

        Args:
            collection_id: The collection ID.
            prompt_id: The prompt ID to remove.
        """
        self._request("DELETE", f"/collections/{collection_id}/prompts/{prompt_id}")

    # ==================== Usage ====================

    def report_usage(
        self,
        prompt: str,
        model: str,
        input_tokens: int,
        output_tokens: int,
        version: Optional[int] = None,
    ) -> Dict:
        """
        Report token usage from an external LLM call.

        Call this after making an LLM request using a prompt from Dr-Prompt
        to track token consumption.

        Args:
            prompt: The prompt UID (preferred) or ID that was used.
            model: The LLM model name (e.g., "gpt-4", "claude-3").
            input_tokens: Number of input/prompt tokens.
            output_tokens: Number of output/completion tokens.
            version: Optional specific version that was used.

        Returns:
            Usage report confirmation.
        """
        return self._request(
            "POST",
            "/usage/report",
            data={
                "prompt": prompt,
                "model": model,
                "input_tokens": input_tokens,
                "output_tokens": output_tokens,
                "version": version,
            },
        )

    def get_usage_stats(self) -> UsageStats:
        """
        Get usage statistics.

        Returns:
            UsageStats object with usage data.
        """
        data = self._request("GET", "/usage/stats")
        return UsageStats.from_dict(data)

    def get_usage_by_prompt(self) -> List[Dict]:
        """
        Get usage statistics grouped by prompt.

        Returns:
            List of usage stats per prompt.
        """
        return self._request("GET", "/usage/stats/by-prompt")

    def get_recent_usage(self, limit: int = 20) -> List[Dict]:
        """
        Get recent usage logs.

        Args:
            limit: Maximum number of records to return.

        Returns:
            List of recent usage logs.
        """
        return self._request("GET", "/usage/recent", params={"limit": limit})

    # ==================== Smart Features ====================

    def get_prompt_variables(
        self,
        prompt: str,
        version: Optional[int] = None,
    ) -> List[str]:
        """
        Get template variables from a prompt.

        Args:
            prompt: The prompt UID (preferred) or ID.
            version: Optional specific version.

        Returns:
            List of variable names.
        """
        params = {}
        if version:
            params["version"] = version
        data = self._request(
            "GET",
            f"/smart/prompts/{prompt}/variables",
            params=params,
        )
        return data.get("variables", [])

    def analyze_prompt(self, prompt: str, version: Optional[int] = None) -> Dict:
        """
        Analyze a prompt for quality.

        Args:
            prompt: The prompt UID (preferred) or ID.
            version: Optional specific version.

        Returns:
            Analysis results with scores and suggestions.
        """
        return self._request(
            "POST",
            "/smart/analyze",
            data={"prompt": prompt, "version": version},
        )

    def improve_prompt(
        self,
        prompt: str,
        focus: Optional[str] = None,
        version: Optional[int] = None,
    ) -> Dict:
        """
        Get AI-suggested improvements for a prompt.

        Args:
            prompt: The prompt UID (preferred) or ID.
            focus: Optional focus area (clarity, conciseness, specificity, etc.).
            version: Optional specific version.

        Returns:
            Improved prompt content and changes made.
        """
        return self._request(
            "POST",
            "/smart/improve",
            data={
                "prompt": prompt,
                "version": version,
                "focus": focus,
            },
        )

    def generate_variations(
        self,
        prompt: str,
        count: int = 3,
        style: Optional[str] = None,
        version: Optional[int] = None,
    ) -> Dict:
        """
        Generate alternative versions of a prompt.

        Args:
            prompt: The prompt UID (preferred) or ID.
            count: Number of variations to generate.
            style: Optional style direction (formal, casual, technical, etc.).
            version: Optional specific version.

        Returns:
            List of prompt variations.
        """
        return self._request(
            "POST",
            "/smart/variations",
            data={
                "prompt": prompt,
                "version": version,
                "count": count,
                "style": style,
            },
        )
