import warnings
from typing import Optional, List, Any, Union, Literal

from .client.http import HttpClient
from .providers.base import BaseProvider
from .enums import ACTION, FORMAT_TYPE
from .types import (
    SchemaDefinition,
    ExtractOptions,
    PreprocessOptions,
    CrawlOptions,
    FormatOptions,
)


FormatType = Literal["markdown", "text"]


class Makra:
    _api_key: Optional[str] = None
    _client: Optional[HttpClient] = None

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://makra-api.makralabs.org",
        api_version: str = "v0",
        provider: Optional[BaseProvider] = None,
    ):
        if not api_key:
            warnings.warn("API key is not provided to makra client", UserWarning)

        self._api_key = api_key
        self._base_url = base_url
        self._api_version = api_version
        self._provider = provider

        base_url_with_version = f"{self._base_url.rstrip('/')}/{self._api_version.rstrip('/')}"

        self._client = HttpClient(api_key=self._api_key, base_url=base_url_with_version)

    @property
    def api_key(self) -> Optional[str]:
        return self._api_key

    @property
    def base_url(self) -> Optional[str]:
        return self._base_url

    @property
    def api_version(self) -> Optional[str]:
        return self._api_version

    @property
    def client(self) -> HttpClient:
        return self._client

    @property
    def provider(self) -> Optional[BaseProvider]:
        return self._provider

    async def ping(self) -> Any:
        """Ping the Makra API to check connectivity and auth status."""
        return await self._client.get("/ping")

    async def crawl(
        self,
        urls: list[str],
        options: CrawlOptions = CrawlOptions.default(),
    ) -> Any:
        """
        Extract links from the given URLs.

        Args:
            urls: List of URLs to extract links from.
            options: Crawl configuration options.

        Returns:
            Extracted links from the given URLs.
        """
        payload = {
            "urls": urls,
            "options": options.to_dict(),
        }

        return await self._client.post("/crawl", payload)

    async def format(
        self,
        urls: list[str],
        type: Union[FormatType, FORMAT_TYPE] = FORMAT_TYPE.MARKDOWN,
        options: FormatOptions = FormatOptions.default(),
    ) -> Any:
        """
        Format page content as markdown or text.

        Args:
            urls: List of URLs to format.
            type: Output format type ("markdown", "text"), or FORMAT_TYPE enum.
            options: Format configuration options.

        Returns:
            Formatted content from the URLs.
        """
        # Convert enum to string if needed
        type_value = type.value if isinstance(type, FORMAT_TYPE) else type

        payload = {
            "urls": urls,
            "type": type_value,
            "options": options.to_dict(),
        }

        return await self._client.post("/format", payload)

    async def page_map(
        self,
        urls: list[str],
    ) -> Any:
        """
        Get a map of pages and their structure.

        Args:
            urls: List of URLs to map.

        Returns:
            Page map data for the specified URLs.
        """
        payload = {
            "urls": urls,
        }

        return await self._client.post("/page-map", payload)

    async def pre_process(
        self,
        urls: list[str],
        options: PreprocessOptions = PreprocessOptions.default(),
    ) -> Any:
        """
        Pre-process URLs by annotating pages.
        This will annotate the page and keep the annotations in the global db.

        Args:
            urls: List of URLs to pre-process.
            options: Pre-process configuration options.

        Returns:
            Pre-process results.
        """
        payload = {
            "urls": urls,
            "options": options.to_dict(),
        }

        return await self._client.post("/preprocess", payload)

    async def extract(
        self,
        urls: list[str],
        schema: Optional[SchemaDefinition] = None,
        query: Optional[str] = None,
        actions: List[Union[ACTION, str]] = [],
        options: ExtractOptions = ExtractOptions.default(),
    ) -> Any:
        """
        Extract structured data from URLs.

        Args:
            urls: List of URLs to extract data from.
            schema: JSON schema definition for the expected output.
            query: Natural language query describing what to extract.
            actions: List of actions to enable (navigation, pagination), as ACTION enum or string.
            options: Extract configuration options.

        Returns:
            Extracted data matching the schema or query.
        """
        if schema is None and query is None:
            raise ValueError("Either schema or query must be provided")

        # Convert actions to strings (handle both enum and string inputs)
        actions_list = [
            action.value if isinstance(action, ACTION) else action for action in actions
        ]

        payload = {
            "urls": urls,
            "output_schema": schema,
            "query": query,
            "actions": actions_list,
            "options": options.to_dict(),
        }

        return await self._client.post("/extract", payload)
