"""Google Photos Library API async client with OAuth2 token refresh."""

from __future__ import annotations

import logging
import time
from typing import Any

import aiohttp

from .constants import (
    API_BASE_URL,
    MAX_BATCH_GET,
    MAX_PAGE_SIZE,
    TOKEN_URL,
)
from .models import GooglePhotosAlbumData, GooglePhotosMediaItem

_LOGGER = logging.getLogger(__name__)


class GooglePhotosApiError(Exception):
    """Raised when the Google Photos API returns an error."""

    def __init__(self, status: int, message: str) -> None:
        self.status = status
        super().__init__(f"Google Photos API error {status}: {message}")


class GooglePhotosClient:
    """Async HTTP client for the Google Photos Library API."""

    def __init__(
        self,
        session: aiohttp.ClientSession,
        client_id: str,
        client_secret: str,
        refresh_token: str,
    ) -> None:
        self._session = session
        self._client_id = client_id
        self._client_secret = client_secret
        self._refresh_token = refresh_token
        self._access_token: str | None = None
        self._token_expires_at: float = 0.0

    @property
    def access_token(self) -> str | None:
        """Current access token (may be expired)."""
        return self._access_token

    async def _ensure_token(self) -> None:
        """Refresh the OAuth2 access token if expired or missing."""
        if self._access_token and time.monotonic() < self._token_expires_at:
            return

        _LOGGER.debug("Refreshing Google Photos access token")
        data = {
            "client_id": self._client_id,
            "client_secret": self._client_secret,
            "refresh_token": self._refresh_token,
            "grant_type": "refresh_token",
        }
        async with self._session.post(TOKEN_URL, data=data) as resp:
            if resp.status != 200:
                body = await resp.text()
                raise GooglePhotosApiError(resp.status, f"Token refresh failed: {body}")
            result = await resp.json()

        self._access_token = result["access_token"]
        # Refresh ~60s before actual expiry to avoid races
        expires_in = result.get("expires_in", 3600)
        self._token_expires_at = time.monotonic() + expires_in - 60

    async def _request(
        self,
        method: str,
        path: str,
        *,
        params: dict[str, Any] | None = None,
        json_body: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Make an authenticated API request."""
        await self._ensure_token()
        url = f"{API_BASE_URL}/{path}"
        headers = {"Authorization": f"Bearer {self._access_token}"}

        async with self._session.request(
            method, url, headers=headers, params=params, json=json_body,
        ) as resp:
            if resp.status == 200:
                return await resp.json()
            body = await resp.text()
            raise GooglePhotosApiError(resp.status, body)

    # ------------------------------------------------------------------
    # Albums
    # ------------------------------------------------------------------

    async def ping(self) -> bool:
        """Test connectivity by listing a single album."""
        try:
            await self._request("GET", "albums", params={"pageSize": "1"})
            return True
        except Exception:
            _LOGGER.exception("Google Photos ping failed")
            return False

    async def list_albums(self, *, include_shared: bool = True) -> list[dict[str, Any]]:
        """List all albums (owned + optionally shared)."""
        albums: list[dict[str, Any]] = []

        # Owned albums
        page_token: str | None = None
        while True:
            params: dict[str, str] = {"pageSize": str(MAX_PAGE_SIZE)}
            if page_token:
                params["pageToken"] = page_token
            result = await self._request("GET", "albums", params=params)
            albums.extend(result.get("albums", []))
            page_token = result.get("nextPageToken")
            if not page_token:
                break

        # Shared albums
        if include_shared:
            page_token = None
            while True:
                params = {"pageSize": str(MAX_PAGE_SIZE)}
                if page_token:
                    params["pageToken"] = page_token
                result = await self._request("GET", "sharedAlbums", params=params)
                shared = result.get("sharedAlbums", [])
                # Deduplicate: shared albums the user owns are already in the owned list
                existing_ids = {a["id"] for a in albums}
                albums.extend(a for a in shared if a.get("id") not in existing_ids)
                page_token = result.get("nextPageToken")
                if not page_token:
                    break

        return albums

    async def get_album(self, album_id: str) -> dict[str, Any] | None:
        """Get a single album by ID."""
        try:
            return await self._request("GET", f"albums/{album_id}")
        except GooglePhotosApiError as e:
            if e.status == 404:
                return None
            raise

    async def get_album_with_items(self, album_id: str) -> GooglePhotosAlbumData | None:
        """Fetch album metadata + all media items."""
        album_data = await self.get_album(album_id)
        if album_data is None:
            return None

        items = await self.list_media_items(album_id)
        return GooglePhotosAlbumData.from_api_response(album_data, items)

    # ------------------------------------------------------------------
    # Media items
    # ------------------------------------------------------------------

    async def list_media_items(self, album_id: str) -> list[GooglePhotosMediaItem]:
        """List all media items in an album (handles pagination)."""
        items: list[GooglePhotosMediaItem] = []
        page_token: str | None = None

        while True:
            body: dict[str, Any] = {
                "albumId": album_id,
                "pageSize": MAX_PAGE_SIZE,
            }
            if page_token:
                body["pageToken"] = page_token

            result = await self._request("POST", "mediaItems:search", json_body=body)
            for raw in result.get("mediaItems", []):
                items.append(GooglePhotosMediaItem.from_api_response(raw))

            page_token = result.get("nextPageToken")
            if not page_token:
                break

        return items

    async def get_media_item(self, media_item_id: str) -> GooglePhotosMediaItem | None:
        """Get a single media item (with fresh baseUrl)."""
        try:
            data = await self._request("GET", f"mediaItems/{media_item_id}")
            return GooglePhotosMediaItem.from_api_response(data)
        except GooglePhotosApiError as e:
            if e.status == 404:
                return None
            raise

    async def get_media_items_batch(
        self, media_item_ids: list[str],
    ) -> dict[str, GooglePhotosMediaItem]:
        """Batch-get media items (up to 50 per call). Returns dict keyed by ID."""
        result: dict[str, GooglePhotosMediaItem] = {}

        for i in range(0, len(media_item_ids), MAX_BATCH_GET):
            chunk = media_item_ids[i : i + MAX_BATCH_GET]

            # batchGet requires repeated query params (mediaItemIds=a&mediaItemIds=b)
            # which _request() doesn't support, so we make the request manually.
            await self._ensure_token()
            url = f"{API_BASE_URL}/mediaItems:batchGet"
            headers = {"Authorization": f"Bearer {self._access_token}"}
            query_params = "&".join(f"mediaItemIds={mid}" for mid in chunk)
            async with self._session.get(f"{url}?{query_params}", headers=headers) as resp:
                if resp.status != 200:
                    body = await resp.text()
                    _LOGGER.error("batchGet failed: %s %s", resp.status, body)
                    continue
                data = await resp.json()

            for item_result in data.get("mediaItemResults", []):
                media_item = item_result.get("mediaItem")
                if media_item:
                    parsed = GooglePhotosMediaItem.from_api_response(media_item)
                    result[parsed.id] = parsed

        return result

    async def search_media_items(
        self,
        *,
        album_id: str | None = None,
        category: str | None = None,
        media_type: str | None = None,
        date_start: str | None = None,
        date_end: str | None = None,
        page_size: int = 25,
    ) -> list[GooglePhotosMediaItem]:
        """Search media items with filters."""
        body: dict[str, Any] = {"pageSize": min(page_size, MAX_PAGE_SIZE)}

        if album_id:
            body["albumId"] = album_id
        else:
            filters: dict[str, Any] = {}
            if category:
                filters["contentFilter"] = {
                    "includedContentCategories": [category.upper()],
                }
            if media_type:
                filters["mediaTypeFilter"] = {"mediaTypes": [media_type.upper()]}
            if date_start or date_end:
                date_filter: dict[str, Any] = {}
                if date_start:
                    parts = date_start.split("-")
                    date_filter["startDate"] = {
                        "year": int(parts[0]),
                        "month": int(parts[1]),
                        "day": int(parts[2]),
                    }
                if date_end:
                    parts = date_end.split("-")
                    date_filter["endDate"] = {
                        "year": int(parts[0]),
                        "month": int(parts[1]),
                        "day": int(parts[2]),
                    }
                filters["dateFilter"] = {"ranges": [date_filter]}
            if filters:
                body["filters"] = filters

        result = await self._request("POST", "mediaItems:search", json_body=body)
        return [
            GooglePhotosMediaItem.from_api_response(raw)
            for raw in result.get("mediaItems", [])
        ]
