import typing as tp

import mwclient
import mwclient.errors
from mwrogue.auth_credentials import AuthCredentials
from mwrogue.esports_client import EsportsClient
from pydantic import BaseModel

from .cargo_compiler import CargoQueryCompiler
from .errors import APIUnknownError, RateLimitError
from .logger import logger
from .ty import SelectStatement

T = tp.TypeVar("T", bound=BaseModel)

CargoUnitResponse = tp.OrderedDict[str, tp.Any]
CargoResponse = tp.List[CargoUnitResponse]


class LeaguepediaClient:
    """Client for interacting with Leaguepedia's Cargo database.

    This client provides a high-level interface to query Leaguepedia's esports data
    using SQL-like queries that are automatically compiled to Cargo queries.

    Attributes:
        client (EsportsClient): The underlying mwrogue esports client
        query_compiler (CargoQueryCompiler): Compiler for SQL to Cargo queries

    Examples:
        >>> from mwrogue.auth_credentials import AuthCredentials
        >>> from lolpedia_api.client import LeaguepediaClient
        >>>
        >>> # Create credentials (no login required for public data)
        >>> credentials = AuthCredentials(user_file="user-config.txt")
        >>>
        >>> # Or use anonymous credentials
        >>> credentials = AuthCredentials()
        >>>
        >>> # Initialize the client
        >>> client = LeaguepediaClient(credentials)
        >>>
        >>> # Execute a query
        >>> statement = select(P).where(P.Age < 18).limit(2)
        >>> results = client.execute_query(statement)

    Note:
        For most use cases, anonymous credentials (AuthCredentials()) are sufficient.
        User credentials are only needed for write operations or accessing private data.
    """

    def __init__(self, credentials: AuthCredentials) -> None:
        """Initialize the Leaguepedia client.

        Args:
            credentials (AuthCredentials): Authentication credentials for the API.
                Can be created with AuthCredentials() for anonymous access or
                AuthCredentials(user_file="path/to/config") for authenticated access.
        """
        self.client = EsportsClient("lol", credentials=credentials)
        self.query_compiler = CargoQueryCompiler()

    @tp.overload
    def execute_query(
        self, statement: SelectStatement, model: None = ...
    ) -> CargoResponse: ...

    @tp.overload
    def execute_query(
        self, statement: SelectStatement, model: tp.Type[T]
    ) -> tp.List[T]: ...

    def execute_query(
        self, statement: SelectStatement, model: tp.Type[T] | None = None
    ) -> tp.List[T] | CargoResponse:
        """Compile a SQL query to a cargo query and send it.

        Args:
            statement (SelectStatement): SQL query to compile
            model (tp.Type[T] | None, optional): Pydantic model to validate the response. Defaults to None.

        Raises:
            RateLimitError: In case of too many API calls
            APIUnknownError: The statement was wrong

        Returns:
            tp.List[T] | CargoResponse: Leaguepedia response

        Examples:
            >>> SP = tables.ScoreboardPlayer
            >>> SG = tables.ScoreboardGame
            >>> statement = (
                    select(
                        SP.Role,
                        SP.GameId,
                        SG.Tournament,
                        SG.Team1,
                        SG.Team2,
                        func.max(SP.Gold).label("max_gold"),
                        func.min(SP.Gold).label("min_gold"),
                    )
                    .select_from(SP)
                    .join(SG, SG.GameId == SP.GameId)
                    .where(
                        and_(
                            SP.Role == role,
                            or_(
                                SG.Tournament.like(r"LEC%"),
                                SG.Tournament.like(r"EU LCS%"),
                            ),
                            SP.DateTime_UTC >= datetime(2017, 1, 1),
                        )
                    )
                    .group_by(SP.GameId, SP.Role)
                    .order_by(desc(text("(max_gold - min_gold)")))
                    .limit(limit)
                )

            >>> client.execute_query(statement, model=Result)

            See `examples` folder for more.
        """
        query = self.query_compiler.compile(statement)

        try:
            results = self.client.cargo_client.query(**query)
        except mwclient.errors.APIError as e:
            code = e.code
            logger.error(f"Error during API call: {code}: {e.info}")
            if code == "ratelimited":
                raise RateLimitError(code) from e
            else:
                logger.info(f"Query on error was : \n{query}")
                raise APIUnknownError(e) from e

        if results is None:
            return []
        elif model:
            return [model.model_validate(result) for result in results]
        else:
            return results
