"""Tests for upload_image high-level helper."""

import io
import json

from pytest_httpx import HTTPXMock

from concentriq import ConcentriqClient
from concentriq.auth import ApiKeyAuth
from concentriq.upload import upload_image


class TestUploadImage:
    """Tests for upload_image() behavior."""

    def test_upload_small_file_uses_single_part(self, httpx_mock: HTTPXMock):
        """Small files should use single-part upload (no multipart)."""
        # Step 1: POST /api/images (create record)
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            method="POST",
            json={"meta": {"status": 201}, "data": {"id": 100}},
        )

        # Step 2: GET /api/images/100 (get storageKey)
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/100",
            json={
                "meta": {"status": 200},
                "data": {"id": 100, "storageKey": "1/users/1/images/100/small.tif"},
            },
        )

        # Step 3: GET /api/v3/files/upload/url (single-part presigned URL)
        httpx_mock.add_response(
            json={"url": "https://s3.amazonaws.com/bucket/key?signed=true"},
        )

        # Step 4: PUT to S3 presigned URL
        httpx_mock.add_response(
            url="https://s3.amazonaws.com/bucket/key?signed=true",
            method="PUT",
            status_code=200,
        )

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        file_data = io.BytesIO(b"fake image data")

        result = upload_image(
            client,
            file_path_or_obj=file_data,
            file_name="small.tif",
            file_size=15,
            image_set_id=1,
        )

        assert result["id"] == 100

    def test_upload_large_file_uses_multipart(self, httpx_mock: HTTPXMock):
        """Large files should use multipart upload."""
        # Step 1: POST /api/images (create record)
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            method="POST",
            json={"meta": {"status": 201}, "data": {"id": 200}},
        )

        # Step 2: GET /api/images/200 (get storageKey)
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/200",
            json={
                "meta": {"status": 200},
                "data": {"id": 200, "storageKey": "1/users/1/images/200/large.tif"},
            },
        )

        # Step 3: POST create multipart upload
        httpx_mock.add_response(
            method="POST",
            json={"Bucket": "bucket", "Key": "key", "UploadId": "upload-123"},
        )

        # Step 4: GET presigned URL for part 1
        httpx_mock.add_response(
            json={"url": "https://s3.amazonaws.com/part1?signed=true"},
        )

        # Step 5: PUT part 1 to S3
        httpx_mock.add_response(
            url="https://s3.amazonaws.com/part1?signed=true",
            method="PUT",
            status_code=200,
            headers={"ETag": '"etag1"'},
        )

        # Step 6: GET presigned URL for part 2
        httpx_mock.add_response(
            json={"url": "https://s3.amazonaws.com/part2?signed=true"},
        )

        # Step 7: PUT part 2 to S3
        httpx_mock.add_response(
            url="https://s3.amazonaws.com/part2?signed=true",
            method="PUT",
            status_code=200,
            headers={"ETag": '"etag2"'},
        )

        # Step 8: POST complete multipart upload
        httpx_mock.add_response(method="POST", status_code=200)

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        # Create data just over 1 byte (we'll set a small part size for testing)
        file_data = io.BytesIO(b"x" * 100)

        result = upload_image(
            client,
            file_path_or_obj=file_data,
            file_name="large.tif",
            file_size=100,
            image_set_id=1,
            part_size=50,  # Force multipart with small part size
        )

        assert result["id"] == 200

    def test_upload_from_file_path(self, httpx_mock: HTTPXMock, tmp_path):
        """Should accept a file path string and read the file."""
        # Create a temp file
        test_file = tmp_path / "test.tif"
        test_file.write_bytes(b"fake image content")

        # Step 1: POST /api/images
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            method="POST",
            json={"meta": {"status": 201}, "data": {"id": 300}},
        )

        # Step 2: GET /api/images/300
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/300",
            json={
                "meta": {"status": 200},
                "data": {"id": 300, "storageKey": "1/users/1/images/300/test.tif"},
            },
        )

        # Step 3: GET single-part upload URL
        httpx_mock.add_response(
            json={"url": "https://s3.amazonaws.com/bucket/key?signed=true"},
        )

        # Step 4: PUT to S3
        httpx_mock.add_response(
            url="https://s3.amazonaws.com/bucket/key?signed=true",
            method="PUT",
            status_code=200,
        )

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

        result = upload_image(
            client,
            file_path_or_obj=str(test_file),
            image_set_id=1,
        )

        assert result["id"] == 300


