"""Test config flow for Insteon."""

from __future__ import annotations

import logging

from pyinsteon import async_connect

from homeassistant.components import dhcp, usb
from homeassistant.config_entries import (
    DEFAULT_DISCOVERY_UNIQUE_ID,
    ConfigFlow,
    ConfigFlowResult,
)
from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_NAME
from homeassistant.helpers.device_registry import format_mac

from .const import CONF_HUB_VERSION, DOMAIN
from .schemas import build_hub_schema, build_plm_manual_schema, build_plm_schema
from .utils import async_get_usb_ports

STEP_PLM = "plm"
STEP_PLM_MANUALLY = "plm_manually"
STEP_HUB_V1 = "hubv1"
STEP_HUB_V2 = "hubv2"
STEP_CHANGE_HUB_CONFIG = "change_hub_config"
STEP_CHANGE_PLM_CONFIG = "change_plm_config"
STEP_ADD_X10 = "add_x10"
STEP_ADD_OVERRIDE = "add_override"
STEP_REMOVE_OVERRIDE = "remove_override"
STEP_REMOVE_X10 = "remove_x10"
MODEM_TYPE = "modem_type"
PLM_MANUAL = "manual"

_LOGGER = logging.getLogger(__name__)


async def _async_connect(**kwargs):
    """Connect to the Insteon modem."""
    try:
        await async_connect(**kwargs)
    except ConnectionError:
        _LOGGER.error("Could not connect to Insteon modem")
        return False

    _LOGGER.info("Connected to Insteon modem")
    return True


class InsteonFlowHandler(ConfigFlow, domain=DOMAIN):
    """Insteon config flow handler."""

    _device_path: str | None = None
    _device_name: str | None = None
    discovered_conf: dict[str, str] = {}

    async def async_step_user(self, user_input=None):
        """Init the config flow."""
        if self._async_current_entries():
            return self.async_abort(reason="single_instance_allowed")
        modem_types = [STEP_PLM, STEP_HUB_V1, STEP_HUB_V2]
        return self.async_show_menu(step_id="user", menu_options=modem_types)

    async def async_step_plm(self, user_input=None):
        """Set up the PLM modem type."""
        errors = {}
        if user_input is not None:
            if user_input[CONF_DEVICE] == PLM_MANUAL:
                return await self.async_step_plm_manually()
            if await _async_connect(**user_input):
                return self.async_create_entry(title="", data=user_input)
            errors["base"] = "cannot_connect"
        schema_defaults = user_input if user_input is not None else {}
        ports = await async_get_usb_ports(self.hass)
        if not ports:
            return await self.async_step_plm_manually()
        ports[PLM_MANUAL] = "Enter manually"
        data_schema = build_plm_schema(ports, **schema_defaults)
        return self.async_show_form(
            step_id=STEP_PLM, data_schema=data_schema, errors=errors
        )

    async def async_step_plm_manually(self, user_input=None):
        """Set up the PLM modem type manually."""
        errors = {}
        schema_defaults = {}
        if user_input is not None:
            if await _async_connect(**user_input):
                return self.async_create_entry(title="", data=user_input)
            errors["base"] = "cannot_connect"
            schema_defaults = user_input
        data_schema = build_plm_manual_schema(**schema_defaults)
        return self.async_show_form(
            step_id=STEP_PLM_MANUALLY, data_schema=data_schema, errors=errors
        )

    async def async_step_hubv1(self, user_input=None):
        """Set up the Hub v1 modem type."""
        return await self._async_setup_hub(hub_version=1, user_input=user_input)

    async def async_step_hubv2(self, user_input=None):
        """Set up the Hub v2 modem type."""
        return await self._async_setup_hub(hub_version=2, user_input=user_input)

    async def _async_setup_hub(self, hub_version, user_input):
        """Set up the Hub versions 1 and 2."""
        errors = {}
        if user_input is not None:
            user_input[CONF_HUB_VERSION] = hub_version
            if await _async_connect(**user_input):
                return self.async_create_entry(title="", data=user_input)
            user_input.pop(CONF_HUB_VERSION)
            errors["base"] = "cannot_connect"
        schema_defaults = user_input if user_input is not None else self.discovered_conf
        data_schema = build_hub_schema(hub_version=hub_version, **schema_defaults)
        step_id = STEP_HUB_V2 if hub_version == 2 else STEP_HUB_V1
        return self.async_show_form(
            step_id=step_id, data_schema=data_schema, errors=errors
        )

    async def async_step_usb(
        self, discovery_info: usb.UsbServiceInfo
    ) -> ConfigFlowResult:
        """Handle USB discovery."""
        if self._async_current_entries():
            return self.async_abort(reason="single_instance_allowed")

        self._device_path = discovery_info.device
        self._device_name = usb.human_readable_device_name(
            discovery_info.device,
            discovery_info.serial_number,
            discovery_info.manufacturer,
            discovery_info.description,
            discovery_info.vid,
            discovery_info.pid,
        )
        self._set_confirm_only()
        self.context["title_placeholders"] = {
            CONF_NAME: f"Insteon PLM {self._device_name}"
        }
        await self.async_set_unique_id(DEFAULT_DISCOVERY_UNIQUE_ID)
        return await self.async_step_confirm_usb()

    async def async_step_confirm_usb(self, user_input=None) -> ConfigFlowResult:
        """Confirm a USB discovery."""
        if user_input is not None:
            return await self.async_step_plm({CONF_DEVICE: self._device_path})

        return self.async_show_form(
            step_id="confirm_usb",
            description_placeholders={CONF_NAME: self._device_name},
        )

    async def async_step_dhcp(
        self, discovery_info: dhcp.DhcpServiceInfo
    ) -> ConfigFlowResult:
        """Handle a DHCP discovery."""
        self.discovered_conf = {CONF_HOST: discovery_info.ip}
        self.context["title_placeholders"] = {
            CONF_NAME: f"Insteon Hub {discovery_info.ip}"
        }
        await self.async_set_unique_id(format_mac(discovery_info.macaddress))
        return await self.async_step_user()
