"""Test the pjlink media player platform."""
from datetime import timedelta
import socket
from unittest.mock import create_autospec, patch

import pypjlink
from pypjlink import MUTE_AUDIO
from pypjlink.projector import ProjectorError
import pytest

import homeassistant.components.media_player as media_player
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util

from tests.common import assert_setup_component, async_fire_time_changed


@pytest.fixture(name="projector_from_address")
def projector_from_address():
    """Create pjlink Projector mock."""

    with patch("pypjlink.Projector.from_address") as from_address:
        constructor = create_autospec(pypjlink.Projector)
        from_address.return_value = constructor.return_value
        yield from_address


@pytest.fixture(name="mocked_projector")
def mocked_projector(projector_from_address):
    """Create pjlink Projector instance mock."""

    instance = projector_from_address.return_value

    with instance as mocked_instance:
        mocked_instance.get_name.return_value = "Test"
        mocked_instance.get_power.return_value = "on"
        mocked_instance.get_mute.return_value = [0, True]
        mocked_instance.get_input.return_value = [0, 1]
        mocked_instance.get_inputs.return_value = (
            ("HDMI", 1),
            ("HDMI", 2),
            ("VGA", 1),
        )

        yield mocked_instance


@pytest.mark.parametrize("side_effect", [socket.timeout, OSError])
async def test_offline_initialization(
    projector_from_address, hass: HomeAssistant, side_effect
) -> None:
    """Test initialization of a device that is offline."""

    with assert_setup_component(1, media_player.DOMAIN):
        projector_from_address.side_effect = side_effect

        assert await async_setup_component(
            hass,
            media_player.DOMAIN,
            {
                media_player.DOMAIN: {
                    "platform": "pjlink",
                    "name": "test_offline",
                    "host": "127.0.0.1",
                }
            },
        )
        await hass.async_block_till_done()

        state = hass.states.get("media_player.test_offline")
        assert state.state == "unavailable"


async def test_initialization(projector_from_address, hass: HomeAssistant) -> None:
    """Test a device that is available."""

    with assert_setup_component(1, media_player.DOMAIN):
        instance = projector_from_address.return_value

        with instance as mocked_instance:
            mocked_instance.get_name.return_value = "Test"
            mocked_instance.get_inputs.return_value = (
                ("HDMI", 1),
                ("HDMI", 2),
                ("VGA", 1),
            )

        assert await async_setup_component(
            hass,
            media_player.DOMAIN,
            {
                media_player.DOMAIN: {
                    "platform": "pjlink",
                    "host": "127.0.0.1",
                }
            },
        )

        await hass.async_block_till_done()

        state = hass.states.get("media_player.test")
        assert state.state == "off"

        assert "source_list" in state.attributes
        source_list = state.attributes["source_list"]

        assert set(source_list) == {"HDMI 1", "HDMI 2", "VGA 1"}


@pytest.mark.parametrize("power_state", ["on", "warm-up"])
async def test_on_state_init(
    projector_from_address, hass: HomeAssistant, power_state
) -> None:
    """Test a device that is available."""

    with assert_setup_component(1, media_player.DOMAIN):
        instance = projector_from_address.return_value

        with instance as mocked_instance:
            mocked_instance.get_name.return_value = "Test"
            mocked_instance.get_power.return_value = power_state
            mocked_instance.get_inputs.return_value = (("HDMI", 1),)
            mocked_instance.get_input.return_value = ("HDMI", 1)

        assert await async_setup_component(
            hass,
            media_player.DOMAIN,
            {
                media_player.DOMAIN: {
                    "platform": "pjlink",
                    "host": "127.0.0.1",
                }
            },
        )

        await hass.async_block_till_done()

        state = hass.states.get("media_player.test")
        assert state.state == "on"

        assert state.attributes["source"] == "HDMI 1"


async def test_api_error(projector_from_address, hass: HomeAssistant) -> None:
    """Test invalid api responses."""

    with assert_setup_component(1, media_player.DOMAIN):
        instance = projector_from_address.return_value

        with instance as mocked_instance:
            mocked_instance.get_name.return_value = "Test"
            mocked_instance.get_inputs.return_value = (
                ("HDMI", 1),
                ("HDMI", 2),
                ("VGA", 1),
            )
            mocked_instance.get_power.side_effect = KeyError("OK")

        assert await async_setup_component(
            hass,
            media_player.DOMAIN,
            {
                media_player.DOMAIN: {
                    "platform": "pjlink",
                    "host": "127.0.0.1",
                }
            },
        )

        await hass.async_block_till_done()

        state = hass.states.get("media_player.test")
        assert state.state == "off"


