import pytest
from requests import HTTPError

from arkindex_worker.cache import CachedElement
from arkindex_worker.models import Element


@pytest.mark.parametrize(
    ("zone", "expected_url"),
    [
        (None, None),
        (
            {"image": {"url": "http://something/", "server": {"version": 2}}},
            "http://something/full/full/0/default.jpg",
        ),
        (
            {"image": {"url": "http://something", "server": {"version": 2}}},
            "http://something/full/full/0/default.jpg",
        ),
        (
            {"image": {"url": "http://something/", "server": {"version": 3}}},
            "http://something/full/max/0/default.jpg",
        ),
    ],
)
def test_image_url(zone, expected_url):
    url = Element({"zone": None}).image_url()
    assert not url


def test_image_url_iiif_resize():
    url = Element(
        {"zone": {"image": {"url": "http://something/", "server": {"version": 2}}}}
    ).image_url(500)
    assert url == "http://something/full/500/0/default.jpg"


def test_image_url_s3():
    url = Element(
        {
            "zone": {
                "image": {"s3_url": "http://s3/something", "url": "http://something/"}
            }
        }
    ).image_url()
    assert url == "http://s3/something"


def test_image_url_s3_resize():
    url = Element(
        {
            "zone": {
                "image": {"s3_url": "http://s3/something", "url": "http://something/"}
            }
        }
    ).image_url(500)
    assert url == "http://s3/something"


def test_open_image(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "server": {"max_width": None, "max_height": None},
                },
                "polygon": [[0, 0], [181, 0], [181, 240], [0, 240], [0, 0]],
            },
            "rotation_angle": 0,
            "mirrored": False,
        },
    )
    assert elt.open_image(use_full_image=True) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://something/full/full/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )


def test_open_image_resize_portrait(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "width": 400,
                    "height": 600,
                    "server": {"max_width": None, "max_height": None},
                },
                "polygon": [[0, 0], [400, 0], [400, 600], [0, 600], [0, 0]],
            },
            "rotation_angle": 0,
            "mirrored": False,
        }
    )
    # Resize = original size
    assert elt.open_image(max_height=600, use_full_image=True) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://something/full/full/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )
    # Resize = smaller height
    assert elt.open_image(max_height=400, use_full_image=True) == "an image!"
    assert open_mock.call_count == 2
    assert open_mock.call_args == mocker.call(
        "http://something/full/,400/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )
    # Resize = bigger height
    assert elt.open_image(max_height=800, use_full_image=True) == "an image!"
    assert open_mock.call_count == 3
    assert open_mock.call_args == mocker.call(
        "http://something/full/full/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )


def test_open_image_resize_partial_element(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "width": 400,
                    "height": 600,
                    "server": {"max_width": None, "max_height": None},
                },
                "polygon": [[0, 0], [200, 0], [200, 600], [0, 600], [0, 0]],
            },
            "rotation_angle": 0,
            "mirrored": False,
        }
    )
    assert elt.open_image(max_height=400, use_full_image=True) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://something/full/,400/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )


def test_open_image_resize_landscape(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "width": 600,
                    "height": 400,
                    "server": {"max_width": None, "max_height": None},
                },
                "polygon": [[0, 0], [600, 0], [600, 400], [0, 400], [0, 0]],
            },
            "rotation_angle": 0,
            "mirrored": False,
        }
    )
    # Resize = original size
    assert elt.open_image(max_width=600, use_full_image=True) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://something/full/full/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )
    # Resize = smaller width
    assert elt.open_image(max_width=400, use_full_image=True) == "an image!"
    assert open_mock.call_count == 2
    assert open_mock.call_args == mocker.call(
        "http://something/full/400,/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )
    # Resize = bigger width
    assert elt.open_image(max_width=800, use_full_image=True) == "an image!"
    assert open_mock.call_count == 3
    assert open_mock.call_args == mocker.call(
        "http://something/full/full/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )


def test_open_image_resize_square(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "width": 400,
                    "height": 400,
                    "server": {"max_width": None, "max_height": None},
                },
                "polygon": [[0, 0], [400, 0], [400, 400], [0, 400], [0, 0]],
            },
            "rotation_angle": 0,
            "mirrored": False,
        }
    )
    # Resize = original size
    assert (
        elt.open_image(
            max_width=400,
            max_height=400,
            use_full_image=True,
        )
        == "an image!"
    )
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://something/full/full/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )
    # Resize = smaller
    assert (
        elt.open_image(
            max_width=200,
            max_height=200,
            use_full_image=True,
        )
        == "an image!"
    )
    assert open_mock.call_count == 2
    assert open_mock.call_args == mocker.call(
        "http://something/full/200,200/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )
    # Resize = bigger
    assert (
        elt.open_image(
            max_width=800,
            max_height=800,
            use_full_image=True,
        )
        == "an image!"
    )
    assert open_mock.call_count == 3
    assert open_mock.call_args == mocker.call(
        "http://something/full/full/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )


def test_open_image_resize_tiles(mocker):
    mocker.patch("arkindex_worker.image.open_image", return_value="an image!")
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "server": {"max_width": 600, "max_height": 600},
                },
                "polygon": [[0, 0], [800, 0], [800, 800], [0, 800], [0, 0]],
            }
        }
    )
    with pytest.raises(NotImplementedError):
        elt.open_image(max_width=400)


