"""Beatlyze API client."""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

import httpx


class BeatlyzeError(Exception):
    """Raised when the Beatlyze API returns an error."""

    def __init__(self, status_code: int, detail: str):
        self.status_code = status_code
        self.detail = detail
        super().__init__(f"HTTP {status_code}: {detail}")


@dataclass
class Beatlyze:
    """Beatlyze API client.

    Usage::

        from beatlyze import Beatlyze

        bz = Beatlyze("bz_your_api_key")
        result = bz.analyze_url("https://example.com/track.mp3")
        print(result["bpm"], result["key"])
    """

    api_key: str
    base_url: str = "https://api.beatlyze.dev"
    timeout: float = 10.0
    poll_interval: float = 2.0
    max_wait: float = 300.0
    _client: httpx.Client = field(init=False, repr=False)

    def __post_init__(self):
        self._client = httpx.Client(
            base_url=self.base_url,
            headers={"X-API-Key": self.api_key},
            timeout=self.timeout,
        )

    def close(self):
        """Close the underlying HTTP client."""
        self._client.close()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()

    def _request(self, method: str, path: str, **kwargs) -> dict[str, Any]:
        resp = self._client.request(method, path, **kwargs)
        if resp.status_code >= 400:
            detail = (
                resp.json().get("detail", resp.text)
                if resp.headers.get("content-type", "").startswith("application/json")
                else resp.text
            )
            raise BeatlyzeError(resp.status_code, detail)
        return resp.json()

    def _wait_for_result(self, job_id: str) -> dict[str, Any]:
        """Poll until job completes or fails."""
        start = time.monotonic()
        while True:
            result = self._request("GET", f"/v1/analysis/{job_id}")
            if result["status"] in ("completed", "failed"):
                if result["status"] == "failed":
                    raise BeatlyzeError(500, result.get("error", "Analysis failed"))
                return result.get("result", {})

            if time.monotonic() - start > self.max_wait:
                raise BeatlyzeError(
                    408, f"Timed out waiting for job {job_id} after {self.max_wait}s"
                )

            time.sleep(self.poll_interval)

    def analyze_url(
        self,
        url: str,
        *,
        webhook_url: str | None = None,
        wait: bool = True,
        idempotency_key: str | None = None,
    ) -> dict[str, Any]:
        """Analyze audio from a URL.

        Args:
            url: Public URL to an audio file (MP3, WAV, FLAC, OGG, etc.).
            webhook_url: Optional URL to receive results via POST when analysis completes.
            wait: If True (default), poll until the analysis completes and return the result.
                  If False, return immediately with ``{"job_id": ..., "status": "processing"}``.
            idempotency_key: Optional key to prevent duplicate submissions.

        Returns:
            Analysis result dict with keys like ``bpm``, ``key``, ``energy``, etc.
            If ``wait=False``, returns ``{"job_id": ..., "status": ...}`` instead.
        """
        body: dict[str, Any] = {"url": url}
        if webhook_url:
            body["webhook_url"] = webhook_url

        headers = {}
        if idempotency_key:
            headers["Idempotency-Key"] = idempotency_key

        resp = self._request("POST", "/v1/analyze/url", json=body, headers=headers)

        if not wait:
            return resp

        return self._wait_for_result(resp["job_id"])

    def analyze_file(
        self,
        path: str | Path,
        *,
        webhook_url: str | None = None,
        wait: bool = True,
        idempotency_key: str | None = None,
    ) -> dict[str, Any]:
        """Upload and analyze a local audio file.

        Args:
            path: Path to a local audio file.
            webhook_url: Optional webhook URL for results.
            wait: If True (default), poll until complete. If False, return immediately.
            idempotency_key: Optional key to prevent duplicate submissions.

        Returns:
            Analysis result dict, or job status dict if ``wait=False``.
        """
        path = Path(path)
        if not path.exists():
            raise FileNotFoundError(f"Audio file not found: {path}")

        data = {}
        if webhook_url:
            data["webhook_url"] = webhook_url

        headers = {}
        if idempotency_key:
            headers["Idempotency-Key"] = idempotency_key

        with open(path, "rb") as f:
            resp = self._request(
                "POST",
                "/v1/analyze/upload",
                files={"file": (path.name, f)},
                data=data,
                headers=headers,
            )

        if not wait:
            return resp

        return self._wait_for_result(resp["job_id"])

    def get_analysis(self, job_id: str, *, full: bool = False) -> dict[str, Any]:
        """Get analysis result for a job.

        Args:
            job_id: The job UUID returned from analyze_url/analyze_file.
            full: If True, include section-level detail.

        Returns:
            Analysis result response dict.
        """
        suffix = "/full" if full else ""
        return self._request("GET", f"/v1/analysis/{job_id}{suffix}")

    def get_usage(self) -> dict[str, Any]:
        """Get current month's usage stats.

        Returns:
            Dict with ``month``, ``count``, ``limit``, ``tier``.
        """
        return self._request("GET", "/v1/usage")

    def analyze_batch(
        self,
        urls: list[str],
        *,
        webhook_url: str | None = None,
    ) -> list[dict[str, Any]]:
        """Submit a batch of URLs for analysis (max 10).

        Args:
            urls: List of audio URLs to analyze.
            webhook_url: Optional webhook URL applied to all items.

        Returns:
            List of ``{"job_id": ..., "status": ...}`` dicts.
        """
        items = [
            {"url": u, "webhook_url": webhook_url} if webhook_url else {"url": u}
            for u in urls
        ]
        resp = self._request("POST", "/v1/batch", json={"items": items})
        return resp.get("jobs", [])
