"""Test for the smhi weather entity."""

from datetime import datetime, timedelta
from unittest.mock import MagicMock

from freezegun.api import FrozenDateTimeFactory
from pysmhi import (
    SMHIFirePointForecast,
    SMHIForecast,
    SmhiForecastException,
    SMHIPointForecast,
)
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.smhi.const import DOMAIN
from homeassistant.components.smhi.weather import (
    ATTR_SMHI_THUNDER_PROBABILITY,
    CONDITION_CLASSES,
)
from homeassistant.components.weather import (
    ATTR_CONDITION_CLEAR_NIGHT,
    ATTR_FORECAST_CONDITION,
    ATTR_WEATHER_WIND_GUST_SPEED,
    ATTR_WEATHER_WIND_SPEED_UNIT,
    DOMAIN as WEATHER_DOMAIN,
    SERVICE_GET_FORECASTS,
)
from homeassistant.const import (
    ATTR_ATTRIBUTION,
    STATE_UNAVAILABLE,
    STATE_UNKNOWN,
    Platform,
    UnitOfSpeed,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util import dt as dt_util

from . import ENTITY_ID, TEST_CONFIG

from tests.common import MockConfigEntry, async_fire_time_changed
from tests.typing import WebSocketGenerator


@pytest.mark.parametrize(
    "load_platforms",
    [[Platform.WEATHER]],
)
async def test_setup_hass(
    hass: HomeAssistant,
    load_int: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test for successfully setting up the smhi integration."""

    state = hass.states.get(ENTITY_ID)

    assert state
    assert state.state == "cloudy"
    assert state.attributes == snapshot


@pytest.mark.parametrize(
    "to_load",
    [1],
)
@pytest.mark.freeze_time(datetime(2026, 4, 3, 1, tzinfo=dt_util.UTC))
async def test_clear_night(
    hass: HomeAssistant,
    mock_client: SMHIPointForecast,
    mock_fire_client: SMHIFirePointForecast,
    snapshot: SnapshotAssertion,
) -> None:
    """Test for successfully setting up the smhi integration."""
    hass.config.latitude = "59.32624"
    hass.config.longitude = "17.84197"
    config_entry = MockConfigEntry(
        domain=DOMAIN,
        data=TEST_CONFIG,
        entry_id="01JMZDH8N5PFHGJNYKKYCSCWER",
        unique_id="59.32624-17.84197",
        version=3,
        title="Test",
    )
    config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(config_entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)

    assert state
    assert state.state == ATTR_CONDITION_CLEAR_NIGHT
    assert state.attributes == snapshot(name="clear_night")

    response = await hass.services.async_call(
        WEATHER_DOMAIN,
        SERVICE_GET_FORECASTS,
        {"entity_id": ENTITY_ID, "type": "hourly"},
        blocking=True,
        return_response=True,
    )
    assert response == snapshot(name="clear-night_forecast")


async def test_properties_no_data(
    hass: HomeAssistant,
    load_int: MockConfigEntry,
    mock_client: MagicMock,
    mock_fire_client: SMHIFirePointForecast,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test properties when no API data available."""

    mock_client.async_get_daily_forecast.side_effect = SmhiForecastException("boom")
    freezer.tick(timedelta(minutes=35))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)

    assert state
    assert state.name == "Test"
    assert state.state == STATE_UNAVAILABLE
    assert state.attributes[ATTR_ATTRIBUTION] == "Swedish weather institute (SMHI)"

    mock_client.async_get_daily_forecast.side_effect = None
    mock_client.async_get_daily_forecast.return_value = None
    freezer.tick(timedelta(minutes=35))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)

    assert state
    assert state.name == "Test"
    assert state.state == "cloudy"
    assert ATTR_SMHI_THUNDER_PROBABILITY not in state.attributes
    assert state.attributes[ATTR_ATTRIBUTION] == "Swedish weather institute (SMHI)"


