"""Test the aiohttp client helper."""
import asyncio
from unittest.mock import Mock, patch

import aiohttp
import pytest

from homeassistant.core import EVENT_HOMEASSISTANT_CLOSE
import homeassistant.helpers.aiohttp_client as client
from homeassistant.setup import async_setup_component


@pytest.fixture(name="camera_client")
def camera_client_fixture(hass, hass_client):
    """Fixture to fetch camera streams."""
    assert hass.loop.run_until_complete(
        async_setup_component(
            hass,
            "camera",
            {
                "camera": {
                    "name": "config_test",
                    "platform": "mjpeg",
                    "mjpeg_url": "http://example.com/mjpeg_stream",
                }
            },
        )
    )
    hass.loop.run_until_complete(hass.async_block_till_done())

    yield hass.loop.run_until_complete(hass_client())


async def test_get_clientsession_with_ssl(hass):
    """Test init clientsession with ssl."""
    client.async_get_clientsession(hass)

    assert isinstance(hass.data[client.DATA_CLIENTSESSION], aiohttp.ClientSession)
    assert isinstance(hass.data[client.DATA_CONNECTOR], aiohttp.TCPConnector)


async def test_get_clientsession_without_ssl(hass):
    """Test init clientsession without ssl."""
    client.async_get_clientsession(hass, verify_ssl=False)

    assert isinstance(
        hass.data[client.DATA_CLIENTSESSION_NOTVERIFY], aiohttp.ClientSession
    )
    assert isinstance(hass.data[client.DATA_CONNECTOR_NOTVERIFY], aiohttp.TCPConnector)


async def test_create_clientsession_with_ssl_and_cookies(hass):
    """Test create clientsession with ssl."""
    session = client.async_create_clientsession(hass, cookies={"bla": True})
    assert isinstance(session, aiohttp.ClientSession)
    assert isinstance(hass.data[client.DATA_CONNECTOR], aiohttp.TCPConnector)


async def test_create_clientsession_without_ssl_and_cookies(hass):
    """Test create clientsession without ssl."""
    session = client.async_create_clientsession(hass, False, cookies={"bla": True})
    assert isinstance(session, aiohttp.ClientSession)
    assert isinstance(hass.data[client.DATA_CONNECTOR_NOTVERIFY], aiohttp.TCPConnector)


async def test_get_clientsession_cleanup(hass):
    """Test init clientsession with ssl."""
    client.async_get_clientsession(hass)

    assert isinstance(hass.data[client.DATA_CLIENTSESSION], aiohttp.ClientSession)
    assert isinstance(hass.data[client.DATA_CONNECTOR], aiohttp.TCPConnector)

    hass.bus.async_fire(EVENT_HOMEASSISTANT_CLOSE)
    await hass.async_block_till_done()

    assert hass.data[client.DATA_CLIENTSESSION].closed
    assert hass.data[client.DATA_CONNECTOR].closed


async def test_get_clientsession_cleanup_without_ssl(hass):
    """Test init clientsession with ssl."""
    client.async_get_clientsession(hass, verify_ssl=False)

    assert isinstance(
        hass.data[client.DATA_CLIENTSESSION_NOTVERIFY], aiohttp.ClientSession
    )
    assert isinstance(hass.data[client.DATA_CONNECTOR_NOTVERIFY], aiohttp.TCPConnector)

    hass.bus.async_fire(EVENT_HOMEASSISTANT_CLOSE)
    await hass.async_block_till_done()

    assert hass.data[client.DATA_CLIENTSESSION_NOTVERIFY].closed
    assert hass.data[client.DATA_CONNECTOR_NOTVERIFY].closed


async def test_get_clientsession_patched_close(hass):
    """Test closing clientsession does not work."""
    with patch("aiohttp.ClientSession.close") as mock_close:
        session = client.async_get_clientsession(hass)

        assert isinstance(hass.data[client.DATA_CLIENTSESSION], aiohttp.ClientSession)
        assert isinstance(hass.data[client.DATA_CONNECTOR], aiohttp.TCPConnector)

        with pytest.raises(RuntimeError):
            await session.close()

        assert mock_close.call_count == 0


@patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set())
async def test_warning_close_session_integration(hass, caplog):
    """Test log warning message when closing the session from integration context."""
    with patch(
        "homeassistant.helpers.frame.extract_stack",
        return_value=[
            Mock(
                filename="/home/paulus/homeassistant/core.py",
                lineno="23",
                line="do_something()",
            ),
            Mock(
                filename="/home/paulus/homeassistant/components/hue/light.py",
                lineno="23",
                line="await session.close()",
            ),
            Mock(
                filename="/home/paulus/aiohue/lights.py",
                lineno="2",
                line="something()",
            ),
        ],
    ):
        session = client.async_get_clientsession(hass)
        await session.close()
    assert (
        "Detected integration that closes the Home Assistant aiohttp session. "
        "Please report issue for hue using this method at "
        "homeassistant/components/hue/light.py, line 23: await session.close()"
    ) in caplog.text


@patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set())
async def test_warning_close_session_custom(hass, caplog):
    """Test log warning message when closing the session from custom context."""
    with patch(
        "homeassistant.helpers.frame.extract_stack",
        return_value=[
            Mock(
                filename="/home/paulus/homeassistant/core.py",
                lineno="23",
                line="do_something()",
            ),
            Mock(
                filename="/home/paulus/config/custom_components/hue/light.py",
                lineno="23",
                line="await session.close()",
            ),
            Mock(
                filename="/home/paulus/aiohue/lights.py",
                lineno="2",
                line="something()",
            ),
        ],
    ):
        session = client.async_get_clientsession(hass)
        await session.close()
    assert (
        "Detected integration that closes the Home Assistant aiohttp session. "
        "Please report issue to the custom component author for hue using this method at "
        "custom_components/hue/light.py, line 23: await session.close()" in caplog.text
    )


async def test_async_aiohttp_proxy_stream(aioclient_mock, camera_client):
    """Test that it fetches the given url."""
    aioclient_mock.get("http://example.com/mjpeg_stream", content=b"Frame1Frame2Frame3")

    resp = await camera_client.get("/api/camera_proxy_stream/camera.config_test")

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


async def test_async_aiohttp_proxy_stream_timeout(aioclient_mock, camera_client):
    """Test that it fetches the given url."""
    aioclient_mock.get("http://example.com/mjpeg_stream", exc=asyncio.TimeoutError())

    resp = await camera_client.get("/api/camera_proxy_stream/camera.config_test")
    assert resp.status == 504


async def test_async_aiohttp_proxy_stream_client_err(aioclient_mock, camera_client):
    """Test that it fetches the given url."""
    aioclient_mock.get("http://example.com/mjpeg_stream", exc=aiohttp.ClientError())

    resp = await camera_client.get("/api/camera_proxy_stream/camera.config_test")
    assert resp.status == 502


async def test_client_session_immutable_headers(hass):
    """Test we can't mutate headers."""
    session = client.async_get_clientsession(hass)

    with pytest.raises(TypeError):
        session.headers["user-agent"] = "bla"

    with pytest.raises(AttributeError):
        session.headers.update({"user-agent": "bla"})