"""Test the Victron Bluetooth Low Energy config flow."""

from unittest.mock import AsyncMock, patch

from home_assistant_bluetooth import BluetoothServiceInfo
import pytest

from homeassistant import config_entries
from homeassistant.components.victron_ble.const import DOMAIN
from homeassistant.config_entries import SOURCE_BLUETOOTH, SOURCE_USER
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_ADDRESS
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType

from .fixtures import (
    NOT_VICTRON_SERVICE_INFO,
    VICTRON_INVERTER_RS_SERVICE_INFO,
    VICTRON_TEST_WRONG_TOKEN,
    VICTRON_VEBUS_SERVICE_INFO,
    VICTRON_VEBUS_TOKEN,
)

from tests.common import MockConfigEntry


@pytest.fixture(autouse=True)
def mock_bluetooth(enable_bluetooth: None) -> None:
    """Mock bluetooth for all tests in this module."""


@pytest.mark.usefixtures("mock_setup_entry")
async def test_async_step_bluetooth_valid_device(hass: HomeAssistant) -> None:
    """Test discovery via bluetooth with a valid device."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_BLUETOOTH},
        data=VICTRON_VEBUS_SERVICE_INFO,
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "access_token"

    # test valid access token
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={CONF_ACCESS_TOKEN: VICTRON_VEBUS_TOKEN},
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == VICTRON_VEBUS_SERVICE_INFO.name
    flow_result = result["result"]
    assert flow_result is not None
    assert flow_result.unique_id == VICTRON_VEBUS_SERVICE_INFO.address
    assert flow_result.data == {
        CONF_ACCESS_TOKEN: VICTRON_VEBUS_TOKEN,
    }
    assert set(flow_result.data.keys()) == {CONF_ACCESS_TOKEN}


@pytest.mark.usefixtures("mock_setup_entry")
async def test_async_step_bluetooth_invalid_key_retry(hass: HomeAssistant) -> None:
    """Test wrong key via bluetooth discovery shows error and allows retry."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_BLUETOOTH},
        data=VICTRON_VEBUS_SERVICE_INFO,
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "access_token"

    # enter wrong key
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={CONF_ACCESS_TOKEN: VICTRON_TEST_WRONG_TOKEN},
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "access_token"
    assert result["errors"] == {"base": "invalid_access_token"}

    # retry with correct key
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={CONF_ACCESS_TOKEN: VICTRON_VEBUS_TOKEN},
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == VICTRON_VEBUS_SERVICE_INFO.name


@pytest.mark.parametrize(
    ("source", "service_info", "expected_reason"),
    [
        (
            SOURCE_BLUETOOTH,
            NOT_VICTRON_SERVICE_INFO,
            "not_supported",
        ),
        (
            SOURCE_BLUETOOTH,
            VICTRON_INVERTER_RS_SERVICE_INFO,
            "not_supported",
        ),
        (
            SOURCE_USER,
            None,
            "no_devices_found",
        ),
    ],
    ids=["bluetooth_not_victron", "bluetooth_unsupported_device", "user_no_devices"],
)
async def test_abort_scenarios(
    hass: HomeAssistant,
    source: str,
    service_info: BluetoothServiceInfo | None,
    expected_reason: str,
) -> None:
    """Test flows that result in abort."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": source},
        data=service_info,
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == expected_reason


@pytest.mark.usefixtures("mock_setup_entry")
async def test_async_step_user_with_devices_found(
    hass: HomeAssistant, mock_discovered_service_info: AsyncMock
) -> None:
    """Test setup from service info cache with devices found."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_USER},
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={CONF_ADDRESS: VICTRON_VEBUS_SERVICE_INFO.address},
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "access_token"

    # test invalid access token shows error and allows retry
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], user_input={CONF_ACCESS_TOKEN: VICTRON_TEST_WRONG_TOKEN}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "access_token"
    assert result["errors"] == {"base": "invalid_access_token"}

    # test retry with valid access token succeeds
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={CONF_ACCESS_TOKEN: VICTRON_VEBUS_TOKEN},
    )
    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == VICTRON_VEBUS_SERVICE_INFO.name