async def test_properties_unknown_symbol(
    hass: HomeAssistant,
    mock_client: SMHIPointForecast,
    mock_fire_client: SMHIFirePointForecast,
) -> None:
    """Test behaviour when unknown symbol from API."""
    data = SMHIForecast(
        frozen_precipitation=0,
        high_cloud=100,
        humidity=96,
        low_cloud=100,
        max_precipitation=0.0,
        mean_precipitation=0.0,
        median_precipitation=0.0,
        medium_cloud=75,
        min_precipitation=0.0,
        precipitation_category=0,
        pressure=1018.9,
        symbol=100,  # Faulty symbol
        temperature=1.0,
        temperature_max=1.0,
        temperature_min=1.0,
        thunder=0,
        total_cloud=100,
        valid_time=datetime(2018, 1, 1, 0, 0, 0),
        visibility=8.8,
        wind_direction=114,
        wind_gust=5.8,
        wind_speed=2.5,
    )
    data2 = SMHIForecast(
        frozen_precipitation=0,
        high_cloud=100,
        humidity=96,
        low_cloud=100,
        max_precipitation=0.0,
        mean_precipitation=0.0,
        median_precipitation=0.0,
        medium_cloud=75,
        min_precipitation=0.0,
        precipitation_category=0,
        pressure=1018.9,
        symbol=100,  # Faulty symbol
        temperature=1.0,
        temperature_max=1.0,
        temperature_min=1.0,
        thunder=0,
        total_cloud=100,
        valid_time=datetime(2018, 1, 1, 12, 0, 0),
        visibility=8.8,
        wind_direction=114,
        wind_gust=5.8,
        wind_speed=2.5,
    )
    data3 = SMHIForecast(
        frozen_precipitation=0,
        high_cloud=100,
        humidity=96,
        low_cloud=100,
        max_precipitation=0.0,
        mean_precipitation=0.0,
        median_precipitation=0.0,
        medium_cloud=75,
        min_precipitation=0.0,
        precipitation_category=0,
        pressure=1018.9,
        symbol=100,  # Faulty symbol
        temperature=1.0,
        temperature_max=1.0,
        temperature_min=1.0,
        thunder=0,
        total_cloud=100,
        valid_time=datetime(2018, 1, 2, 0, 0, 0),
        visibility=8.8,
        wind_direction=114,
        wind_gust=5.8,
        wind_speed=2.5,
    )

    testdata = [data, data2, data3]

    mock_client.async_get_daily_forecast.return_value = testdata

    entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3)
    entry.add_to_hass(hass)

    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)

    assert state
    assert state.name == "test"
    assert state.state == STATE_UNKNOWN
    response = await hass.services.async_call(
        WEATHER_DOMAIN,
        SERVICE_GET_FORECASTS,
        {"entity_id": ENTITY_ID, "type": "daily"},
        blocking=True,
        return_response=True,
    )
    assert all(
        forecast[ATTR_FORECAST_CONDITION] is None
        for forecast in response[ENTITY_ID]["forecast"]
    )


