"""Tests for the seventeentrack sensor."""
from __future__ import annotations

import datetime
from unittest.mock import patch

from py17track.package import Package
import pytest

from homeassistant.components.seventeentrack.sensor import (
    CONF_SHOW_ARCHIVED,
    CONF_SHOW_DELIVERED,
)
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.setup import async_setup_component
from homeassistant.util import utcnow

from tests.common import async_fire_time_changed

VALID_CONFIG_MINIMAL = {
    "sensor": {
        "platform": "seventeentrack",
        CONF_USERNAME: "test",
        CONF_PASSWORD: "test",
    }
}

INVALID_CONFIG = {"sensor": {"platform": "seventeentrack", "boom": "test"}}

VALID_CONFIG_FULL = {
    "sensor": {
        "platform": "seventeentrack",
        CONF_USERNAME: "test",
        CONF_PASSWORD: "test",
        CONF_SHOW_ARCHIVED: True,
        CONF_SHOW_DELIVERED: True,
    }
}

VALID_CONFIG_FULL_NO_DELIVERED = {
    "sensor": {
        "platform": "seventeentrack",
        CONF_USERNAME: "test",
        CONF_PASSWORD: "test",
        CONF_SHOW_ARCHIVED: False,
        CONF_SHOW_DELIVERED: False,
    }
}

DEFAULT_SUMMARY = {
    "Not Found": 0,
    "In Transit": 0,
    "Expired": 0,
    "Ready to be Picked Up": 0,
    "Undelivered": 0,
    "Delivered": 0,
    "Returned": 0,
}

NEW_SUMMARY_DATA = {
    "Not Found": 1,
    "In Transit": 1,
    "Expired": 1,
    "Ready to be Picked Up": 1,
    "Undelivered": 1,
    "Delivered": 1,
    "Returned": 1,
}


class ClientMock:
    """Mock the py17track client to inject the ProfileMock."""

    def __init__(self, session) -> None:
        """Mock the profile."""
        self.profile = ProfileMock()


class ProfileMock:
    """ProfileMock will mock data coming from 17track."""

    package_list = []
    login_result = True
    summary_data = DEFAULT_SUMMARY
    account_id = "123"

    @classmethod
    def reset(cls):
        """Reset data to defaults."""
        cls.package_list = []
        cls.login_result = True
        cls.summary_data = DEFAULT_SUMMARY
        cls.account_id = "123"

    def __init__(self) -> None:
        """Override Account id."""
        self.account_id = self.__class__.account_id

    async def login(self, email: str, password: str) -> bool:
        """Login mock."""
        return self.__class__.login_result

    async def packages(
        self,
        package_state: int | str = "",
        show_archived: bool = False,
        tz: str = "UTC",
    ) -> list:
        """Packages mock."""  # noqa: D401
        return self.__class__.package_list[:]

    async def summary(self, show_archived: bool = False) -> dict:
        """Summary mock."""
        return self.__class__.summary_data


@pytest.fixture(autouse=True, name="mock_client")
def fixture_mock_client():
    """Mock py17track client."""
    with patch(
        "homeassistant.components.seventeentrack.sensor.SeventeenTrackClient",
        new=ClientMock,
    ):
        yield
    ProfileMock.reset()


async def _setup_seventeentrack(hass, config=None, summary_data=None):
    """Set up component using config."""
    if not config:
        config = VALID_CONFIG_MINIMAL
    if not summary_data:
        summary_data = {}

    ProfileMock.summary_data = summary_data
    assert await async_setup_component(hass, "sensor", config)
    await hass.async_block_till_done()


async def _goto_future(hass, future=None):
    """Move to future."""
    if not future:
        future = utcnow() + datetime.timedelta(minutes=10)
    with patch("homeassistant.util.utcnow", return_value=future):
        async_fire_time_changed(hass, future)
        await hass.async_block_till_done()
        async_fire_time_changed(hass, future)
        await hass.async_block_till_done()


async def test_full_valid_config(hass):
    """Ensure everything starts correctly."""
    assert await async_setup_component(hass, "sensor", VALID_CONFIG_FULL)
    await hass.async_block_till_done()
    assert len(hass.states.async_entity_ids()) == len(ProfileMock.summary_data.keys())


async def test_valid_config(hass):
    """Ensure everything starts correctly."""
    assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL)
    await hass.async_block_till_done()
    assert len(hass.states.async_entity_ids()) == len(ProfileMock.summary_data.keys())


async def test_invalid_config(hass):
    """Ensure nothing is created when config is wrong."""
    assert await async_setup_component(hass, "sensor", INVALID_CONFIG)

    assert not hass.states.async_entity_ids("sensor")


async def test_add_package(hass):
    """Ensure package is added correctly when user add a new package."""
    package = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
    )
    ProfileMock.package_list = [package]

    await _setup_seventeentrack(hass)
    assert hass.states.get("sensor.seventeentrack_package_456") is not None
    assert len(hass.states.async_entity_ids()) == 1

    package2 = Package(
        tracking_number="789",
        destination_country=206,
        friendly_name="friendly name 2",
        info_text="info text 2",
        location="location 2",
        timestamp="2020-08-10 14:25",
        origin_country=206,
        package_type=2,
    )
    ProfileMock.package_list = [package, package2]

    await _goto_future(hass)

    assert hass.states.get("sensor.seventeentrack_package_789") is not None
    assert len(hass.states.async_entity_ids()) == 2


