"""The tests for the Graphite component."""
import socket
from unittest import mock
from unittest.mock import patch

import pytest

from homeassistant.components import graphite
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component


@pytest.fixture(name="mock_gf")
def fixture_mock_gf():
    """Mock Graphite Feeder fixture."""
    with patch("homeassistant.components.graphite.GraphiteFeeder") as mock_gf:
        yield mock_gf


@pytest.fixture(name="mock_socket")
def fixture_mock_socket():
    """Mock socket fixture."""
    with patch("socket.socket") as mock_socket:
        yield mock_socket


@pytest.fixture(name="mock_time")
def fixture_mock_time():
    """Mock time fixture."""
    with patch("time.time") as mock_time:
        yield mock_time


async def test_setup(hass: HomeAssistant, mock_socket) -> None:
    """Test setup."""
    assert await async_setup_component(hass, graphite.DOMAIN, {"graphite": {}})
    assert mock_socket.call_count == 1
    assert mock_socket.call_args == mock.call(socket.AF_INET, socket.SOCK_STREAM)


async def test_setup_failure(hass: HomeAssistant, mock_socket) -> None:
    """Test setup fails due to socket error."""
    mock_socket.return_value.connect.side_effect = OSError
    assert not await async_setup_component(hass, graphite.DOMAIN, {"graphite": {}})

    assert mock_socket.call_count == 1
    assert mock_socket.call_args == mock.call(socket.AF_INET, socket.SOCK_STREAM)
    assert mock_socket.return_value.connect.call_count == 1


async def test_full_config(hass: HomeAssistant, mock_gf, mock_socket) -> None:
    """Test setup with full configuration."""
    config = {"graphite": {"host": "foo", "port": 123, "prefix": "me"}}

    assert await async_setup_component(hass, graphite.DOMAIN, config)
    assert mock_gf.call_count == 1
    assert mock_gf.call_args == mock.call(hass, "foo", 123, "tcp", "me")
    assert mock_socket.call_count == 1
    assert mock_socket.call_args == mock.call(socket.AF_INET, socket.SOCK_STREAM)


async def test_full_udp_config(hass: HomeAssistant, mock_gf, mock_socket) -> None:
    """Test setup with full configuration and UDP protocol."""
    config = {
        "graphite": {"host": "foo", "port": 123, "protocol": "udp", "prefix": "me"}
    }

    assert await async_setup_component(hass, graphite.DOMAIN, config)
    assert mock_gf.call_count == 1
    assert mock_gf.call_args == mock.call(hass, "foo", 123, "udp", "me")
    assert mock_socket.call_count == 0


async def test_config_port(hass: HomeAssistant, mock_gf, mock_socket) -> None:
    """Test setup with invalid port."""
    config = {"graphite": {"host": "foo", "port": 2003}}

    assert await async_setup_component(hass, graphite.DOMAIN, config)
    assert mock_gf.called
    assert mock_socket.call_count == 1
    assert mock_socket.call_args == mock.call(socket.AF_INET, socket.SOCK_STREAM)


async def test_start(hass: HomeAssistant, mock_socket, mock_time) -> None:
    """Test the start."""
    mock_time.return_value = 12345
    assert await async_setup_component(hass, graphite.DOMAIN, {"graphite": {}})
    await hass.async_block_till_done()
    mock_socket.reset_mock()

    await hass.async_start()
    await hass.async_block_till_done()

    hass.states.async_set("test.entity", STATE_ON)
    await hass.async_block_till_done()
    hass.data[graphite.DOMAIN]._queue.join()

    assert mock_socket.return_value.connect.call_count == 1
    assert mock_socket.return_value.connect.call_args == mock.call(("localhost", 2003))
    assert mock_socket.return_value.sendall.call_count == 1
    assert mock_socket.return_value.sendall.call_args == mock.call(
        b"ha.test.entity.state 1.000000 12345"
    )
    assert mock_socket.return_value.send.call_count == 1
    assert mock_socket.return_value.send.call_args == mock.call(b"\n")
    assert mock_socket.return_value.close.call_count == 1


async def test_shutdown(hass: HomeAssistant, mock_socket, mock_time) -> None:
    """Test the shutdown."""
    mock_time.return_value = 12345
    assert await async_setup_component(hass, graphite.DOMAIN, {"graphite": {}})
    await hass.async_block_till_done()
    mock_socket.reset_mock()

    await hass.async_start()
    await hass.async_block_till_done()

    hass.states.async_set("test.entity", STATE_ON)
    await hass.async_block_till_done()
    hass.data[graphite.DOMAIN]._queue.join()

    assert mock_socket.return_value.connect.call_count == 1
    assert mock_socket.return_value.connect.call_args == mock.call(("localhost", 2003))
    assert mock_socket.return_value.sendall.call_count == 1
    assert mock_socket.return_value.sendall.call_args == mock.call(
        b"ha.test.entity.state 1.000000 12345"
    )
    assert mock_socket.return_value.send.call_count == 1
    assert mock_socket.return_value.send.call_args == mock.call(b"\n")
    assert mock_socket.return_value.close.call_count == 1

    mock_socket.reset_mock()

    await hass.async_stop()
    await hass.async_block_till_done()

    hass.states.async_set("test.entity", STATE_OFF)
    await hass.async_block_till_done()

    assert mock_socket.return_value.connect.call_count == 0
    assert mock_socket.return_value.sendall.call_count == 0


