"""Tests for the AndroidTV config flow."""

from typing import Any
from unittest.mock import patch

import pytest

from homeassistant.components.androidtv.config_flow import (
    APPS_NEW_ID,
    CONF_APP_DELETE,
    CONF_APP_ID,
    CONF_APP_NAME,
    CONF_RULE_DELETE,
    CONF_RULE_ID,
    CONF_RULE_VALUES,
    RULES_NEW_ID,
)
from homeassistant.components.androidtv.const import (
    CONF_ADB_SERVER_IP,
    CONF_ADB_SERVER_PORT,
    CONF_ADBKEY,
    CONF_APPS,
    CONF_EXCLUDE_UNNAMED_APPS,
    CONF_GET_SOURCES,
    CONF_MORE_OPTIONS,
    CONF_SCREENCAP_INTERVAL,
    CONF_STATE_DETECTION_RULES,
    CONF_TURN_OFF_COMMAND,
    CONF_TURN_ON_COMMAND,
    DEFAULT_ADB_SERVER_PORT,
    DEFAULT_PORT,
    DEVICE_ANDROIDTV,
    DOMAIN,
    PROP_ETHMAC,
    PROP_WIFIMAC,
)
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_DEVICE_CLASS, CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType

from .patchers import PATCH_ACCESS, PATCH_ISFILE, PATCH_SETUP_ENTRY

from tests.common import MockConfigEntry

ADBKEY = "adbkey"
ETH_MAC = "a1:b1:c1:d1:e1:f1"
WIFI_MAC = "a2:b2:c2:d2:e2:f2"
INVALID_MAC = "ff:ff:ff:ff:ff:ff"
HOST = "127.0.0.1"
VALID_DETECT_RULE = [{"paused": {"media_session_state": 3}}]

# Android device with Python ADB implementation
CONFIG_PYTHON_ADB = {
    CONF_HOST: HOST,
    CONF_PORT: DEFAULT_PORT,
    CONF_DEVICE_CLASS: DEVICE_ANDROIDTV,
}

# Android device with ADB server
CONFIG_ADB_SERVER = {
    CONF_HOST: HOST,
    CONF_PORT: DEFAULT_PORT,
    CONF_DEVICE_CLASS: DEVICE_ANDROIDTV,
    CONF_ADB_SERVER_IP: "127.0.0.1",
    CONF_ADB_SERVER_PORT: DEFAULT_ADB_SERVER_PORT,
}

# Flow input variants (with section nesting)
FLOW_PYTHON_ADB = {
    **CONFIG_PYTHON_ADB,
    CONF_MORE_OPTIONS: {},
}

FLOW_ADB_SERVER = {
    CONF_HOST: HOST,
    CONF_PORT: DEFAULT_PORT,
    CONF_DEVICE_CLASS: DEVICE_ANDROIDTV,
    CONF_MORE_OPTIONS: {
        CONF_ADB_SERVER_IP: "127.0.0.1",
        CONF_ADB_SERVER_PORT: DEFAULT_ADB_SERVER_PORT,
    },
}

CONNECT_METHOD = (
    "homeassistant.components.androidtv.config_flow.async_connect_androidtv"
)


class MockConfigDevice:
    """Mock class to emulate Android device."""

    def __init__(self, eth_mac=ETH_MAC, wifi_mac=None) -> None:
        """Initialize a fake device to test config flow."""
        self.available = True
        self.device_properties = {PROP_ETHMAC: eth_mac, PROP_WIFIMAC: wifi_mac}

    async def adb_close(self):
        """Fake method to close connection."""
        self.available = False


@pytest.mark.parametrize(
    ("flow_input", "expected_data", "eth_mac", "wifi_mac"),
    [
        (FLOW_PYTHON_ADB, CONFIG_PYTHON_ADB, ETH_MAC, None),
        (FLOW_ADB_SERVER, CONFIG_ADB_SERVER, ETH_MAC, None),
        (FLOW_PYTHON_ADB, CONFIG_PYTHON_ADB, None, WIFI_MAC),
        (FLOW_ADB_SERVER, CONFIG_ADB_SERVER, None, WIFI_MAC),
        (FLOW_PYTHON_ADB, CONFIG_PYTHON_ADB, ETH_MAC, WIFI_MAC),
        (FLOW_ADB_SERVER, CONFIG_ADB_SERVER, ETH_MAC, WIFI_MAC),
    ],
)
async def test_user(
    hass: HomeAssistant,
    flow_input: dict[str, Any],
    expected_data: dict[str, Any],
    eth_mac: str | None,
    wifi_mac: str | None,
) -> None:
    """Test user config."""
    flow_result = await hass.config_entries.flow.async_init(
        DOMAIN, context={"source": SOURCE_USER}
    )
    assert flow_result["type"] is FlowResultType.FORM
    assert flow_result["step_id"] == "user"

    # test with all provided
    with (
        patch(
            CONNECT_METHOD,
            return_value=(MockConfigDevice(eth_mac, wifi_mac), None),
        ),
        PATCH_SETUP_ENTRY as mock_setup_entry,
    ):
        result = await hass.config_entries.flow.async_configure(
            flow_result["flow_id"], user_input=flow_input
        )
        await hass.async_block_till_done()

        assert result["type"] is FlowResultType.CREATE_ENTRY
        assert result["title"] == HOST
        assert result["data"] == expected_data

        assert len(mock_setup_entry.mock_calls) == 1


