"""The tests for generic camera component."""
import asyncio
from aiohttp.client_exceptions import ClientResponseError

from homeassistant.util import dt as dt_util

from homeassistant.setup import async_setup_component

# An infinitesimally small time-delta.
EPSILON_DELTA = 0.0000000001


def radar_map_url(dim: int = 512) -> str:
    """Build map url, defaulting to 512 wide (as in component)."""
    return ("https://api.buienradar.nl/" "image/1.0/RadarMapNL?w={dim}&h={dim}").format(
        dim=dim
    )


async def test_fetching_url_and_caching(aioclient_mock, hass, hass_client):
    """Test that it fetches the given url."""
    aioclient_mock.get(radar_map_url(), text="hello world")

    await async_setup_component(
        hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}}
    )

    client = await hass_client()

    resp = await client.get("/api/camera_proxy/camera.config_test")

    assert resp.status == 200
    assert aioclient_mock.call_count == 1
    body = await resp.text()
    assert body == "hello world"

    # default delta is 600s -> should be the same when calling immediately
    # afterwards.

    resp = await client.get("/api/camera_proxy/camera.config_test")
    assert aioclient_mock.call_count == 1


async def test_expire_delta(aioclient_mock, hass, hass_client):
    """Test that the cache expires after delta."""
    aioclient_mock.get(radar_map_url(), text="hello world")

    await async_setup_component(
        hass,
        "camera",
        {
            "camera": {
                "name": "config_test",
                "platform": "buienradar",
                "delta": EPSILON_DELTA,
            }
        },
    )

    client = await hass_client()

    resp = await client.get("/api/camera_proxy/camera.config_test")

    assert resp.status == 200
    assert aioclient_mock.call_count == 1
    body = await resp.text()
    assert body == "hello world"

    await asyncio.sleep(EPSILON_DELTA)
    # tiny delta has passed -> should immediately call again
    resp = await client.get("/api/camera_proxy/camera.config_test")
    assert aioclient_mock.call_count == 2


async def test_only_one_fetch_at_a_time(aioclient_mock, hass, hass_client):
    """Test that it fetches with only one request at the same time."""
    aioclient_mock.get(radar_map_url(), text="hello world")

    await async_setup_component(
        hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}}
    )

    client = await hass_client()

    resp_1 = client.get("/api/camera_proxy/camera.config_test")
    resp_2 = client.get("/api/camera_proxy/camera.config_test")

    resp = await resp_1
    resp_2 = await resp_2

    assert (await resp.text()) == (await resp_2.text())

    assert aioclient_mock.call_count == 1


async def test_dimension(aioclient_mock, hass, hass_client):
    """Test that it actually adheres to the dimension."""
    aioclient_mock.get(radar_map_url(700), text="hello world")

    await async_setup_component(
        hass,
        "camera",
        {"camera": {"name": "config_test", "platform": "buienradar", "dimension": 700}},
    )

    client = await hass_client()

    await client.get("/api/camera_proxy/camera.config_test")

    assert aioclient_mock.call_count == 1


async def test_failure_response_not_cached(aioclient_mock, hass, hass_client):
    """Test that it does not cache a failure response."""
    aioclient_mock.get(radar_map_url(), text="hello world", status=401)

    await async_setup_component(
        hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}}
    )

    client = await hass_client()

    await client.get("/api/camera_proxy/camera.config_test")
    await client.get("/api/camera_proxy/camera.config_test")

    assert aioclient_mock.call_count == 2


async def test_last_modified_updates(aioclient_mock, hass, hass_client):
    """Test that it does respect HTTP not modified."""
    # Build Last-Modified header value
    now = dt_util.utcnow()
    last_modified = now.strftime("%a, %d %m %Y %H:%M:%S GMT")

    aioclient_mock.get(
        radar_map_url(),
        text="hello world",
        status=200,
        headers={"Last-Modified": last_modified},
    )

    await async_setup_component(
        hass,
        "camera",
        {
            "camera": {
                "name": "config_test",
                "platform": "buienradar",
                "delta": EPSILON_DELTA,
            }
        },
    )

    client = await hass_client()

    resp_1 = await client.get("/api/camera_proxy/camera.config_test")
    # It is not possible to check if header was sent.
    assert aioclient_mock.call_count == 1

    await asyncio.sleep(EPSILON_DELTA)

    # Content has expired, change response to a 304 NOT MODIFIED, which has no
    # text, i.e. old value should be kept
    aioclient_mock.clear_requests()
    # mock call count is now reset as well:
    assert aioclient_mock.call_count == 0

    aioclient_mock.get(radar_map_url(), text=None, status=304)

    resp_2 = await client.get("/api/camera_proxy/camera.config_test")
    assert aioclient_mock.call_count == 1

    assert (await resp_1.read()) == (await resp_2.read())


async def test_retries_after_error(aioclient_mock, hass, hass_client):
    """Test that it does retry after an error instead of caching."""
    await async_setup_component(
        hass, "camera", {"camera": {"name": "config_test", "platform": "buienradar"}}
    )

    client = await hass_client()

    aioclient_mock.get(radar_map_url(), text=None, status=500)

    # A 404 should not return data and throw:
    try:
        await client.get("/api/camera_proxy/camera.config_test")
    except ClientResponseError:
        pass

    assert aioclient_mock.call_count == 1

    # Change the response to a 200
    aioclient_mock.clear_requests()
    aioclient_mock.get(radar_map_url(), text="DEADBEEF")

    assert aioclient_mock.call_count == 0

    # http error should not be cached, immediate retry.
    resp_2 = await client.get("/api/camera_proxy/camera.config_test")
    assert aioclient_mock.call_count == 1

    # Binary text can not be added as body to `aioclient_mock.get(text=...)`,
    # while `resp.read()` returns bytes, encode the value.
    assert (await resp_2.read()) == b"DEADBEEF"
