from __future__ import annotations

from typing import TYPE_CHECKING, Self

import httpx

from fortytwo.core.auth import (
    AuthProvider,
    ClientCredentialsProvider,
    SyncAuthenticator,
)
from fortytwo.core.config import Config
from fortytwo.core.request.handler import SyncRequestHandler
from fortytwo.resources.campus.manager.sync import SyncCampusManager
from fortytwo.resources.campus_user.manager.sync import SyncCampusUserManager
from fortytwo.resources.cursus.manager.sync import SyncCursusManager
from fortytwo.resources.cursus_user.manager.sync import SyncCursusUserManager
from fortytwo.resources.location.manager.sync import SyncLocationManager
from fortytwo.resources.project.manager.sync import SyncProjectManager
from fortytwo.resources.project_user.manager.sync import SyncProjectUserManager
from fortytwo.resources.scale_team.manager.sync import SyncScaleTeamManager
from fortytwo.resources.slot.manager.sync import SyncSlotManager
from fortytwo.resources.team.manager.sync import SyncTeamManager
from fortytwo.resources.token.manager.sync import SyncTokenManager
from fortytwo.resources.user.manager.sync import SyncUserManager


if TYPE_CHECKING:
    from fortytwo.core.request.response import ApiListResponse, ApiResponse
    from fortytwo.parameter import Parameter
    from fortytwo.resources.resource import Resource, ResourceTemplate


class SyncClient:
    """
    Synchronous client for the 42 School API.

    Usage::

        client = Client(client_id, client_secret)
        users = client.users.get_all()
        client.close()
    """

    def __init__(
        self,
        client_id: str | None = None,
        client_secret: str | None = None,
        client_scopes: list[str] | None = None,
        *,
        provider: AuthProvider | None = None,
        config: Config | None = None,
    ) -> None:
        """
        Initialize the Client with authentication and configuration.

        Either supply ``client_id`` / ``client_secret`` (uses the default
        client-credentials grant), or pass a custom ``provider`` for other
        OAuth2 flows.

        Args:
            client_id: The client ID for authentication.
            client_secret: The client secret for authentication.
            client_scopes: Optional list of OAuth scopes. Defaults to ``["public"]``.
            provider: Custom :class:`AuthProvider` instance.
            config: Optional configuration object.
        """
        self.__config = config or Config()
        self.__http_client = httpx.Client()

        if provider is None:
            provider = ClientCredentialsProvider(
                client_id=client_id,
                client_secret=client_secret,
                scopes=client_scopes,
            )

        self.__authenticator = SyncAuthenticator(
            self.__config,
            provider,
            self.__http_client,
        )

        self.__request_handler = SyncRequestHandler(
            self.__config,
            self.__authenticator,
            self.__http_client,
        )

        # fmt: off
        self.campuses       = SyncCampusManager(self)
        self.campus_users   = SyncCampusUserManager(self)
        self.cursuses       = SyncCursusManager(self)
        self.cursus_users   = SyncCursusUserManager(self)
        self.users          = SyncUserManager(self)
        self.locations      = SyncLocationManager(self)
        self.projects       = SyncProjectManager(self)
        self.project_users  = SyncProjectUserManager(self)
        self.scale_teams    = SyncScaleTeamManager(self)
        self.slots          = SyncSlotManager(self)
        self.teams          = SyncTeamManager(self)
        self.tokens         = SyncTokenManager(self)
        # fmt: on

    @property
    def config(self) -> Config:
        """
        The client's configuration settings.
        """
        return self.__config

    @config.setter
    def config(self, config: Config) -> None:
        """
        Update the client's configuration settings.

        Args:
            config: The new configuration to apply.
        """
        self.__config = config
        self.__authenticator.config = config
        self.__request_handler.config = config

    @property
    def authenticator(self) -> SyncAuthenticator:
        """
        The token lifecycle manager.
        """
        return self.__authenticator

    def request(
        self,
        resource: Resource[ResourceTemplate],
        *params: Parameter,
    ) -> ApiResponse[ResourceTemplate] | ApiListResponse[ResourceTemplate]:
        """
        Send a synchronous request to the API and return the response.

        Args:
            resource: The resource to fetch.
            *params: The parameters for the request.

        Returns:
            An ApiResponse wrapping the parsed data and response metadata.

        Raises:
            FortyTwoNotFoundException: If the resource is not found (404).
            FortyTwoAuthException: If authentication fails.
            FortyTwoRequestException: If the request fails for other reasons.
        """
        return self.__request_handler.request(resource, *params)

    def close(self) -> None:
        """
        Close the underlying HTTP client and release resources.
        """
        self.__http_client.close()

    def __enter__(self) -> Self:
        return self

    def __exit__(self, *args: object) -> None:
        self.close()