async def test_remove_package(hass):
    """Ensure entity is not there anymore if package is not there."""
    package1 = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
    )
    package2 = Package(
        tracking_number="789",
        destination_country=206,
        friendly_name="friendly name 2",
        info_text="info text 2",
        location="location 2",
        timestamp="2020-08-10 14:25",
        origin_country=206,
        package_type=2,
    )

    ProfileMock.package_list = [package1, package2]

    await _setup_seventeentrack(hass)

    assert hass.states.get("sensor.seventeentrack_package_456") is not None
    assert hass.states.get("sensor.seventeentrack_package_789") is not None
    assert len(hass.states.async_entity_ids()) == 2

    ProfileMock.package_list = [package2]

    await _goto_future(hass)

    assert hass.states.get("sensor.seventeentrack_package_456") is None
    assert hass.states.get("sensor.seventeentrack_package_789") is not None
    assert len(hass.states.async_entity_ids()) == 1


async def test_friendly_name_changed(hass):
    """Test friendly name change."""
    package = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
    )
    ProfileMock.package_list = [package]

    await _setup_seventeentrack(hass)

    assert hass.states.get("sensor.seventeentrack_package_456") is not None
    assert len(hass.states.async_entity_ids()) == 1

    package = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 2",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
    )
    ProfileMock.package_list = [package]

    await _goto_future(hass)

    assert hass.states.get("sensor.seventeentrack_package_456") is not None
    entity = hass.data["entity_components"]["sensor"].get_entity(
        "sensor.seventeentrack_package_456"
    )
    assert entity.name == "Seventeentrack Package: friendly name 2"
    assert len(hass.states.async_entity_ids()) == 1


async def test_delivered_not_shown(hass):
    """Ensure delivered packages are not shown."""
    package = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
        status=40,
    )
    ProfileMock.package_list = [package]

    with patch(
        "homeassistant.components.seventeentrack.sensor.persistent_notification"
    ) as persistent_notification_mock:
        await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED)
        await _goto_future(hass)

        assert not hass.states.async_entity_ids()
        persistent_notification_mock.create.assert_called()


async def test_delivered_shown(hass):
    """Ensure delivered packages are show when user choose to show them."""
    package = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
        status=40,
    )
    ProfileMock.package_list = [package]

    with patch(
        "homeassistant.components.seventeentrack.sensor.persistent_notification"
    ) as persistent_notification_mock:
        await _setup_seventeentrack(hass, VALID_CONFIG_FULL)

        assert hass.states.get("sensor.seventeentrack_package_456") is not None
        assert len(hass.states.async_entity_ids()) == 1
        persistent_notification_mock.create.assert_not_called()


async def test_becomes_delivered_not_shown_notification(hass):
    """Ensure notification is triggered when package becomes delivered."""
    package = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
    )
    ProfileMock.package_list = [package]

    await _setup_seventeentrack(hass, VALID_CONFIG_FULL_NO_DELIVERED)

    assert hass.states.get("sensor.seventeentrack_package_456") is not None
    assert len(hass.states.async_entity_ids()) == 1

    package_delivered = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
        status=40,
    )
    ProfileMock.package_list = [package_delivered]

    with patch(
        "homeassistant.components.seventeentrack.sensor.persistent_notification"
    ) as persistent_notification_mock:
        await _goto_future(hass)

        persistent_notification_mock.create.assert_called()
        assert not hass.states.async_entity_ids()


async def test_summary_correctly_updated(hass):
    """Ensure summary entities are not duplicated."""
    package = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
        status=30,
    )
    ProfileMock.package_list = [package]

    await _setup_seventeentrack(hass, summary_data=DEFAULT_SUMMARY)

    assert len(hass.states.async_entity_ids()) == 8
    for state in hass.states.async_all():
        if state.entity_id == "sensor.seventeentrack_package_456":
            break
        assert state.state == "0"

    assert (
        len(
            hass.states.get(
                "sensor.seventeentrack_packages_ready_to_be_picked_up"
            ).attributes["packages"]
        )
        == 1
    )

    ProfileMock.package_list = []
    ProfileMock.summary_data = NEW_SUMMARY_DATA

    await _goto_future(hass)

    assert len(hass.states.async_entity_ids()) == 7
    for state in hass.states.async_all():
        assert state.state == "1"

    assert (
        hass.states.get(
            "sensor.seventeentrack_packages_ready_to_be_picked_up"
        ).attributes["packages"]
        is None
    )


async def test_utc_timestamp(hass):
    """Ensure package timestamp is converted correctly from HA-defined time zone to UTC."""
    package = Package(
        tracking_number="456",
        destination_country=206,
        friendly_name="friendly name 1",
        info_text="info text 1",
        location="location 1",
        timestamp="2020-08-10 10:32",
        origin_country=206,
        package_type=2,
        tz="Asia/Jakarta",
    )
    ProfileMock.package_list = [package]

    await _setup_seventeentrack(hass)
    assert hass.states.get("sensor.seventeentrack_package_456") is not None
    assert len(hass.states.async_entity_ids()) == 1
    assert (
        str(
            hass.states.get("sensor.seventeentrack_package_456").attributes.get(
                "timestamp"
            )
        )
        == "2020-08-10 03:32:00+00:00"
    )