async def test_async_step_user_device_added_between_steps(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
    mock_discovered_service_info: AsyncMock,
) -> None:
    """Test abort when the device gets added via another flow between steps."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_USER},
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "user"

    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={"address": VICTRON_VEBUS_SERVICE_INFO.address},
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "already_configured"


async def test_async_step_user_with_found_devices_already_setup(
    hass: HomeAssistant,
    mock_config_entry_added_to_hass: MockConfigEntry,
    mock_discovered_service_info: AsyncMock,
) -> None:
    """Test setup from service info cache with devices found."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_USER},
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "no_devices_found"


async def test_async_step_bluetooth_devices_already_setup(
    hass: HomeAssistant, mock_config_entry_added_to_hass: MockConfigEntry
) -> None:
    """Test we can't start a flow if there is already a config entry."""

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_BLUETOOTH},
        data=VICTRON_VEBUS_SERVICE_INFO,
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "already_configured"


async def test_async_step_bluetooth_already_in_progress(hass: HomeAssistant) -> None:
    """Test we can't start a flow for the same device twice."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_BLUETOOTH},
        data=VICTRON_VEBUS_SERVICE_INFO,
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "access_token"

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_BLUETOOTH},
        data=VICTRON_VEBUS_SERVICE_INFO,
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "already_in_progress"


async def test_async_step_reauth_valid_key(
    hass: HomeAssistant,
    mock_discovered_service_info: AsyncMock,
) -> None:
    """Test reauth flow with a valid new key."""
    entry = MockConfigEntry(
        domain=DOMAIN,
        data={
            CONF_ADDRESS: VICTRON_VEBUS_SERVICE_INFO.address,
            CONF_ACCESS_TOKEN: VICTRON_TEST_WRONG_TOKEN,
        },
        unique_id=VICTRON_VEBUS_SERVICE_INFO.address,
    )
    entry.add_to_hass(hass)

    result = await entry.start_reauth_flow(hass)
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reauth_confirm"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={CONF_ACCESS_TOKEN: VICTRON_VEBUS_TOKEN},
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "reauth_successful"
    assert entry.data[CONF_ACCESS_TOKEN] == VICTRON_VEBUS_TOKEN


async def test_async_step_reauth_invalid_key(
    hass: HomeAssistant,
    mock_config_entry_added_to_hass: MockConfigEntry,
    mock_discovered_service_info: AsyncMock,
) -> None:
    """Test reauth flow with an invalid key shows error and allows retry."""
    result = await mock_config_entry_added_to_hass.start_reauth_flow(hass)
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reauth_confirm"

    # Submit wrong key
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={CONF_ACCESS_TOKEN: VICTRON_TEST_WRONG_TOKEN},
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "reauth_confirm"
    assert result["errors"] == {"base": "invalid_access_token"}

    # Now submit correct key
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        user_input={CONF_ACCESS_TOKEN: VICTRON_VEBUS_TOKEN},
    )
    assert result["type"] is FlowResultType.ABORT
    assert result["reason"] == "reauth_successful"


async def test_async_step_reauth_device_not_found(
    hass: HomeAssistant,
    mock_config_entry_added_to_hass: MockConfigEntry,
) -> None:
    """Test reauth flow when device is not currently broadcasting."""
    # No mock_discovered_service_info, so no devices will be found
    with patch(
        "homeassistant.components.victron_ble.config_flow.async_discovered_service_info",
        return_value=[],
    ):
        result = await mock_config_entry_added_to_hass.start_reauth_flow(hass)
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "reauth_confirm"

        result = await hass.config_entries.flow.async_configure(
            result["flow_id"],
            user_input={CONF_ACCESS_TOKEN: VICTRON_VEBUS_TOKEN},
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "reauth_confirm"
        assert result["errors"] == {"base": "no_devices_found"}


async def test_reauth_flow_sets_title_placeholders(
    hass: HomeAssistant,
    mock_config_entry_added_to_hass: MockConfigEntry,
    mock_discovered_service_info: AsyncMock,
) -> None:
    """Test that reauth flow has title_placeholders set for flow_title rendering.

    Regression test for https://github.com/home-assistant/core/issues/167105 where
    the flow_title used '{title}' but HA only automatically provides 'name' in
    title_placeholders for reauth flows, causing a frontend MISSING_VALUE error.
    """
    await mock_config_entry_added_to_hass.start_reauth_flow(hass)

    flows = hass.config_entries.flow.async_progress()
    assert len(flows) == 1
    assert flows[0]["context"]["title_placeholders"]["name"] == (
        mock_config_entry_added_to_hass.title
    )
