import pytest

from arkindex.exceptions import ArkindexClientError, ArkindexConflictError, ErrorResponse
from arkindex.shortcuts import create_iiif_image, create_image

CONFLICT_EXCEPTIONS = [
    # Old format
    ErrorResponse(
        status_code=400, title="400 Bad Request", content={"path": ["An image already exists"], "id": "imageid"}
    ),
    # New format
    ArkindexConflictError(
        status_code=409,
        title="409 Conflict",
        content={
            "type": "client_error",
            "errors": [{"attr": ["path"], "code": "conflict", "detail": "An image already exists"}],
            "existing_id": "imageid",
        },
    ),
]


UNSUPPORTED_EXCEPTIONS = [
    # Errors without an ID
    ErrorResponse(status_code=400, title="400 Bad Request", content={"path": ["An image already exists"]}),
    ArkindexConflictError(
        status_code=409,
        title="409 Conflict",
        content={
            "type": "client_error",
            "errors": [{"attr": ["path"], "code": "conflict", "detail": "An image already exists"}],
            "existing_id": None,
        },
    ),
    # Non-conflict errors
    ErrorResponse(status_code=403, title="403 Forbidden", content={"detail": "Nope."}),
    ArkindexClientError(
        status_code=403,
        title="403 Forbidden",
        content={"type": "client_error", "errors": [{"attr": None, "code": "nope", "detail": "Nope."}]},
    ),
]


def test_create_image(mock_client, responses, tmp_path):
    file_path = tmp_path / "image.jpg"
    file_path.write_bytes(b"hello")
    mock_client.add_response(
        "CreateImage",
        body={"path": "image_path"},
        response={
            "id": "imageid",
            "status": "unchecked",
            "s3_put_url": "https://s3.dummy.test/puturl",
        },
    )
    responses.add(responses.PUT, "https://s3.dummy.test/puturl", body=b"hello", status=200)
    mock_client.add_response(
        "PartialUpdateImage", id="imageid", body={"status": "checked"}, response={"id": "imageid", "status": "checked"}
    )

    assert create_image(mock_client, file_path, "image_path") == {"id": "imageid", "status": "checked"}


@pytest.mark.parametrize("exception", CONFLICT_EXCEPTIONS)
def test_create_image_existing(mock_client, tmp_path, exception):
    file_path = tmp_path / "image.jpg"
    file_path.write_bytes(b"hello")
    mock_client.add_response(
        "CreateImage",
        body={"path": "image_path"},
        response=exception,
    )
    mock_client.add_response(
        "RetrieveImage",
        id="imageid",
        response={
            "id": "imageid",
            "status": "checked",
        },
    )
    assert create_image(mock_client, file_path, "image_path") == {"id": "imageid", "status": "checked"}


@pytest.mark.parametrize("exception", CONFLICT_EXCEPTIONS)
@pytest.mark.parametrize("image_status", ("unchecked", "error"))
def test_create_image_reupload(mock_client, responses, tmp_path, exception, image_status):
    file_path = tmp_path / "image.jpg"
    file_path.write_bytes(b"hello")
    mock_client.add_response(
        "CreateImage",
        body={"path": "image_path"},
        response=exception,
    )
    mock_client.add_response(
        "RetrieveImage",
        id="imageid",
        response={
            "id": "imageid",
            "status": image_status,
            "s3_put_url": "https://s3.dummy.test/puturl",
        },
    )
    responses.add(responses.PUT, "https://s3.dummy.test/puturl", body=b"hello", status=200)
    mock_client.add_response(
        "PartialUpdateImage", id="imageid", body={"status": "checked"}, response={"id": "imageid", "status": "checked"}
    )
    assert create_image(mock_client, file_path, "image_path") == {"id": "imageid", "status": "checked"}