async def test_update_unavailable(projector_from_address, hass: HomeAssistant) -> None:
    """Test update to a device that is unavailable."""

    with assert_setup_component(1, media_player.DOMAIN):
        instance = projector_from_address.return_value

        with instance as mocked_instance:
            mocked_instance.get_name.return_value = "Test"
            mocked_instance.get_inputs.return_value = (
                ("HDMI", 1),
                ("HDMI", 2),
                ("VGA", 1),
            )

        assert await async_setup_component(
            hass,
            media_player.DOMAIN,
            {
                media_player.DOMAIN: {
                    "platform": "pjlink",
                    "host": "127.0.0.1",
                }
            },
        )

        await hass.async_block_till_done()

        state = hass.states.get("media_player.test")
        assert state.state == "off"

        projector_from_address.side_effect = socket.timeout
        async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10))
        await hass.async_block_till_done()

        state = hass.states.get("media_player.test")
        assert state.state == "unavailable"


async def test_unavailable_time(mocked_projector, hass: HomeAssistant) -> None:
    """Test unavailable time projector error."""

    assert await async_setup_component(
        hass,
        media_player.DOMAIN,
        {
            media_player.DOMAIN: {
                "platform": "pjlink",
                "host": "127.0.0.1",
            }
        },
    )

    await hass.async_block_till_done()

    state = hass.states.get("media_player.test")
    assert state.state == "on"
    assert state.attributes["source"] is not None
    assert state.attributes["is_volume_muted"] is not False

    mocked_projector.get_power.side_effect = ProjectorError("unavailable time")
    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10))
    await hass.async_block_till_done()

    state = hass.states.get("media_player.test")
    assert state.state == "off"
    assert "source" not in state.attributes
    assert "is_volume_muted" not in state.attributes


async def test_turn_off(mocked_projector, hass: HomeAssistant) -> None:
    """Test turning off beamer."""

    assert await async_setup_component(
        hass,
        media_player.DOMAIN,
        {
            media_player.DOMAIN: {
                "platform": "pjlink",
                "host": "127.0.0.1",
            }
        },
    )

    await hass.async_block_till_done()
    await hass.services.async_call(
        domain=media_player.DOMAIN,
        service="turn_off",
        service_data={ATTR_ENTITY_ID: "media_player.test"},
        blocking=True,
    )

    mocked_projector.set_power.assert_called_with("off")


async def test_turn_on(mocked_projector, hass: HomeAssistant) -> None:
    """Test turning on beamer."""

    assert await async_setup_component(
        hass,
        media_player.DOMAIN,
        {
            media_player.DOMAIN: {
                "platform": "pjlink",
                "host": "127.0.0.1",
            }
        },
    )

    await hass.async_block_till_done()
    await hass.services.async_call(
        domain=media_player.DOMAIN,
        service="turn_on",
        service_data={ATTR_ENTITY_ID: "media_player.test"},
        blocking=True,
    )

    mocked_projector.set_power.assert_called_with("on")


async def test_mute(mocked_projector, hass: HomeAssistant) -> None:
    """Test muting beamer."""

    assert await async_setup_component(
        hass,
        media_player.DOMAIN,
        {
            media_player.DOMAIN: {
                "platform": "pjlink",
                "host": "127.0.0.1",
            }
        },
    )

    await hass.async_block_till_done()
    await hass.services.async_call(
        domain=media_player.DOMAIN,
        service="volume_mute",
        service_data={ATTR_ENTITY_ID: "media_player.test", "is_volume_muted": True},
        blocking=True,
    )

    mocked_projector.set_mute.assert_called_with(MUTE_AUDIO, True)


async def test_unmute(mocked_projector, hass: HomeAssistant) -> None:
    """Test unmuting beamer."""

    assert await async_setup_component(
        hass,
        media_player.DOMAIN,
        {
            media_player.DOMAIN: {
                "platform": "pjlink",
                "host": "127.0.0.1",
            }
        },
    )

    await hass.async_block_till_done()
    await hass.services.async_call(
        domain=media_player.DOMAIN,
        service="volume_mute",
        service_data={ATTR_ENTITY_ID: "media_player.test", "is_volume_muted": False},
        blocking=True,
    )

    mocked_projector.set_mute.assert_called_with(MUTE_AUDIO, False)


async def test_select_source(mocked_projector, hass: HomeAssistant) -> None:
    """Test selecting source."""

    assert await async_setup_component(
        hass,
        media_player.DOMAIN,
        {
            media_player.DOMAIN: {
                "platform": "pjlink",
                "host": "127.0.0.1",
            }
        },
    )

    await hass.async_block_till_done()
    await hass.services.async_call(
        domain=media_player.DOMAIN,
        service="select_source",
        service_data={ATTR_ENTITY_ID: "media_player.test", "source": "VGA 1"},
        blocking=True,
    )

    mocked_projector.set_input.assert_called_with("VGA", 1)