async def test_report_attributes(hass: HomeAssistant, mock_socket, mock_time) -> None:
    """Test the reporting with attributes."""
    attrs = {"foo": 1, "bar": 2.0, "baz": True, "bat": "NaN"}
    expected = [
        "ha.test.entity.foo 1.000000 12345",
        "ha.test.entity.bar 2.000000 12345",
        "ha.test.entity.baz 1.000000 12345",
        "ha.test.entity.state 1.000000 12345",
    ]

    mock_time.return_value = 12345
    assert await async_setup_component(hass, graphite.DOMAIN, {"graphite": {}})
    await hass.async_block_till_done()
    mock_socket.reset_mock()

    await hass.async_start()
    await hass.async_block_till_done()

    hass.states.async_set("test.entity", STATE_ON, attrs)
    await hass.async_block_till_done()
    hass.data[graphite.DOMAIN]._queue.join()

    assert mock_socket.return_value.connect.call_count == 1
    assert mock_socket.return_value.connect.call_args == mock.call(("localhost", 2003))
    assert mock_socket.return_value.sendall.call_count == 1
    assert mock_socket.return_value.sendall.call_args == mock.call(
        "\n".join(expected).encode("utf-8")
    )
    assert mock_socket.return_value.send.call_count == 1
    assert mock_socket.return_value.send.call_args == mock.call(b"\n")
    assert mock_socket.return_value.close.call_count == 1


async def test_report_with_string_state(
    hass: HomeAssistant, mock_socket, mock_time
) -> None:
    """Test the reporting with strings."""
    expected = [
        "ha.test.entity.foo 1.000000 12345",
        "ha.test.entity.state 1.000000 12345",
    ]

    mock_time.return_value = 12345
    assert await async_setup_component(hass, graphite.DOMAIN, {"graphite": {}})
    await hass.async_block_till_done()
    mock_socket.reset_mock()

    await hass.async_start()
    await hass.async_block_till_done()

    hass.states.async_set("test.entity", "above_horizon", {"foo": 1.0})
    await hass.async_block_till_done()
    hass.data[graphite.DOMAIN]._queue.join()

    assert mock_socket.return_value.connect.call_count == 1
    assert mock_socket.return_value.connect.call_args == mock.call(("localhost", 2003))
    assert mock_socket.return_value.sendall.call_count == 1
    assert mock_socket.return_value.sendall.call_args == mock.call(
        "\n".join(expected).encode("utf-8")
    )
    assert mock_socket.return_value.send.call_count == 1
    assert mock_socket.return_value.send.call_args == mock.call(b"\n")
    assert mock_socket.return_value.close.call_count == 1

    mock_socket.reset_mock()

    hass.states.async_set("test.entity", "not_float")
    await hass.async_block_till_done()
    hass.data[graphite.DOMAIN]._queue.join()

    assert mock_socket.return_value.connect.call_count == 0
    assert mock_socket.return_value.sendall.call_count == 0
    assert mock_socket.return_value.send.call_count == 0
    assert mock_socket.return_value.close.call_count == 0


async def test_report_with_binary_state(
    hass: HomeAssistant, mock_socket, mock_time
) -> None:
    """Test the reporting with binary state."""
    mock_time.return_value = 12345
    assert await async_setup_component(hass, graphite.DOMAIN, {"graphite": {}})
    await hass.async_block_till_done()
    mock_socket.reset_mock()

    await hass.async_start()
    await hass.async_block_till_done()

    expected = [
        "ha.test.entity.foo 1.000000 12345",
        "ha.test.entity.state 1.000000 12345",
    ]
    hass.states.async_set("test.entity", STATE_ON, {"foo": 1.0})
    await hass.async_block_till_done()
    hass.data[graphite.DOMAIN]._queue.join()

    assert mock_socket.return_value.connect.call_count == 1
    assert mock_socket.return_value.connect.call_args == mock.call(("localhost", 2003))
    assert mock_socket.return_value.sendall.call_count == 1
    assert mock_socket.return_value.sendall.call_args == mock.call(
        "\n".join(expected).encode("utf-8")
    )
    assert mock_socket.return_value.send.call_count == 1
    assert mock_socket.return_value.send.call_args == mock.call(b"\n")
    assert mock_socket.return_value.close.call_count == 1

    mock_socket.reset_mock()

    expected = [
        "ha.test.entity.foo 1.000000 12345",
        "ha.test.entity.state 0.000000 12345",
    ]
    hass.states.async_set("test.entity", STATE_OFF, {"foo": 1.0})
    await hass.async_block_till_done()
    hass.data[graphite.DOMAIN]._queue.join()

    assert mock_socket.return_value.connect.call_count == 1
    assert mock_socket.return_value.connect.call_args == mock.call(("localhost", 2003))
    assert mock_socket.return_value.sendall.call_count == 1
    assert mock_socket.return_value.sendall.call_args == mock.call(
        "\n".join(expected).encode("utf-8")
    )
    assert mock_socket.return_value.send.call_count == 1
    assert mock_socket.return_value.send.call_args == mock.call(b"\n")
    assert mock_socket.return_value.close.call_count == 1


@pytest.mark.parametrize(
    ("error", "log_text"),
    [
        (OSError, "Failed to send data to graphite"),
        (socket.gaierror, "Unable to connect to host"),
        (Exception, "Failed to process STATE_CHANGED event"),
    ],
)
async def test_send_to_graphite_errors(
    hass: HomeAssistant,
    mock_socket,
    mock_time,
    caplog: pytest.LogCaptureFixture,
    error,
    log_text,
) -> None:
    """Test the sending with errors."""
    mock_time.return_value = 12345
    assert await async_setup_component(hass, graphite.DOMAIN, {"graphite": {}})
    await hass.async_block_till_done()
    mock_socket.reset_mock()

    await hass.async_start()
    await hass.async_block_till_done()

    mock_socket.return_value.connect.side_effect = error

    hass.states.async_set("test.entity", STATE_ON)
    await hass.async_block_till_done()
    hass.data[graphite.DOMAIN]._queue.join()

    assert log_text in caplog.text