"""Tests for Images client."""

import json
import os
import tempfile

import pytest
from pytest_httpx import HTTPXMock

from concentriq import ConcentriqClient
from concentriq.auth import ApiKeyAuth


class TestImagesList:
    """Tests for Images.list() behavior."""

    def test_list_returns_list_of_images(self, httpx_mock: HTTPXMock):
        """List should return a list of image data from nested response."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            json={
                "meta": {"status": 200, "duration": 123},
                "data": {
                    "images": [
                        {"id": 1, "name": "Image 1.tif"},
                        {"id": 2, "name": "Image 2.tif"},
                    ]
                },
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.list()

        assert isinstance(result, list)
        assert len(result) == 2
        assert result[0]["id"] == 1
        assert result[1]["name"] == "Image 2.tif"

    def test_list_sends_pagination_as_json_string(self, httpx_mock: HTTPXMock):
        """List should send pagination as JSON string in query param."""
        httpx_mock.add_response(
            json={"meta": {"status": 200}, "data": {"images": []}},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        client.v1.images.list(page=2, rows_per_page=50, sort_by="name", descending=True)

        request = httpx_mock.get_request()
        pagination_param = request.url.params.get("pagination")
        assert pagination_param is not None
        parsed = json.loads(pagination_param)
        assert parsed["page"] == 2
        assert parsed["rowsPerPage"] == 50
        assert parsed["sortBy"] == "name"
        assert parsed["descending"] is True

    def test_list_returns_empty_list_when_no_results(self, httpx_mock: HTTPXMock):
        """List should return empty list when API returns no data."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            json={"meta": {"status": 200}, "data": {"images": []}},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.list()

        assert result == []

    def test_list_returns_paginated_response_with_meta(self, httpx_mock: HTTPXMock):
        """List should return PaginatedResponse with accessible meta."""
        httpx_mock.add_response(
            json={
                "meta": {
                    "status": 200,
                    "pagination": {"page": 1, "rowsPerPage": 20, "total": 50},
                },
                "data": {
                    "images": [{"id": 1, "name": "Image 1.tif"}]
                },
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.list()

        assert len(result) == 1
        assert result.meta["pagination"]["total"] == 50

    def test_list_raises_authentication_error_when_unauthorized(
        self, httpx_mock: HTTPXMock
    ):
        """List should raise AuthenticationError when API returns 401."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            status_code=401,
            json={"error": "Unauthorized"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import AuthenticationError

        with pytest.raises(AuthenticationError):
            client.v1.images.list()


class TestImagesGet:
    """Tests for Images.get() behavior."""

    def test_get_returns_single_image(self, httpx_mock: HTTPXMock):
        """Get should return a single image by ID."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456",
            json={
                "meta": {"status": 200},
                "data": {"id": 456, "name": "slide001.tif", "width": 1024, "height": 768},
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.get(456)

        assert result["id"] == 456
        assert result["name"] == "slide001.tif"
        assert result["width"] == 1024

    def test_get_raises_not_found_for_invalid_id(self, httpx_mock: HTTPXMock):
        """Get should raise NotFoundError for non-existent image."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/99999",
            status_code=404,
            json={"error": "Image not found"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import NotFoundError

        with pytest.raises(NotFoundError):
            client.v1.images.get(99999)


class TestImagesCreate:
    """Tests for Images.create() behavior."""

    def test_create_returns_created_image(self, httpx_mock: HTTPXMock):
        """Create should POST and return the created image data."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            method="POST",
            json={
                "meta": {"status": 201},
                "data": {"id": 789},
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.create(
            {"name": "slide.tif", "size": 1048576, "imageSetId": 100}
        )

        assert result["id"] == 789

    def test_create_raises_validation_error_on_missing_required_fields(
        self, httpx_mock: HTTPXMock
    ):
        """Create should raise ValidationError when required fields are missing."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            method="POST",
            status_code=400,
            json={"error": "name must be a string."},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import ValidationError

        with pytest.raises(ValidationError):
            client.v1.images.create({})


class TestImagesUpdate:
    """Tests for Images.update() behavior."""

    def test_update_returns_updated_image(self, httpx_mock: HTTPXMock):
        """Update should PATCH the image and return updated data."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456",
            method="PATCH",
            json={
                "meta": {"status": 200},
                "data": {"id": 456, "name": "renamed.tif", "status": 1},
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.update(456, {"status": 1})

        assert result["id"] == 456
        assert result["status"] == 1

    def test_update_raises_not_found_for_invalid_id(self, httpx_mock: HTTPXMock):
        """Update should raise NotFoundError for non-existent image."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/99999",
            method="PATCH",
            status_code=404,
            json={"error": "Image not found"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import NotFoundError

        with pytest.raises(NotFoundError):
            client.v1.images.update(99999, {"status": 1})


class TestImagesDelete:
    """Tests for Images.delete() behavior."""

    def test_delete_returns_successfully(self, httpx_mock: HTTPXMock):
        """Delete should complete successfully for valid ID."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456",
            method="DELETE",
            status_code=204,
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.delete(456)

        assert result is None

    def test_delete_raises_not_found_for_invalid_id(self, httpx_mock: HTTPXMock):
        """Delete should raise NotFoundError for non-existent image."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/99999",
            method="DELETE",
            status_code=404,
            json={"error": "Image not found"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import NotFoundError

        with pytest.raises(NotFoundError):
            client.v1.images.delete(99999)


class TestImagesDownload:
    """Tests for Images.download() behavior."""

    def test_download_returns_signed_url(self, httpx_mock: HTTPXMock):
        """Download should return signed S3 URL from redirect."""
        # Backend returns 302 redirect to signed S3 URL
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456/download",
            status_code=302,
            headers={
                "Location": "https://s3.amazonaws.com/bucket/image.tif?signature=abc123"
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        url = client.v1.images.download(456)

        assert isinstance(url, str)
        assert url.startswith("https://s3.amazonaws.com/")
        assert "signature=" in url

    def test_download_raises_not_found_for_invalid_id(self, httpx_mock: HTTPXMock):
        """Download should raise NotFoundError for non-existent image."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/99999/download",
            status_code=404,
            json={"error": "Image not found"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import NotFoundError

        with pytest.raises(NotFoundError):
            client.v1.images.download(99999)

    def test_download_raises_authorization_error_when_no_export_permission(
        self, httpx_mock: HTTPXMock
    ):
        """Download should raise AuthorizationError when user lacks export permission."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456/download",
            status_code=403,
            json={"error": "Insufficient permissions to export data"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import AuthorizationError

        with pytest.raises(AuthorizationError):
            client.v1.images.download(456)


SAMPLE_XML = b"""<?xml version="1.0"?>
<Annotations MicronsPerPixel="0.2525">
  <Annotation Id="1" Name="Tumor" LineColor="255">
    <Regions>
      <Region Id="100" Type="1" Text="ROI">
        <Vertices>
          <Vertex X="100" Y="100" Z="0" />
          <Vertex X="200" Y="100" Z="0" />
          <Vertex X="200" Y="200" Z="0" />
          <Vertex X="100" Y="200" Z="0" />
        </Vertices>
      </Region>
    </Regions>
  </Annotation>
</Annotations>"""


class TestImagesExportAnnotationsXml:
    """Tests for Images.export_annotations_xml() behavior."""

    def test_export_returns_xml_bytes(self, httpx_mock: HTTPXMock):
        """Export should return annotation XML as bytes."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456/annotations/export/xml",
            content=SAMPLE_XML,
            headers={"Content-Type": "application/xml"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.export_annotations_xml(456)

        assert isinstance(result, bytes)
        assert b"<Annotations" in result
        assert b"Tumor" in result

    def test_export_subset_with_annotation_ids(self, httpx_mock: HTTPXMock):
        """Export should POST with annotation IDs to export a subset."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456/annotations/export/xml",
            method="POST",
            content=SAMPLE_XML,
            headers={"Content-Type": "application/xml"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        result = client.v1.images.export_annotations_xml(456, annotation_ids=[100, 101])

        assert isinstance(result, bytes)

        request = httpx_mock.get_request()
        body = json.loads(request.content)
        assert body["annotationIds"] == [100, 101]

    def test_export_raises_not_found_for_invalid_image(self, httpx_mock: HTTPXMock):
        """Export should raise NotFoundError for non-existent image."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/99999/annotations/export/xml",
            status_code=404,
            json={"error": "Image not found"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import NotFoundError

        with pytest.raises(NotFoundError):
            client.v1.images.export_annotations_xml(99999)

    def test_export_raises_authorization_error_without_export_permission(
        self, httpx_mock: HTTPXMock
    ):
        """Export should raise AuthorizationError when user lacks canExportData."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456/annotations/export/xml",
            status_code=403,
            json={"error": "Insufficient permissions"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        from concentriq.exceptions import AuthorizationError

        with pytest.raises(AuthorizationError):
            client.v1.images.export_annotations_xml(456)


class TestImagesImportAnnotations:
    """Tests for Images.import_annotations() behavior."""

    def test_import_returns_result_with_count(self, httpx_mock: HTTPXMock):
        """Import should return result dict with imported annotation count."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456/annotations/import",
            method="POST",
            status_code=201,
            json={
                "meta": {"status": 201},
                "data": {
                    "annotationsResult": {
                        "importedAnnotationsCount": 42,
                        "requireConfirmation": False,
                    }
                },
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as f:
            f.write(SAMPLE_XML)
            temp_path = f.name

        try:
            result = client.v1.images.import_annotations(456, temp_path)
            assert result["importedAnnotationsCount"] == 42
        finally:
            os.unlink(temp_path)

    def test_import_sends_file_as_multipart(self, httpx_mock: HTTPXMock):
        """Import should send the file as multipart form data."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456/annotations/import",
            method="POST",
            status_code=201,
            json={
                "meta": {"status": 201},
                "data": {
                    "annotationsResult": {
                        "importedAnnotationsCount": 1,
                        "requireConfirmation": False,
                    }
                },
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as f:
            f.write(SAMPLE_XML)
            temp_path = f.name

        try:
            client.v1.images.import_annotations(456, temp_path)

            request = httpx_mock.get_request()
            assert "multipart/form-data" in request.headers.get("content-type", "")
        finally:
            os.unlink(temp_path)

    def test_import_with_require_confirmation(self, httpx_mock: HTTPXMock):
        """Import with require_confirmation should return 202 with missing classes."""
        httpx_mock.add_response(
            method="POST",
            status_code=202,
            json={
                "meta": {"status": 202},
                "data": {
                    "annotationsResult": {
                        "requireConfirmation": True,
                        "importedAnnotationsCount": 0,
                        "nonExistingAnnotationClassNames": ["Tumor", "Necrosis"],
                    }
                },
            },
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as f:
            f.write(SAMPLE_XML)
            temp_path = f.name

        try:
            result = client.v1.images.import_annotations(
                456, temp_path, require_confirmation=True
            )
            assert result["requireConfirmation"] is True
            assert "Tumor" in result["nonExistingAnnotationClassNames"]
        finally:
            os.unlink(temp_path)

    def test_import_raises_not_found_for_invalid_image(self, httpx_mock: HTTPXMock):
        """Import should raise NotFoundError for non-existent image."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/99999/annotations/import",
            method="POST",
            status_code=404,
            json={"error": "Image not found"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as f:
            f.write(SAMPLE_XML)
            temp_path = f.name

        try:
            from concentriq.exceptions import NotFoundError

            with pytest.raises(NotFoundError):
                client.v1.images.import_annotations(99999, temp_path)
        finally:
            os.unlink(temp_path)

    def test_import_raises_authorization_error_without_permission(
        self, httpx_mock: HTTPXMock
    ):
        """Import should raise AuthorizationError without canCreateAnnotations."""
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/456/annotations/import",
            method="POST",
            status_code=403,
            json={"error": "Insufficient permissions"},
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )

        with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as f:
            f.write(SAMPLE_XML)
            temp_path = f.name

        try:
            from concentriq.exceptions import AuthorizationError

            with pytest.raises(AuthorizationError):
                client.v1.images.import_annotations(456, temp_path)
        finally:
            os.unlink(temp_path)