@pytest.mark.parametrize("error", [SmhiForecastException(), TimeoutError()])
async def test_refresh_weather_forecast_retry(
    hass: HomeAssistant,
    error: Exception,
    load_int: MockConfigEntry,
    mock_client: SMHIPointForecast,
    mock_fire_client: SMHIFirePointForecast,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test the refresh weather forecast function."""

    mock_client.async_get_daily_forecast.side_effect = error

    freezer.tick(timedelta(minutes=35))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)

    assert state
    assert state.name == "Test"
    assert state.state == STATE_UNAVAILABLE
    assert mock_client.async_get_daily_forecast.call_count == 2

    freezer.tick(timedelta(minutes=35))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)
    assert state
    assert state.state == STATE_UNAVAILABLE
    assert mock_client.async_get_daily_forecast.call_count == 3


def test_condition_class() -> None:
    """Test condition class."""

    def get_condition(index: int) -> str:
        """Return condition given index."""
        return [k for k, v in CONDITION_CLASSES.items() if index in v][0]

    # SMHI definitions as follows, see
    # http://opendata.smhi.se/apidocs/metfcst/parameters.html

    # 1. Clear sky
    assert get_condition(1) == "sunny"
    # 2. Nearly clear sky
    assert get_condition(2) == "sunny"
    # 3. Variable cloudiness
    assert get_condition(3) == "partlycloudy"
    # 4. Halfclear sky
    assert get_condition(4) == "partlycloudy"
    # 5. Cloudy sky
    assert get_condition(5) == "cloudy"
    # 6. Overcast
    assert get_condition(6) == "cloudy"
    # 7. Fog
    assert get_condition(7) == "fog"
    # 8. Light rain showers
    assert get_condition(8) == "rainy"
    # 9. Moderate rain showers
    assert get_condition(9) == "rainy"
    # 18. Light rain
    assert get_condition(18) == "rainy"
    # 19. Moderate rain
    assert get_condition(19) == "rainy"
    # 10. Heavy rain showers
    assert get_condition(10) == "pouring"
    # 20. Heavy rain
    assert get_condition(20) == "pouring"
    # 21. Thunder
    assert get_condition(21) == "lightning"
    # 11. Thunderstorm
    assert get_condition(11) == "lightning-rainy"
    # 15. Light snow showers
    assert get_condition(15) == "snowy"
    # 16. Moderate snow showers
    assert get_condition(16) == "snowy"
    # 17. Heavy snow showers
    assert get_condition(17) == "snowy"
    # 25. Light snowfall
    assert get_condition(25) == "snowy"
    # 26. Moderate snowfall
    assert get_condition(26) == "snowy"
    # 27. Heavy snowfall
    assert get_condition(27) == "snowy"
    # 12. Light sleet showers
    assert get_condition(12) == "snowy-rainy"
    # 13. Moderate sleet showers
    assert get_condition(13) == "snowy-rainy"
    # 14. Heavy sleet showers
    assert get_condition(14) == "snowy-rainy"
    # 22. Light sleet
    assert get_condition(22) == "snowy-rainy"
    # 23. Moderate sleet
    assert get_condition(23) == "snowy-rainy"
    # 24. Heavy sleet
    assert get_condition(24) == "snowy-rainy"


async def test_custom_speed_unit(
    hass: HomeAssistant,
    entity_registry: er.EntityRegistry,
    load_int: MockConfigEntry,
) -> None:
    """Test Wind Gust speed with custom unit."""
    state = hass.states.get(ENTITY_ID)

    assert state
    assert state.name == "Test"
    assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 24.48

    entity_registry.async_update_entity_options(
        state.entity_id,
        WEATHER_DOMAIN,
        {ATTR_WEATHER_WIND_SPEED_UNIT: UnitOfSpeed.METERS_PER_SECOND},
    )

    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)
    assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 6.8


@pytest.mark.parametrize(
    "load_platforms",
    [[Platform.WEATHER]],
)
async def test_forecast_services(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    load_int: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test multiple forecast."""
    client = await hass_ws_client(hass)

    await client.send_json_auto_id(
        {
            "type": "weather/subscribe_forecast",
            "forecast_type": "daily",
            "entity_id": ENTITY_ID,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] is None
    subscription_id = msg["id"]

    msg = await client.receive_json()
    assert msg["id"] == subscription_id
    assert msg["type"] == "event"
    forecast1 = msg["event"]["forecast"]

    assert len(forecast1) == 11
    assert forecast1[0] == snapshot
    assert forecast1[6] == snapshot

    await client.send_json_auto_id(
        {
            "type": "weather/subscribe_forecast",
            "forecast_type": "hourly",
            "entity_id": ENTITY_ID,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] is None
    subscription_id = msg["id"]

    msg = await client.receive_json()
    assert msg["id"] == subscription_id
    assert msg["type"] == "event"
    forecast1 = msg["event"]["forecast"]

    assert len(forecast1) == 59
    assert forecast1[0] == snapshot
    assert forecast1[6] == snapshot


@pytest.mark.parametrize(
    "load_platforms",
    [[Platform.WEATHER]],
)
@pytest.mark.parametrize(
    "to_load",
    [2],
)
async def test_forecast_services_lack_of_data(
    hass: HomeAssistant,
    hass_ws_client: WebSocketGenerator,
    load_int: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test forecast lacking data."""
    client = await hass_ws_client(hass)

    await client.send_json_auto_id(
        {
            "type": "weather/subscribe_forecast",
            "forecast_type": "daily",
            "entity_id": ENTITY_ID,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    assert msg["result"] is None
    subscription_id = msg["id"]

    msg = await client.receive_json()
    assert msg["id"] == subscription_id
    assert msg["type"] == "event"
    forecast1 = msg["event"]["forecast"]

    assert forecast1 is None


@pytest.mark.parametrize(
    "load_platforms",
    [[Platform.WEATHER]],
)
async def test_forecast_service(
    hass: HomeAssistant,
    load_int: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test forecast service."""
    response = await hass.services.async_call(
        WEATHER_DOMAIN,
        SERVICE_GET_FORECASTS,
        {"entity_id": ENTITY_ID, "type": "daily"},
        blocking=True,
        return_response=True,
    )
    assert response == snapshot


@pytest.mark.parametrize(
    "load_platforms",
    [[Platform.WEATHER]],
)
async def test_twice_daily_forecast_service(
    hass: HomeAssistant,
    load_int: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test forecast service."""
    response = await hass.services.async_call(
        WEATHER_DOMAIN,
        SERVICE_GET_FORECASTS,
        {"entity_id": ENTITY_ID, "type": "twice_daily"},
        blocking=True,
        return_response=True,
    )
    assert response == snapshot
