"""Test the PoolDose config flow."""

from datetime import timedelta
from typing import Any
from unittest.mock import AsyncMock

from freezegun.api import FrozenDateTimeFactory
import pytest

from homeassistant.components.pooldose.const import DOMAIN
from homeassistant.config_entries import SOURCE_DHCP, SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_MAC
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo

from .conftest import RequestStatus

from tests.common import MockConfigEntry, async_fire_time_changed


@pytest.mark.usefixtures("mock_setup_entry")
async def test_full_flow(hass: HomeAssistant) -> None:
    """Test the full config flow."""

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == "PoolDose TEST123456789"
    assert result["data"] == {CONF_HOST: "192.168.1.100"}
    assert result["result"].unique_id == "TEST123456789"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_device_unreachable(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock
) -> None:
    """Test that the form shows error when device is unreachable."""
    mock_pooldose_client.is_connected = False
    mock_pooldose_client.connect.return_value = RequestStatus.HOST_UNREACHABLE

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] == {"base": "cannot_connect"}

    mock_pooldose_client.is_connected = True
    mock_pooldose_client.connect.return_value = RequestStatus.SUCCESS

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY


@pytest.mark.usefixtures("mock_setup_entry")
async def test_api_version_unsupported(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock
) -> None:
    """Test that the form shows error when API version is unsupported."""
    mock_pooldose_client.check_apiversion_supported.return_value = (
        RequestStatus.API_VERSION_UNSUPPORTED,
        {"api_version_is": "v0.9", "api_version_should": "v1.0"},
    )

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] == {"base": "api_not_supported"}

    mock_pooldose_client.is_connected = True
    mock_pooldose_client.check_apiversion_supported.return_value = (
        RequestStatus.SUCCESS,
        {},
    )

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY


@pytest.mark.usefixtures("mock_setup_entry")
async def test_form_no_device_info(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock, device_info: dict[str, Any]
) -> None:
    """Test that the form shows error when device_info is None."""
    mock_pooldose_client.device_info = None

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] == {"base": "no_device_info"}

    mock_pooldose_client.device_info = device_info

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY


@pytest.mark.parametrize(
    ("client_status", "expected_error"),
    [
        (RequestStatus.HOST_UNREACHABLE, "cannot_connect"),
        (RequestStatus.PARAMS_FETCH_FAILED, "params_fetch_failed"),
        (RequestStatus.UNKNOWN_ERROR, "cannot_connect"),
    ],
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_connection_errors(
    hass: HomeAssistant,
    mock_pooldose_client: AsyncMock,
    client_status: str,
    expected_error: str,
) -> None:
    """Test that the form shows appropriate errors for various connection issues."""
    mock_pooldose_client.connect.return_value = client_status

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] == {"base": expected_error}

    mock_pooldose_client.connect.return_value = RequestStatus.SUCCESS

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY


@pytest.mark.usefixtures("mock_setup_entry")
async def test_api_no_data(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock
) -> None:
    """Test that the form shows error when API returns NO_DATA."""
    mock_pooldose_client.check_apiversion_supported.return_value = (
        RequestStatus.NO_DATA,
        {},
    )

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] == {"base": "api_not_set"}

    mock_pooldose_client.check_apiversion_supported.return_value = (
        RequestStatus.SUCCESS,
        {},
    )

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY


@pytest.mark.usefixtures("mock_setup_entry")
async def test_form_no_serial_number(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock, device_info: dict[str, Any]
) -> None:
    """Test that the form shows error when device_info has no serial number."""
    mock_pooldose_client.device_info = {"NAME": "Pool Device", "MODEL": "POOL DOSE"}

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["errors"] == {"base": "no_serial_number"}

    mock_pooldose_client.device_info = device_info

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY


async def test_duplicate_entry_aborts(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
    """Test that the flow aborts if the device is already configured."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "192.168.1.100"}
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "already_configured"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_flow(hass: HomeAssistant) -> None:
    """Test the full DHCP config flow."""

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            ip="192.168.0.123", hostname="kommspot", macaddress="a4e57caabbcc"
        ),
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "dhcp_confirm"

    result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == "PoolDose TEST123456789"
    assert result["data"][CONF_HOST] == "192.168.0.123"
    assert result["data"][CONF_MAC] == "a4e57caabbcc"
    assert result["result"].unique_id == "TEST123456789"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_no_serial_number(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock
) -> None:
    """Test that the DHCP flow aborts if no serial number is found."""
    mock_pooldose_client.device_info = {"NAME": "Pool Device", "MODEL": "POOL DOSE"}

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            ip="192.168.0.123", hostname="kommspot", macaddress="a4e57caabbcc"
        ),
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "no_serial_number"


@pytest.mark.parametrize(
    ("client_status"),
    [
        (RequestStatus.HOST_UNREACHABLE),
        (RequestStatus.PARAMS_FETCH_FAILED),
        (RequestStatus.UNKNOWN_ERROR),
    ],
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_connection_errors(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock, client_status: str
) -> None:
    """Test that the DHCP flow aborts on connection errors."""
    mock_pooldose_client.connect.return_value = client_status

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            ip="192.168.0.123", hostname="kommspot", macaddress="a4e57caabbcc"
        ),
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "no_serial_number"


@pytest.mark.parametrize(
    "api_status",
    [
        RequestStatus.NO_DATA,
        RequestStatus.API_VERSION_UNSUPPORTED,
    ],
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_api_errors(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock, api_status: str
) -> None:
    """Test that the DHCP flow aborts on API errors."""
    mock_pooldose_client.check_apiversion_supported.return_value = (api_status, {})

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            ip="192.168.0.123", hostname="kommspot", macaddress="a4e57caabbcc"
        ),
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "no_serial_number"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_updates_host(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
    """Test that DHCP discovery updates the host if it has changed."""
    mock_config_entry.add_to_hass(hass)

    # Verify initial host IP
    assert mock_config_entry.data[CONF_HOST] == "192.168.1.100"

    # Simulate DHCP discovery event with different IP
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            ip="192.168.0.123", hostname="kommspot", macaddress="a4e57caabbcc"
        ),
    )

    # Verify flow aborts as device is already configured
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "already_configured"

    assert mock_config_entry.data[CONF_HOST] == "192.168.0.123"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_adds_mac_if_not_present(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock
) -> None:
    """Test that DHCP flow adds MAC address if not already in config entry data."""
    # Create a config entry without MAC address
    entry = MockConfigEntry(
        domain=DOMAIN,
        unique_id="TEST123456789",
        data={CONF_HOST: "192.168.1.100"},
    )
    entry.add_to_hass(hass)

    # Verify initial state has no MAC
    assert CONF_MAC not in entry.data

    # Simulate DHCP discovery event
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            ip="192.168.0.123", hostname="kommspot", macaddress="a4e57caabbcc"
        ),
    )

    # Verify flow aborts as device is already configured
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "already_configured"

    # Verify MAC was added to the config entry
    assert entry.data[CONF_HOST] == "192.168.0.123"
    assert entry.data[CONF_MAC] == "a4e57caabbcc"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_dhcp_preserves_existing_mac(
    hass: HomeAssistant, mock_pooldose_client: AsyncMock
) -> None:
    """Test that DHCP flow preserves existing MAC in config entry data."""
    # Create a config entry with MAC address already set
    entry = MockConfigEntry(
        domain=DOMAIN,
        unique_id="TEST123456789",
        data={
            CONF_HOST: "192.168.1.100",
            CONF_MAC: "existing11aabb",  # Existing MAC that should be preserved
        },
    )
    entry.add_to_hass(hass)

    # Verify initial state has the expected MAC
    assert entry.data[CONF_MAC] == "existing11aabb"

    # Simulate DHCP discovery event with different MAC
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_DHCP},
        data=DhcpServiceInfo(
            ip="192.168.0.123", hostname="kommspot", macaddress="different22ccdd"
        ),
    )

    # Verify flow aborts as device is already configured
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "already_configured"

    # Verify MAC in config entry was NOT updated (original MAC preserved)
    assert entry.data[CONF_HOST] == "192.168.0.123"  # IP was updated
    assert entry.data[CONF_MAC] == "existing11aabb"  # MAC remains unchanged
    assert entry.data[CONF_MAC] != "different22ccdd"  # Not updated to new MAC


async def _start_reconfigure_flow(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry, host_ip: str
) -> Any:
    """Initialize a reconfigure flow for PoolDose and submit new host."""
    mock_config_entry.add_to_hass(hass)

    reconfigure_result = await mock_config_entry.start_reconfigure_flow(hass)

    assert reconfigure_result["type"] is FlowResultType.FORM
    assert reconfigure_result["step_id"] == "reconfigure"

    return await hass.config_entries.flow.async_configure(
        reconfigure_result["flow_id"], {CONF_HOST: host_ip}
    )


@pytest.mark.usefixtures("mock_setup_entry")
async def test_reconfigure_flow_success(
    hass: HomeAssistant,
    mock_pooldose_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test successful reconfigure updates host and reloads entry."""
    # Ensure the mocked device returns the same serial number as the
    # config entry so the reconfigure flow matches the device
    mock_pooldose_client.device_info = {"SERIAL_NUMBER": mock_config_entry.unique_id}

    result = await _start_reconfigure_flow(hass, mock_config_entry, "192.168.0.200")

    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "reconfigure_successful"

    # Config entry should have updated host
    assert mock_config_entry.data.get(CONF_HOST) == "192.168.0.200"

    freezer.tick(timedelta(seconds=5))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    # Config entry should have updated host
    entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
    assert entry is not None
    assert entry.data.get(CONF_HOST) == "192.168.0.200"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_reconfigure_flow_cannot_connect(
    hass: HomeAssistant,
    mock_pooldose_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test reconfigure shows cannot_connect when device unreachable."""
    mock_pooldose_client.connect.return_value = RequestStatus.HOST_UNREACHABLE

    result = await _start_reconfigure_flow(hass, mock_config_entry, "192.168.0.200")

    assert result["type"] is FlowResultType.FORM
    assert result["errors"] == {"base": "cannot_connect"}


@pytest.mark.usefixtures("mock_setup_entry")
async def test_reconfigure_flow_wrong_device(
    hass: HomeAssistant,
    mock_pooldose_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test reconfigure aborts when serial number doesn't match existing entry."""
    # Return device info with different serial number
    mock_pooldose_client.device_info = {"SERIAL_NUMBER": "OTHER123"}

    result = await _start_reconfigure_flow(hass, mock_config_entry, "192.168.0.200")

    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "wrong_device"
