"""Test the Squeezebox config flow."""

from http import HTTPStatus
from typing import Any
from unittest.mock import AsyncMock, MagicMock

import pytest

from homeassistant import config_entries
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.components.squeezebox.const import (
    CONF_BROWSE_LIMIT,
    CONF_SERVER_LIST,
    CONF_VOLUME_STEP,
    DOMAIN,
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import entity_registry as er

from .conftest import SERVER_UUIDS

from tests.common import MockConfigEntry

# Use the same UUIDs defined in the conftest
TEST_UUID = SERVER_UUIDS[0]


async def test_manual_setup(
    hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
    """Test we can finish a manual setup successfully."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    assert result["type"] is FlowResultType.MENU
    assert result["step_id"] == "user"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "edit"}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "edit"

    mock_server.async_query.return_value = {"uuid": TEST_UUID}

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["result"].unique_id == TEST_UUID
    assert result["title"] == "1.2.3.4"
    assert result["data"][CONF_HOST] == "1.2.3.4"
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.parametrize(
    ("query_return", "http_status", "expected_error"),
    [
        (False, HTTPStatus.UNAUTHORIZED, "invalid_auth"),
        (False, HTTPStatus.NOT_FOUND, "cannot_connect"),
        ({"no_uuid": True}, HTTPStatus.OK, "missing_uuid"),
    ],
)
async def test_manual_setup_data_errors(
    hass: HomeAssistant,
    mock_setup_entry: AsyncMock,
    mock_server: AsyncMock,
    query_return: Any,
    http_status: HTTPStatus,
    expected_error: str,
) -> None:
    """Test data-driven error states during manual setup and recovery."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "edit"}
    )

    mock_server.async_query.return_value = query_return
    mock_server.http_status = http_status

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "1.2.3.4", CONF_PORT: 9000}
    )

    assert result["type"] is FlowResultType.FORM
    assert result["errors"]["base"] == expected_error

    mock_server.async_query.return_value = {"uuid": TEST_UUID}
    mock_server.http_status = HTTPStatus.OK

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {
            CONF_HOST: "1.2.3.4",
            CONF_PORT: 9000,
            CONF_USERNAME: "admin",
            CONF_PASSWORD: "password",
        },
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["result"].unique_id == TEST_UUID
    assert len(mock_setup_entry.mock_calls) == 1


async def test_manual_setup_exception_error(
    hass: HomeAssistant,
    mock_setup_entry: AsyncMock,
    mock_server: AsyncMock,
) -> None:
    """Test exception error state during manual setup and recovery."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "edit"}
    )

    mock_server.async_query.side_effect = Exception("Connection fail")

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_HOST: "1.2.3.4", CONF_PORT: 9000}
    )

    assert result["type"] is FlowResultType.FORM
    assert result["errors"]["base"] == "unknown"

    mock_server.async_query.side_effect = None
    mock_server.async_query.return_value = {"uuid": TEST_UUID}
    mock_server.http_status = HTTPStatus.OK

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {
            CONF_HOST: "1.2.3.4",
            CONF_PORT: 9000,
            CONF_USERNAME: "admin",
            CONF_PASSWORD: "password",
        },
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["result"].unique_id == TEST_UUID
    assert len(mock_setup_entry.mock_calls) == 1


async def test_manual_setup_recovery(
    hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
    """Test manual setup error recovery."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "edit"}
    )

    mock_server.async_query.return_value = False
    mock_server.http_status = HTTPStatus.UNAUTHORIZED

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
    )

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "edit"
    assert result["errors"]["base"] == "invalid_auth"

    mock_server.async_query.return_value = {"uuid": TEST_UUID}
    mock_server.http_status = HTTPStatus.OK

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {
            CONF_HOST: "1.2.3.4",
            CONF_PORT: 9000,
            CONF_USERNAME: "admin",
            CONF_PASSWORD: "correct_password",
        },
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["result"].unique_id == TEST_UUID
    assert result["title"] == "1.2.3.4"
    assert result["data"][CONF_HOST] == "1.2.3.4"
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.usefixtures("mock_setup_entry")
async def test_duplicate_setup(
    hass: HomeAssistant, mock_server: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
    """Test abort if setting up an already configured server."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "edit"}
    )

    mock_server.async_query.return_value = {"uuid": TEST_UUID}

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"],
        {CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
    )

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


async def test_discovery_flow_success(
    hass: HomeAssistant,
    mock_setup_entry: AsyncMock,
    mock_server: AsyncMock,
    mock_discover: MagicMock,
) -> None:
    """Test discovery flow where default connect succeeds immediately."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "start_discovery"}
    )

    assert result["type"] is FlowResultType.SHOW_PROGRESS
    assert result["step_id"] == "start_discovery"

    await hass.async_block_till_done()

    result = await hass.config_entries.flow.async_configure(result["flow_id"])
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "choose_server"

    mock_server.async_query.return_value = {"uuid": TEST_UUID}

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_SERVER_LIST: "1.1.1.1"}
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["result"].unique_id == TEST_UUID
    assert result["title"] == "1.1.1.1"
    assert len(mock_setup_entry.mock_calls) == 1