def test_open_image_requires_zone():
    with pytest.raises(ValueError, match="Element 42 has no zone"):
        Element({"id": "42"}).open_image()


def test_open_image_s3(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {"image": {"url": "http://something", "s3_url": "http://s3url"}},
            "rotation_angle": 0,
            "mirrored": False,
        }
    )
    assert elt.open_image(use_full_image=True) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://s3url", rotation_angle=0, mirrored=False
    )


def test_open_image_s3_retry(mocker):
    response_mock = mocker.MagicMock()
    response_mock.status_code = 403
    mocker.patch(
        "arkindex_worker.image.open_image",
        return_value="an image!",
        side_effect=HTTPError(response=response_mock),
    )

    elt = Element(
        {
            "id": "cafe",
            "zone": {"image": {"url": "http://something", "s3_url": "http://oldurl"}},
            "rotation_angle": 0,
            "mirrored": False,
        }
    )

    with pytest.raises(NotImplementedError):
        elt.open_image(use_full_image=True)


def test_open_image_s3_retry_once(mocker):
    response_mock = mocker.MagicMock()
    response_mock.status_code = 403
    mocker.patch(
        "arkindex_worker.image.open_image",
        side_effect=HTTPError(response=response_mock),
    )
    elt = Element(
        {
            "id": "cafe",
            "zone": {"image": {"url": "http://something", "s3_url": "http://oldurl"}},
            "rotation_angle": 0,
            "mirrored": False,
        }
    )

    with pytest.raises(NotImplementedError):
        elt.open_image(use_full_image=True)


def test_open_image_use_full_image_false(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {"url": "http://something", "s3_url": "http://s3url"},
                "url": "http://zoneurl/0,0,400,600/full/0/default.jpg",
            },
            "rotation_angle": 0,
            "mirrored": False,
        }
    )
    assert elt.open_image(use_full_image=False) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://zoneurl/0,0,400,600/full/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )


def test_open_image_resize_use_full_image_false(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "width": 400,
                    "height": 600,
                    "server": {"max_width": None, "max_height": None},
                },
                "polygon": [[0, 0], [400, 0], [400, 600], [0, 600], [0, 0]],
                "url": "http://zoneurl/0,0,400,600/full/0/default.jpg",
            },
            "rotation_angle": 0,
            "mirrored": False,
        }
    )
    # Resize = smaller
    assert elt.open_image(max_height=200, use_full_image=False) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://zoneurl/0,0,400,600/,200/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )


def test_open_image_rotation_mirror(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "server": {"max_width": None, "max_height": None},
                },
                "polygon": [[0, 0], [181, 0], [181, 240], [0, 240], [0, 0]],
            },
            "rotation_angle": 42,
            "mirrored": True,
        },
    )
    assert elt.open_image(use_full_image=True) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://something/full/full/0/default.jpg",
        rotation_angle=42,
        mirrored=True,
    )


def test_open_image_iiif_3(mocker):
    open_mock = mocker.patch(
        "arkindex_worker.image.open_image", return_value="an image!"
    )
    elt = Element(
        {
            "zone": {
                "image": {
                    "url": "http://something",
                    "server": {
                        "max_width": None,
                        "max_height": None,
                        "version": 3,
                    },
                },
                "polygon": [[0, 0], [181, 0], [181, 240], [0, 240], [0, 0]],
            },
            "rotation_angle": 0,
            "mirrored": False,
        },
    )
    assert elt.open_image(use_full_image=True) == "an image!"
    assert open_mock.call_count == 1
    assert open_mock.call_args == mocker.call(
        "http://something/full/max/0/default.jpg",
        rotation_angle=0,
        mirrored=False,
    )


def test_setattr_setitem():
    element = Element({"name": "something"})
    element.type = "page"
    assert dict(element) == {"name": "something", "type": "page"}


def test_element_polygon():
    polygon = [[0, 0], [181, 0], [181, 240], [0, 240], [0, 0]]
    element = Element({"zone": {"polygon": polygon}})
    cached_element = CachedElement(polygon=polygon)
    assert element.polygon == polygon
    assert element.polygon == cached_element.polygon


def test_element_no_polygon():
    element = Element(id="element_id")
    with pytest.raises(ValueError, match="Element element_id has no zone"):
        _ = element.polygon
