"""Images resource client for V1."""

from __future__ import annotations

import json
import os
from typing import TYPE_CHECKING, Any, List, Union

from concentriq.models.paginated import PaginatedResponse
from concentriq.models.v1.filters import ImageFilters

if TYPE_CHECKING:
    from concentriq.client import ConcentriqClient


class Images:
    """Client for Image resources (/api/images)."""

    def __init__(self, client: "ConcentriqClient"):
        """Initialize with client instance.

        Args:
            client: ConcentriqClient instance
        """
        self._client = client
        self._base_path = "/api/images"

    def list(
        self,
        page: int | None = None,
        rows_per_page: int | None = None,
        sort_by: str | None = None,
        descending: bool | None = None,
        filters: ImageFilters | None = None,
        include_metadata: bool | None = None,
    ) -> PaginatedResponse:
        """List images with pagination and filters.

        Args:
            page: Page number (1-indexed)
            rows_per_page: Items per page
            sort_by: Field to sort by (e.g., 'name', 'created')
            descending: Sort descending if True, ascending if False
            filters: Filter criteria (ImageFilters model)
            include_metadata: Include metadata fields in response

        Returns:
            PaginatedResponse of image data dictionaries

        Raises:
            AuthenticationError: If not authenticated
            NotFoundError: If resource not found

        Example:
            filters = ImageFilters(imageSetId=[100], hasAnnotations=True)
            images = client.v1.images.list(filters=filters)
        """
        params: dict[str, Any] = {}

        # Pagination is passed as JSON object in query string
        pagination: dict[str, Any] = {}
        if page is not None:
            pagination["page"] = page
        if rows_per_page is not None:
            pagination["rowsPerPage"] = rows_per_page
        if sort_by is not None:
            pagination["sortBy"] = sort_by
        if descending is not None:
            pagination["descending"] = descending

        if pagination:
            params["pagination"] = json.dumps(pagination)

        if filters is not None:
            params["filters"] = filters.model_dump_json(exclude_none=True)

        if include_metadata is not None:
            params["includeMetadata"] = "true" if include_metadata else "false"

        response = self._client._request("GET", self._base_path, params=params)
        data = response.json()

        # V1 API returns data in envelope format with 'images' key
        meta = data.get("meta", {})
        items = data.get("data", {}).get("images", [])
        return PaginatedResponse(items, meta=meta)

    def get(self, id: int) -> dict[str, Any]:
        """Get a single image by ID.

        Args:
            id: Image ID

        Returns:
            Image data dictionary

        Raises:
            AuthenticationError: If not authenticated
            NotFoundError: If image not found
        """
        response = self._client._request("GET", f"{self._base_path}/{id}")
        data = response.json()

        # V1 API returns data in envelope format
        return data.get("data", {})

    def create(self, data: dict[str, Any]) -> dict[str, Any]:
        """Create a new image record.

        This creates the image metadata record in the repository. To upload
        the actual file data, use the V3 Files API for multipart upload after
        creating the image record.

        Args:
            data: Image data. Required fields:
                - name: Filename (e.g., "slide.tif")
                - size: File size in bytes
                - imageSetId: Repository ID to create the image in

        Returns:
            Created image data dictionary (contains at minimum {"id": ...})

        Raises:
            AuthenticationError: If not authenticated
            ValidationError: If required fields are missing

        Example:
            image = client.v1.images.create({
                "name": "slide001.tif",
                "size": 1048576,
                "imageSetId": 100,
            })
            image_id = image["id"]
            # Then use V3 Files API to upload the actual file
        """
        response = self._client._request("POST", self._base_path, json=data)
        response_data = response.json()

        # V1 API returns data in envelope format
        return response_data.get("data", {})

    def update(self, id: int, data: dict[str, Any]) -> dict[str, Any]:
        """Update an existing image.

        Args:
            id: Image ID
            data: Updated image data (e.g., {"status": 1} to trigger re-ingestion)

        Returns:
            Updated image data dictionary

        Raises:
            AuthenticationError: If not authenticated
            NotFoundError: If image not found
            ValidationError: If data is invalid

        Example:
            # Trigger re-ingestion
            client.v1.images.update(456, {"status": 1})
        """
        response = self._client._request("PATCH", f"{self._base_path}/{id}", json=data)
        response_data = response.json()

        # V1 API returns data in envelope format
        return response_data.get("data", {})

    def delete(self, id: int) -> None:
        """Delete an image.

        Args:
            id: Image ID

        Returns:
            None

        Raises:
            AuthenticationError: If not authenticated
            NotFoundError: If image not found

        Example:
            client.v1.images.delete(456)
        """
        self._client._request("DELETE", f"{self._base_path}/{id}")
        return None

    def download(self, id: int) -> str:
        """Get signed download URL for image file.

        The backend returns a 302 redirect to a signed S3 URL that expires
        in 15 minutes. This method extracts and returns that URL.

        Args:
            id: Image ID

        Returns:
            Signed S3 URL for downloading the image file

        Raises:
            AuthenticationError: If not authenticated
            AuthorizationError: If user lacks data export permission
            NotFoundError: If image not found

        Example:
            url = client.v1.images.download(456)
            # Use url to download: wget url or requests.get(url)
        """
        # Don't follow redirects - we want to capture the Location header
        response = self._client._request(
            "GET", f"{self._base_path}/{id}/download", follow_redirects=False
        )

        # Backend returns 302 with Location header containing signed S3 URL
        if response.status_code == 302:
            location = response.headers.get("Location")
            if not location:
                raise ValueError("Expected Location header in redirect response")
            return location

        # If not a redirect, something went wrong - let error handling catch it
        # This will trigger _handle_error_response for 4xx/5xx codes
        self._client._handle_error_response(response)

        # Should never reach here
        raise ValueError(f"Unexpected response status: {response.status_code}")

    def export_annotations_xml(
        self,
        id: int,
        annotation_ids: List[int] | None = None,
    ) -> bytes:
        """Export annotations for an image as Aperio XML.

        Args:
            id: Image ID
            annotation_ids: Optional list of annotation IDs to export.
                If None, exports all annotations for the image.

        Returns:
            XML content as bytes

        Raises:
            AuthenticationError: If not authenticated
            AuthorizationError: If user lacks canExportData permission
            NotFoundError: If image not found

        Example:
            xml_bytes = client.v1.images.export_annotations_xml(456)
            with open("annotations.xml", "wb") as f:
                f.write(xml_bytes)
        """
        path = f"{self._base_path}/{id}/annotations/export/xml"

        if annotation_ids is not None:
            response = self._client._request(
                "POST", path, json={"annotationIds": annotation_ids}
            )
        else:
            response = self._client._request("GET", path)

        return response.content

    def import_annotations(
        self,
        id: int,
        file_path: Union[str, os.PathLike[str]],
        require_confirmation: bool = False,
    ) -> dict[str, Any]:
        """Import annotations from an XML file.

        Supports Aperio XML, HALO XML, and NDPA formats.

        Args:
            id: Image ID
            file_path: Path to the annotation file (.xml, .ndpa, or .mld)
            require_confirmation: If True, returns 202 with missing annotation
                classes instead of auto-importing

        Returns:
            Result dict with importedAnnotationsCount and other details.
            If require_confirmation=True and classes are missing, includes
            nonExistingAnnotationClassNames.

        Raises:
            AuthenticationError: If not authenticated
            AuthorizationError: If user lacks canCreateAnnotations permission
            NotFoundError: If image not found

        Example:
            result = client.v1.images.import_annotations(456, "annotations.xml")
            print(f"Imported {result['importedAnnotationsCount']} annotations")
        """
        path = f"{self._base_path}/{id}/annotations/import"
        params: dict[str, str] = {}
        if require_confirmation:
            params["requireConfirmation"] = "true"

        filename = os.path.basename(str(file_path))
        with open(file_path, "rb") as f:
            files = {"file": (filename, f)}
            response = self._client._request(
                "POST", path, files=files, params=params
            )

        data = response.json()
        return data.get("data", {}).get("annotationsResult", {})