async def test_discovery_flow_edit_discovered_success(
    hass: HomeAssistant,
    mock_setup_entry: AsyncMock,
    mock_server: AsyncMock,
    mock_discover: MagicMock,
) -> None:
    """Test the successful outcome of the edit_discovered step."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "start_discovery"}
    )
    await hass.async_block_till_done()
    result = await hass.config_entries.flow.async_configure(result["flow_id"])

    mock_server.http_status = HTTPStatus.UNAUTHORIZED
    mock_server.async_query.return_value = False

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_SERVER_LIST: "1.1.1.1"}
    )

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "edit_discovered"

    mock_server.http_status = HTTPStatus.OK
    mock_server.async_query.side_effect = [False, {"uuid": TEST_UUID}]

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "password"}
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["title"] == "1.1.1.1"
    assert result["data"][CONF_HOST] == "1.1.1.1"
    assert result["data"][CONF_USERNAME] == "admin"
    assert result["data"][CONF_PASSWORD] == "password"
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.parametrize(
    ("side_effect", "http_status", "expected_error"),
    [
        (False, HTTPStatus.UNAUTHORIZED, "invalid_auth"),
        (False, HTTPStatus.NOT_FOUND, "cannot_connect"),
        ({"no_uuid": True}, HTTPStatus.OK, "missing_uuid"),
        (Exception("Test error"), None, "unknown"),
    ],
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_discovery_flow_edit_discovered_errors(
    hass: HomeAssistant,
    mock_server: AsyncMock,
    mock_discover: MagicMock,
    side_effect: Any,
    http_status: HTTPStatus | None,
    expected_error: str,
) -> None:
    """Test all error outcomes of the edit_discovered step."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "start_discovery"}
    )
    await hass.async_block_till_done()
    result = await hass.config_entries.flow.async_configure(result["flow_id"])

    mock_server.http_status = HTTPStatus.UNAUTHORIZED
    mock_server.async_query.return_value = False
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_SERVER_LIST: "1.1.1.1"}
    )

    mock_server.http_status = http_status
    mock_server.async_query.side_effect = [False, side_effect]

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "password"}
    )

    assert result["type"] is FlowResultType.FORM
    assert result["errors"]["base"] == expected_error


