"""Main client for the Daly Energy API SDK.

Usage::

    from dalysdk import DalyClient

    client = DalyClient(
        workspace_api_key="wk_...",
        user_api_key="uk_...",
    )

    locations = client.locations.list()
    location = client.locations.create({"name": "Site A", "latitude": 33.4, "longitude": -112.0})

    client.close()

Or as a context manager::

    with DalyClient(workspace_api_key="wk_...", user_api_key="uk_...") as client:
        projects = client.projects.list()
"""

from __future__ import annotations

from typing import Any

import httpx

from dalysdk.auth import AuthManager
from dalysdk.exceptions import (
    AuthenticationError,
    ConflictError,
    DalyAPIError,
    ForbiddenError,
    NotFoundError,
    RateLimitError,
    ServerError,
    TimeoutError,
    ValidationError,
)
from dalysdk.resources.energy_models import EnergyModelResource
from dalysdk.resources.inverters import InverterResource
from dalysdk.resources.locations import LocationResource
from dalysdk.resources.modules import ModuleResource
from dalysdk.resources.projects import ProjectResource
from dalysdk.resources.shading_scenes import ShadingSceneResource
from dalysdk.resources.tasks import TaskResource
from dalysdk.resources.weather_data import WeatherDataResource
from dalysdk.resources.workflows import WorkflowResource
from dalysdk.resources.workspaces import WorkspaceResource

DEFAULT_BASE_URL = "https://api.dalyenergy.com"
DEFAULT_TIMEOUT = 30.0


class DalyClient:
    """Synchronous client for the Daly Energy REST API.

    Args:
        workspace_api_key: The workspace API key (e.g. ``wk_...``).
        user_api_key: The user API key (e.g. ``uk_...``).
        timeout: Default request timeout in seconds. Defaults to 30.
    """

    def __init__(
        self,
        workspace_api_key: str,
        user_api_key: str,
        timeout: float = DEFAULT_TIMEOUT,
    ):
        self._base_url = DEFAULT_BASE_URL
        self._auth = AuthManager(workspace_api_key, user_api_key, self._base_url)
        self._http = httpx.Client(base_url=self._base_url, timeout=timeout)

        # Resource namespaces
        self.locations = LocationResource(self)
        self.weather_data = WeatherDataResource(self)
        self.modules = ModuleResource(self)
        self.inverters = InverterResource(self)
        self.projects = ProjectResource(self)
        self.energy_models = EnergyModelResource(self)
        self.shading_scenes = ShadingSceneResource(self)
        self.tasks = TaskResource(self)
        self.workflows = WorkflowResource(self)
        self.workspaces = WorkspaceResource(self)

    # -- Context manager protocol --

    def __enter__(self) -> DalyClient:
        return self

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

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

    # -- Legacy compatibility helper --

    def run_energy_model(
        self,
        model_json: dict,
        *,
        run_async: bool = False,
        block_results: bool = False,
        block_results_index: int = -1,
        timeout: float | None = None,
    ) -> tuple[bool, dict]:
        """Legacy-compatible wrapper matching the old SDK tuple return style.

        ``run_async`` is retained for compatibility but does not change
        submission behavior.
        """
        del run_async
        try:
            result = self.energy_models.create_legacy_query(
                model_json,
                block_results=block_results,
                block_results_index=block_results_index,
                timeout=timeout,
            )
            return True, result
        except DalyAPIError as exc:
            return False, exc.response_body

    # -- Internal request method --

    def _request(
        self,
        method: str,
        path: str,
        *,
        json: dict | list | None = None,
        params: dict | None = None,
        data: dict | None = None,
        files: dict | list[tuple[str, tuple[str, Any, str]]] | None = None,
        timeout: float | None = None,
    ) -> Any:
        """Send an authenticated request to the API.

        Automatically attaches the Authorization header with a valid JWT.
        On 401, invalidates the token and retries once.

        Returns:
            Parsed JSON response body (dict or list).

        Raises:
            AuthenticationError: On 401 after retry.
            ForbiddenError: On 403.
            NotFoundError: On 404.
            ConflictError: On 409.
            ValidationError: On 400 or 422.
            RateLimitError: On 429 (includes ``retry_after`` when available).
            ServerError: On 5xx.
            TimeoutError: When the request times out.
            DalyAPIError: On any other non-2xx status.
        """
        kwargs: dict[str, Any] = {}
        if json is not None:
            kwargs["json"] = json
        if params is not None:
            kwargs["params"] = params
        if data is not None:
            kwargs["data"] = data
        if files is not None:
            kwargs["files"] = files
        if timeout is not None:
            kwargs["timeout"] = timeout

        try:
            # First attempt
            response = self._send(method, path, **kwargs)

            # On 401, refresh token and retry once
            if response.status_code == 401:
                self._auth.invalidate()
                response = self._send(method, path, **kwargs)

            return self._handle_response(response)
        except httpx.TimeoutException as exc:
            raise TimeoutError(detail=f"Request timed out: {method} {path}") from exc

    def _send(self, method: str, path: str, **kwargs: Any) -> httpx.Response:
        """Send a single HTTP request with the current auth token."""
        headers = kwargs.pop("headers", {})
        headers["Authorization"] = f"Bearer {self._auth.token}"
        return self._http.request(method, path, headers=headers, **kwargs)

    @staticmethod
    def _handle_response(response: httpx.Response) -> Any:
        """Parse the response and raise typed exceptions on errors."""
        # Success
        if 200 <= response.status_code < 300:
            # Some DELETE endpoints return 204 with no body
            if response.status_code == 204 or not response.content:
                return {}
            return response.json()

        # Error handling
        body = _safe_json(response)
        detail = body.get("detail", response.text)
        if isinstance(detail, list):
            # Pydantic validation errors come back as a list
            detail = str(detail)

        status = response.status_code

        if status == 401:
            raise AuthenticationError(detail=detail, response_body=body)
        if status == 403:
            raise ForbiddenError(detail=detail, response_body=body)
        if status == 404:
            raise NotFoundError(detail=detail, response_body=body)
        if status == 409:
            raise ConflictError(detail=detail, response_body=body)
        if status in (400, 422):
            raise ValidationError(status_code=status, detail=detail, response_body=body)
        if status == 429:
            retry_after_raw = response.headers.get("Retry-After")
            retry_after = float(retry_after_raw) if retry_after_raw else None
            raise RateLimitError(detail=detail, response_body=body, retry_after=retry_after)
        if 500 <= status < 600:
            raise ServerError(status_code=status, detail=detail, response_body=body)

        raise DalyAPIError(status_code=status, detail=detail, response_body=body)


def _safe_json(response: httpx.Response) -> dict:
    """Attempt to parse JSON from a response, returning empty dict on failure."""
    try:
        return response.json()
    except Exception:
        return {}