async def test_user_adbkey(hass: HomeAssistant) -> None:
    """Test user step with adbkey file."""
    flow_input = {
        **CONFIG_PYTHON_ADB,
        CONF_MORE_OPTIONS: {CONF_ADBKEY: ADBKEY},
    }
    expected_data = {**CONFIG_PYTHON_ADB, CONF_ADBKEY: ADBKEY}

    with (
        patch(
            CONNECT_METHOD,
            return_value=(MockConfigDevice(), None),
        ),
        PATCH_ISFILE,
        PATCH_ACCESS,
        PATCH_SETUP_ENTRY as mock_setup_entry,
    ):
        result = await hass.config_entries.flow.async_init(
            DOMAIN,
            context={"source": SOURCE_USER},
            data=flow_input,
        )
        await hass.async_block_till_done()

        assert result["type"] is FlowResultType.CREATE_ENTRY
        assert result["title"] == HOST
        assert result["data"] == expected_data

        assert len(mock_setup_entry.mock_calls) == 1


async def test_error_both_key_server(hass: HomeAssistant) -> None:
    """Test we abort if both adb key and server are provided."""
    flow_input = {
        CONF_HOST: HOST,
        CONF_PORT: DEFAULT_PORT,
        CONF_DEVICE_CLASS: DEVICE_ANDROIDTV,
        CONF_MORE_OPTIONS: {
            CONF_ADB_SERVER_IP: "127.0.0.1",
            CONF_ADB_SERVER_PORT: DEFAULT_ADB_SERVER_PORT,
            CONF_ADBKEY: ADBKEY,
        },
    }
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_USER},
        data=flow_input,
    )

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

    with (
        patch(
            CONNECT_METHOD,
            return_value=(MockConfigDevice(), None),
        ),
        PATCH_SETUP_ENTRY,
    ):
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input=FLOW_ADB_SERVER
        )
        await hass.async_block_till_done()

        assert result2["type"] is FlowResultType.CREATE_ENTRY
        assert result2["title"] == HOST
        assert result2["data"] == CONFIG_ADB_SERVER


async def test_error_invalid_key(hass: HomeAssistant) -> None:
    """Test we abort if component is already setup."""
    flow_input = {
        **CONFIG_PYTHON_ADB,
        CONF_MORE_OPTIONS: {CONF_ADBKEY: ADBKEY},
    }
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_USER},
        data=flow_input,
    )

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

    with (
        patch(
            CONNECT_METHOD,
            return_value=(MockConfigDevice(), None),
        ),
        PATCH_SETUP_ENTRY,
    ):
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input=FLOW_ADB_SERVER
        )
        await hass.async_block_till_done()

        assert result2["type"] is FlowResultType.CREATE_ENTRY
        assert result2["title"] == HOST
        assert result2["data"] == CONFIG_ADB_SERVER


@pytest.mark.parametrize(
    ("flow_input", "eth_mac", "wifi_mac"),
    [
        (FLOW_ADB_SERVER, None, None),
        (FLOW_PYTHON_ADB, None, None),
        (FLOW_ADB_SERVER, INVALID_MAC, None),
        (FLOW_PYTHON_ADB, INVALID_MAC, None),
        (FLOW_ADB_SERVER, None, INVALID_MAC),
        (FLOW_PYTHON_ADB, None, INVALID_MAC),
    ],
)
async def test_invalid_mac(
    hass: HomeAssistant,
    flow_input: dict[str, Any],
    eth_mac: str | None,
    wifi_mac: str | None,
) -> None:
    """Test for invalid mac address."""
    with patch(
        CONNECT_METHOD,
        return_value=(MockConfigDevice(eth_mac, wifi_mac), None),
    ):
        result = await hass.config_entries.flow.async_init(
            DOMAIN,
            context={"source": SOURCE_USER},
            data=flow_input,
        )

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


async def test_abort_if_host_exist(hass: HomeAssistant) -> None:
    """Test we abort if component is already setup."""
    MockConfigEntry(
        domain=DOMAIN, data=CONFIG_ADB_SERVER, unique_id=ETH_MAC
    ).add_to_hass(hass)

    config_data = CONFIG_PYTHON_ADB
    # Should fail, same HOST
    result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_USER},
        data=config_data,
    )

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


