"""Tests for the Vistapool number platform."""

from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, patch

from aioaquarite import AquariteError
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.number import (
    ATTR_VALUE,
    DOMAIN as NUMBER_DOMAIN,
    SERVICE_SET_VALUE,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er

from tests.common import MockConfigEntry, snapshot_platform


@pytest.fixture(autouse=True)
def _only_number_platform() -> Generator[None]:
    """Restrict integration setup to the number platform for these tests."""
    with patch("homeassistant.components.vistapool.PLATFORMS", [Platform.NUMBER]):
        yield


async def test_all_entities(
    hass: HomeAssistant,
    snapshot: SnapshotAssertion,
    entity_registry: er.EntityRegistry,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
    mock_pool_data: dict[str, Any],
) -> None:
    """Test number entities for the default fixture."""
    mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)


async def test_number_scales_ph_value(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
    mock_pool_data: dict[str, Any],
) -> None:
    """Test pH numbers divide the stored hundredths by 100 on read."""
    mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    # Fixture stores low/high pH as the strings "650" / "751" (hundredths).
    assert hass.states.get("number.my_pool_ph_minimum").state == "6.5"
    assert hass.states.get("number.my_pool_ph_maximum").state == "7.51"


async def test_number_scales_electrolysis_value(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
) -> None:
    """Test electrolysis setpoint divides the stored tenths by 10 on read."""
    mock_vistapool_client.fetch_pool_data.return_value = {
        "main": {"hasHidro": 1, "version": 1},
        "hidro": {"level": 50, "maxAllowedValue": 220},
    }
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get("number.my_pool_electrolysis_setpoint")
    assert state.state == "5.0"
    assert state.attributes["max"] == 22.0


@pytest.mark.parametrize(
    "hidro_data",
    [
        pytest.param({"level": 50}, id="missing_max"),
        pytest.param({"level": 50, "maxAllowedValue": "garbage"}, id="non_numeric_max"),
        pytest.param({"level": 50, "maxAllowedValue": {}}, id="non_scalar_max"),
    ],
)
async def test_number_electrolysis_max_fallback(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
    hidro_data: dict[str, Any],
) -> None:
    """Test electrolysis setpoint max falls back to 50 when the API value is missing or unparsable."""
    mock_vistapool_client.fetch_pool_data.return_value = {
        "main": {"hasHidro": 1, "version": 1},
        "hidro": hidro_data,
    }
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get("number.my_pool_electrolysis_setpoint")
    assert state is not None
    assert state.attributes["max"] == 50.0


async def test_number_hydrolysis_setpoint_branch(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
) -> None:
    """Test the hydrolysis (non-electrolysis) branch creates the right entity."""
    mock_vistapool_client.fetch_pool_data.return_value = {
        "main": {"hasHidro": 1, "version": 1},
        "hidro": {"is_electrolysis": False, "level": 200},
    }
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert hass.states.get("number.my_pool_hydrolysis_setpoint") is not None
    assert hass.states.get("number.my_pool_electrolysis_setpoint") is None


async def test_number_heating_requires_has_heat(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
) -> None:
    """Test heating min/max are only created when filtration.hasHeat is set."""
    mock_vistapool_client.fetch_pool_data.return_value = {
        "main": {"version": 1},
        "filtration": {"hasHeat": 1, "heating": {"temp": 18, "tempHi": 28}},
    }
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert hass.states.get("number.my_pool_heating_minimum_temperature") is not None
    assert hass.states.get("number.my_pool_heating_maximum_temperature") is not None
    assert hass.states.get("number.my_pool_smart_minimum_temperature") is None


async def test_number_smart_requires_has_smart(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
) -> None:
    """Test smart min/max are only created when filtration.hasSmart is set."""
    mock_vistapool_client.fetch_pool_data.return_value = {
        "main": {"version": 1},
        "filtration": {"hasSmart": 1, "smart": {"tempMin": 18, "tempHigh": 28}},
    }
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    assert hass.states.get("number.my_pool_smart_minimum_temperature") is not None
    assert hass.states.get("number.my_pool_smart_maximum_temperature") is not None
    assert hass.states.get("number.my_pool_heating_minimum_temperature") is None


@pytest.mark.parametrize(
    ("entity_id", "user_value", "expected_path", "expected_raw"),
    [
        pytest.param(
            "number.my_pool_redox_setpoint",
            720,
            "modules.rx.status.value",
            720,
            id="redox_unscaled",
        ),
        pytest.param(
            "number.my_pool_ph_minimum",
            7.2,
            "modules.ph.status.low_value",
            720,
            id="ph_minimum_scaled_x100",
        ),
        pytest.param(
            "number.my_pool_ph_maximum",
            7.45,
            "modules.ph.status.high_value",
            745,
            id="ph_maximum_scaled_x100",
        ),
        pytest.param(
            "number.my_pool_intel_temperature",
            24,
            "filtration.intel.temp",
            24,
            id="intel_temp_unscaled",
        ),
    ],
)
async def test_number_set_value(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
    mock_pool_data: dict[str, Any],
    entity_id: str,
    user_value: float,
    expected_path: str,
    expected_raw: int,
) -> None:
    """Test set_value writes the scaled raw value to the library at the right path."""
    mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    await hass.services.async_call(
        NUMBER_DOMAIN,
        SERVICE_SET_VALUE,
        {ATTR_ENTITY_ID: entity_id, ATTR_VALUE: user_value},
        blocking=True,
    )

    mock_vistapool_client.set_value.assert_awaited_once_with(
        "ABCDEF1234567890", expected_path, expected_raw
    )
    assert hass.states.get(entity_id).state == str(float(user_value))
    value_arg = mock_vistapool_client.set_value.await_args.args[2]
    assert isinstance(value_arg, int)


async def test_number_set_value_raises_on_api_error(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_vistapool_client: AsyncMock,
    mock_pool_data: dict[str, Any],
) -> None:
    """Test set_value re-raises as HomeAssistantError when the library fails."""
    mock_vistapool_client.fetch_pool_data.return_value = mock_pool_data
    mock_vistapool_client.set_value.side_effect = AquariteError("boom")
    mock_config_entry.add_to_hass(hass)

    assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    with pytest.raises(HomeAssistantError) as excinfo:
        await hass.services.async_call(
            NUMBER_DOMAIN,
            SERVICE_SET_VALUE,
            {ATTR_ENTITY_ID: "number.my_pool_redox_setpoint", ATTR_VALUE: 700},
            blocking=True,
        )
    assert excinfo.value.translation_key == "set_failed"