class TestUploadImageConcurrency:
    """Tests for concurrent multipart upload behavior."""

    def _mock_multipart_upload(self, httpx_mock, num_parts):
        """Helper to set up mocks for a multipart upload with N parts."""
        # Step 1: POST /api/images (create record)
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images",
            method="POST",
            json={"meta": {"status": 201}, "data": {"id": 500}},
        )

        # Step 2: GET /api/images/500 (get storageKey)
        httpx_mock.add_response(
            url="https://test.concentriq.com/api/images/500",
            json={
                "meta": {"status": 200},
                "data": {"id": 500, "storageKey": "1/users/1/images/500/file.tif"},
            },
        )

        # Step 3: POST create multipart upload
        httpx_mock.add_response(
            method="POST",
            json={"Bucket": "bucket", "Key": "key", "UploadId": "upload-concurrent"},
        )

        # For each part: GET presigned URL + PUT to S3
        for i in range(1, num_parts + 1):
            httpx_mock.add_response(
                json={"url": f"https://s3.amazonaws.com/part{i}?signed=true"},
            )
            httpx_mock.add_response(
                url=f"https://s3.amazonaws.com/part{i}?signed=true",
                method="PUT",
                status_code=200,
                headers={"ETag": f'"etag{i}"'},
            )

        # Final: POST complete multipart upload
        httpx_mock.add_response(method="POST", status_code=200)

    def test_concurrent_upload_completes_all_parts(self, httpx_mock: HTTPXMock):
        """Concurrent multipart upload should upload all parts and complete."""
        num_parts = 4
        self._mock_multipart_upload(httpx_mock, num_parts)

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        file_data = io.BytesIO(b"x" * 200)

        result = upload_image(
            client,
            file_path_or_obj=file_data,
            file_name="concurrent.tif",
            file_size=200,
            image_set_id=1,
            part_size=50,
            max_concurrency=2,
        )

        assert result["id"] == 500

        # Verify complete was called with all parts
        requests = httpx_mock.get_requests()
        # Find the complete multipart POST (last POST request)
        post_requests = [r for r in requests if r.method == "POST"]
        complete_request = post_requests[-1]
        body = json.loads(complete_request.content)
        assert len(body["Parts"]) == num_parts
        # Parts should be in order regardless of upload concurrency
        part_numbers = [p["PartNumber"] for p in body["Parts"]]
        assert part_numbers == [1, 2, 3, 4]

    def test_concurrent_upload_with_max_concurrency_one_is_sequential(
        self, httpx_mock: HTTPXMock
    ):
        """max_concurrency=1 should behave like sequential upload."""
        self._mock_multipart_upload(httpx_mock, 2)

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        file_data = io.BytesIO(b"x" * 100)

        result = upload_image(
            client,
            file_path_or_obj=file_data,
            file_name="sequential.tif",
            file_size=100,
            image_set_id=1,
            part_size=50,
            max_concurrency=1,
        )

        assert result["id"] == 500

    def test_default_max_concurrency_is_backwards_compatible(
        self, httpx_mock: HTTPXMock
    ):
        """upload_image without max_concurrency should still work (multipart)."""
        self._mock_multipart_upload(httpx_mock, 2)

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        file_data = io.BytesIO(b"x" * 100)

        # No max_concurrency argument — should use default
        result = upload_image(
            client,
            file_path_or_obj=file_data,
            file_name="default.tif",
            file_size=100,
            image_set_id=1,
            part_size=50,
        )

        assert result["id"] == 500

    def test_concurrent_upload_preserves_etags_per_part(self, httpx_mock: HTTPXMock):
        """Each part's ETag should be correctly associated with its part number."""
        num_parts = 3
        self._mock_multipart_upload(httpx_mock, num_parts)

        client = ConcentriqClient(
            base_url="https://test.concentriq.com", auth=ApiKeyAuth("test-key")
        )
        file_data = io.BytesIO(b"x" * 150)

        upload_image(
            client,
            file_path_or_obj=file_data,
            file_name="etags.tif",
            file_size=150,
            image_set_id=1,
            part_size=50,
            max_concurrency=3,
        )

        # Verify ETags are correctly paired with part numbers
        requests = httpx_mock.get_requests()
        post_requests = [r for r in requests if r.method == "POST"]
        complete_request = post_requests[-1]
        body = json.loads(complete_request.content)

        for part in body["Parts"]:
            expected_etag = f'"etag{part["PartNumber"]}"'
            assert part["ETag"] == expected_etag