@pytest.mark.usefixtures("mock_setup_entry")
async def test_discovery_flow_failed(
    hass: HomeAssistant, mock_discover: MagicMock
) -> None:
    """Test discovery flow when no servers are found."""

    async def _failed_discover(callback: Any) -> list:
        return []

    mock_discover.side_effect = _failed_discover

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "start_discovery"}
    )
    await hass.async_block_till_done()
    result = await hass.config_entries.flow.async_configure(result["flow_id"])

    assert result["type"] is FlowResultType.MENU
    assert result["step_id"] == "discovery_failed"

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "edit"}
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "edit"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_discovery_ignores_existing(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_discover: MagicMock
) -> None:
    """Test discovery properly ignores a server that's already configured."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": config_entries.SOURCE_USER}
    )
    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {"next_step_id": "start_discovery"}
    )
    await hass.async_block_till_done()
    result = await hass.config_entries.flow.async_configure(result["flow_id"])

    assert result["type"] is FlowResultType.MENU
    assert result["step_id"] == "discovery_failed"


async def test_integration_discovery(
    hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
    """Test integration discovery flow with a provided UUID."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
        data={CONF_HOST: "1.2.3.4", CONF_PORT: 9000, "uuid": TEST_UUID},
    )
    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "edit_integration_discovered"

    mock_server.async_query.return_value = {"uuid": TEST_UUID}

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "password"}
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["result"].unique_id == TEST_UUID
    assert result["title"] == "1.2.3.4"
    assert len(mock_setup_entry.mock_calls) == 1


@pytest.mark.usefixtures("mock_setup_entry")
async def test_integration_discovery_no_uuid(
    hass: HomeAssistant, mock_server: AsyncMock
) -> None:
    """Test integration discovery flow without a UUID."""
    mock_server.async_query.return_value = {"uuid": TEST_UUID}

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
        data={CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
    )

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "edit_integration_discovered"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_integration_discovery_no_uuid_fails(
    hass: HomeAssistant, mock_server: AsyncMock
) -> None:
    """Test integration discovery flow routes to form when connection fails."""
    mock_server.async_query.return_value = False

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
        data={CONF_HOST: "1.2.3.4", CONF_PORT: 9000},
    )

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "edit_integration_discovered"


async def test_integration_discovery_edit_recovery(
    hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_server: AsyncMock
) -> None:
    """Test editing an integration discovery returns errors and can recover."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
        data={CONF_HOST: "1.2.3.4", CONF_PORT: 9000, "uuid": TEST_UUID},
    )

    mock_server.async_query.return_value = False
    mock_server.http_status = HTTPStatus.UNAUTHORIZED

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "wrongpassword"}
    )

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "edit_integration_discovered"
    assert result["errors"]["base"] == "invalid_auth"

    mock_server.async_query.return_value = {"uuid": TEST_UUID}
    mock_server.http_status = HTTPStatus.OK

    result = await hass.config_entries.flow.async_configure(
        result["flow_id"], {CONF_USERNAME: "admin", CONF_PASSWORD: "correctpassword"}
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert result["result"].unique_id == TEST_UUID
    assert result["title"] == "1.2.3.4"
    assert len(mock_setup_entry.mock_calls) == 1


async def test_dhcp_unknown_player(
    hass: HomeAssistant, dhcp_info: dict[str, Any]
) -> None:
    """Test DHCP discovery of an unconfigured player routes to user setup."""
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_DHCP},
        data=dhcp_info,
    )

    assert result["type"] is FlowResultType.MENU
    assert result["step_id"] == "user"


async def test_dhcp_known_player(
    hass: HomeAssistant, dhcp_info: dict[str, Any], mock_config_entry: MockConfigEntry
) -> None:
    """Test DHCP discovery aborts if player is already registered."""
    mock_config_entry.add_to_hass(hass)

    entity_registry = er.async_get(hass)  # pylint: disable=home-assistant-tests-registry-fixtures
    entity_registry.async_get_or_create(
        MP_DOMAIN, DOMAIN, "aa:bb:cc:dd:ee:ff", config_entry=mock_config_entry
    )

    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": config_entries.SOURCE_DHCP},
        data=dhcp_info,
    )

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


async def test_options_flow(
    hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
    """Test the options flow."""
    mock_config_entry.add_to_hass(hass)

    result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)

    assert result["type"] is FlowResultType.FORM
    assert result["step_id"] == "init"

    result = await hass.config_entries.options.async_configure(
        result["flow_id"],
        user_input={CONF_BROWSE_LIMIT: 500, CONF_VOLUME_STEP: 5},
    )

    assert result["type"] is FlowResultType.CREATE_ENTRY
    assert mock_config_entry.options == {
        CONF_BROWSE_LIMIT: 500,
        CONF_VOLUME_STEP: 5,
    }