async def test_abort_if_unique_exist(hass: HomeAssistant) -> None:
    """Test we abort if component is already setup."""
    config_data = CONFIG_ADB_SERVER.copy()
    config_data[CONF_HOST] = "127.0.0.2"
    MockConfigEntry(domain=DOMAIN, data=config_data, unique_id=ETH_MAC).add_to_hass(
        hass
    )

    # Should fail, same SerialNo
    with patch(
        CONNECT_METHOD,
        return_value=(MockConfigDevice(), None),
    ):
        result = await hass.config_entries.flow.async_init(
            DOMAIN,
            context={"source": SOURCE_USER},
            data=CONFIG_ADB_SERVER,
        )

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


async def test_on_connect_failed(hass: HomeAssistant) -> None:
    """Test when we have errors connecting the router."""
    flow_result = await hass.config_entries.flow.async_init(
        DOMAIN,
        context={"source": SOURCE_USER},
    )

    with patch(CONNECT_METHOD, return_value=(None, "Error")):
        result = await hass.config_entries.flow.async_configure(
            flow_result["flow_id"], user_input=FLOW_ADB_SERVER
        )
        assert result["type"] is FlowResultType.FORM
        assert result["errors"] == {"base": "cannot_connect"}

    with patch(
        CONNECT_METHOD,
        side_effect=TypeError,
    ):
        result2 = await hass.config_entries.flow.async_configure(
            result["flow_id"], user_input=FLOW_ADB_SERVER
        )
        assert result2["type"] is FlowResultType.FORM
        assert result2["errors"] == {"base": "unknown"}

    with (
        patch(
            CONNECT_METHOD,
            return_value=(MockConfigDevice(), None),
        ),
        PATCH_SETUP_ENTRY,
    ):
        result3 = await hass.config_entries.flow.async_configure(
            result2["flow_id"], user_input=FLOW_ADB_SERVER
        )
        await hass.async_block_till_done()

        assert result3["type"] is FlowResultType.CREATE_ENTRY
        assert result3["title"] == HOST
        assert result3["data"] == CONFIG_ADB_SERVER


async def test_options_flow(hass: HomeAssistant) -> None:
    """Test config flow options."""
    config_entry = MockConfigEntry(
        domain=DOMAIN,
        data=CONFIG_ADB_SERVER,
        unique_id=ETH_MAC,
        options={
            CONF_APPS: {"app1": "App1"},
            CONF_STATE_DETECTION_RULES: {"com.plexapp.android": VALID_DETECT_RULE},
        },
    )
    config_entry.add_to_hass(hass)

    with PATCH_SETUP_ENTRY:
        await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()
        result = await hass.config_entries.options.async_init(config_entry.entry_id)

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

        # test app form with existing app
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_APPS: "app1",
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "apps"

        # test change value in apps form
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_APP_NAME: "Appl1",
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "init"

        # test app form with new app
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_APPS: APPS_NEW_ID,
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "apps"

        # test save value for new app
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_APP_ID: "app2",
                CONF_APP_NAME: "Appl2",
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "init"

        # test app form for delete
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_APPS: "app1",
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "apps"

        # test delete app1
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_APP_NAME: "Appl1",
                CONF_APP_DELETE: True,
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "init"

        # test rules form with existing rule
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_STATE_DETECTION_RULES: "com.plexapp.android",
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "rules"

        # test change value in rule form with invalid json rule
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_RULE_VALUES: "a",
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "rules"
        assert result["errors"] == {"base": "invalid_det_rules"}

        # test change value in rule form with invalid rule
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_RULE_VALUES: {"a": "b"},
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "rules"
        assert result["errors"] == {"base": "invalid_det_rules"}

        # test change value in rule form with valid rule
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_RULE_VALUES: ["standby"],
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "init"

        # test rule form with new rule
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_STATE_DETECTION_RULES: RULES_NEW_ID,
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "rules"

        # test save value for new rule
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_RULE_ID: "rule2",
                CONF_RULE_VALUES: VALID_DETECT_RULE,
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "init"

        # test rules form with delete existing rule
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_STATE_DETECTION_RULES: "com.plexapp.android",
            },
        )
        assert result["type"] is FlowResultType.FORM
        assert result["step_id"] == "rules"

        # test delete rule
        result = await hass.config_entries.options.async_configure(
            result["flow_id"],
            user_input={
                CONF_RULE_DELETE: True,
            },
        )
        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_GET_SOURCES: True,
                CONF_EXCLUDE_UNNAMED_APPS: True,
                CONF_SCREENCAP_INTERVAL: 1,
                CONF_TURN_OFF_COMMAND: "off",
                CONF_TURN_ON_COMMAND: "on",
            },
        )

        assert result["type"] is FlowResultType.CREATE_ENTRY

        apps_options = config_entry.options[CONF_APPS]
        assert apps_options.get("app1") is None
        assert apps_options["app2"] == "Appl2"

        assert config_entry.options[CONF_GET_SOURCES] is True
        assert config_entry.options[CONF_EXCLUDE_UNNAMED_APPS] is True
        assert config_entry.options[CONF_SCREENCAP_INTERVAL] == 1
        assert config_entry.options[CONF_TURN_OFF_COMMAND] == "off"
        assert config_entry.options[CONF_TURN_ON_COMMAND] == "on"