@pytest.mark.parametrize("exception", CONFLICT_EXCEPTIONS)
def test_create_image_checked_force(mock_client, exception):
    """
    Calling create_image(…, force=True) on a checked existing image does not reupload the image, but it performs a check
    """
    mock_client.add_response(
        "CreateImage",
        body={"path": "image_path"},
        response=exception,
    )
    mock_client.add_response(
        "RetrieveImage",
        id="imageid",
        response={
            "id": "imageid",
            "status": "checked",
            "s3_put_url": "https://s3.dummy.test/puturl",
        },
    )
    mock_client.add_response(
        "PartialUpdateImage", id="imageid", body={"status": "checked"}, response={"id": "imageid", "status": "checked"}
    )
    assert create_image(mock_client, "/dev/null", "image_path", force_check=True) == {
        "id": "imageid",
        "status": "checked",
    }


@pytest.mark.parametrize("exception", UNSUPPORTED_EXCEPTIONS)
def test_create_image_error(mock_client, exception):
    mock_client.add_response(
        "CreateImage",
        body={"path": "image_path"},
        response=exception,
    )
    with pytest.raises(ErrorResponse) as ctx:
        create_image(mock_client, "/dev/null", "image_path")
    assert ctx.value == exception


@pytest.mark.parametrize(["width", "height"], [(1000, None), (None, 1000)])
def test_create_iiif_image_width_and_height(mock_client, width, height):
    with pytest.raises(AssertionError):
        create_iiif_image(mock_client, "http://dummy.test", width=width, height=height)


def test_create_iiif_image(mock_client):
    mock_client.add_response(
        "CreateIIIFURL",
        body={"url": "https://dummy.test", "width": None, "height": None},
        response={"id": "imageid", "status": "checked"},
    )
    assert create_iiif_image(mock_client, "https://dummy.test") == {"id": "imageid", "status": "checked"}


@pytest.mark.parametrize("exception", CONFLICT_EXCEPTIONS)
def test_create_iiif_image_exists(mock_client, exception):
    mock_client.add_response(
        "CreateIIIFURL",
        body={"url": "https://dummy.test", "width": None, "height": None},
        response=exception,
    )
    mock_client.add_response(
        "RetrieveImage",
        id="imageid",
        response={"id": "imageid", "status": "checked"},
    )
    assert create_iiif_image(mock_client, "https://dummy.test") == {"id": "imageid", "status": "checked"}


@pytest.mark.parametrize("exception", CONFLICT_EXCEPTIONS)
@pytest.mark.parametrize("image_status", ("unchecked", "error"))
def test_create_iiif_image_recheck(mock_client, exception, image_status):
    mock_client.add_response(
        "CreateIIIFURL",
        body={"url": "https://dummy.test", "width": None, "height": None},
        response=exception,
    )
    mock_client.add_response(
        "RetrieveImage",
        id="imageid",
        response={"id": "imageid", "status": image_status},
    )
    mock_client.add_response(
        "PartialUpdateImage",
        id="imageid",
        body={"status": "checked"},
        response={"id": "imageid", "status": "checked"},
    )
    assert create_iiif_image(mock_client, "https://dummy.test") == {"id": "imageid", "status": "checked"}


@pytest.mark.parametrize("exception", UNSUPPORTED_EXCEPTIONS)
def test_create_iiif_image_error(mock_client, exception):
    mock_client.add_response(
        "CreateIIIFURL",
        body={"url": "https://dummy.test", "width": None, "height": None},
        response=exception,
    )
    with pytest.raises(ErrorResponse) as ctx:
        create_iiif_image(mock_client, "https://dummy.test")
    assert ctx.value == exception


@pytest.mark.parametrize("exception", CONFLICT_EXCEPTIONS)
@pytest.mark.parametrize("image_status", ("unchecked", "error"))
def test_create_iiif_image_async_check(mock_client, exception, image_status):
    mock_client.add_response(
        "CreateIIIFURL",
        body={"url": "https://dummy.test", "width": 1000, "height": 2000},
        response=exception,
    )
    mock_client.add_response(
        "RetrieveImage",
        id="imageid",
        response={"id": "imageid", "status": image_status},
    )
    # PartialUpdateImage is not called, since the check will be done asynchronously
    assert create_iiif_image(mock_client, "https://dummy.test", width=1000, height=2000) == {
        "id": "imageid",
        "status": image_status,
    }
