"""AsusRouter config flow module."""

from __future__ import annotations

import logging
import socket
from typing import Any

from asusrouter import AsusData
from asusrouter.error import (
    AsusRouterAccessError,
    AsusRouterConnectionError,
    AsusRouterTimeoutError,
)
from asusrouter.modules.endpoint.error import AccessError
from asusrouter.modules.homeassistant import convert_to_ha_sensors_group
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.const import (
    CONF_HOST,
    CONF_PASSWORD,
    CONF_PORT,
    CONF_SCAN_INTERVAL,
    CONF_SSL,
    CONF_USERNAME,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import format_mac
import voluptuous as vol

from .bridge import ARBridge
from .const import (
    ACCESS_POINT,
    ALL_CLIENTS,
    BASE,
    CONF_CACHE_TIME,
    CONF_CLIENT_DEVICE,
    CONF_CLIENT_FILTER,
    CONF_CLIENT_FILTER_LIST,
    CONF_CLIENTS_IN_ATTR,
    CONF_CONSIDER_HOME,
    CONF_CREATE_DEVICES,
    CONF_DEFAULT_CACHE_TIME,
    CONF_DEFAULT_CLIENT_DEVICE,
    CONF_DEFAULT_CLIENT_FILTER,
    CONF_DEFAULT_CLIENTS_IN_ATTR,
    CONF_DEFAULT_CONSIDER_HOME,
    CONF_DEFAULT_CREATE_DEVICES,
    CONF_DEFAULT_EVENT,
    CONF_DEFAULT_HIDE_PASSWORDS,
    CONF_DEFAULT_INTERFACES,
    CONF_DEFAULT_INTERVALS,
    CONF_DEFAULT_LATEST_CONNECTED,
    CONF_DEFAULT_MODE,
    CONF_DEFAULT_PORT,
    CONF_DEFAULT_SCAN_INTERVAL,
    CONF_DEFAULT_SPLIT_INTERVALS,
    CONF_DEFAULT_SSL,
    CONF_DEFAULT_TRACK_DEVICES,
    CONF_DEFAULT_USERNAME,
    CONF_HIDE_PASSWORDS,
    CONF_INTERFACES,
    CONF_INTERVAL,
    CONF_INTERVAL_DEVICES,
    CONF_INTERVALS,
    CONF_LABELS_CLIENT_FILTER,
    CONF_LABELS_INTERFACES,
    CONF_LABELS_MODE,
    CONF_LATEST_CONNECTED,
    CONF_MODE,
    CONF_SPLIT_INTERVALS,
    CONF_TRACK_DEVICES,
    CONF_VALUES_MODE,
    CONFIGS,
    DOMAIN,
    ERRORS,
    FIRMWARE,
    INTERFACES,
    MEDIA_BRIDGE,
    METHOD,
    NEXT,
    RESULT_ACCESS_ERROR,
    RESULT_CANNOT_RESOLVE,
    RESULT_CONNECTION_ERROR,
    RESULT_ERROR,
    RESULT_LOGIN_BLOCKED,
    RESULT_SUCCESS,
    RESULT_TIMEOUT,
    RESULT_UNKNOWN,
    RESULT_WRONG_CREDENTIALS,
    ROUTER,
    STEP_CONNECTED_DEVICES,
    STEP_CREDENTIALS,
    STEP_EVENTS,
    STEP_FIND,
    STEP_FINISH,
    STEP_INTERFACES,
    STEP_INTERVALS,
    STEP_OPERATION,
    STEP_OPTIONS,
    STEP_SECURITY,
    UNIQUE_ID,
)

_LOGGER = logging.getLogger(__name__)


def _check_host(
    host: str,
) -> str | None:
    """Get the IP address for the hostname."""

    try:
        return socket.gethostbyname(host)
    except socket.gaierror:
        return None


def _check_errors(
    errors: dict[str, Any] | None = None,
) -> bool:
    """Check for errors."""

    if errors is None:
        return False

    return bool(
        BASE in errors
        and errors[BASE] != RESULT_SUCCESS
        and errors[BASE] != ""
    )


async def _async_get_clients(
    hass: HomeAssistant,
    configs: dict[str, Any],
    options: dict[str, Any],
) -> dict[str, Any]:
    """Return list of all the known clients."""

    bridge = ARBridge(hass, configs, options)

    try:
        if not bridge.connected:
            await bridge.async_connect()
        clients = await bridge.api.async_get_data(AsusData.CLIENTS)
        await bridge.async_disconnect()

        return {
            format_mac(mac): client.description.name
            for mac, client in clients.items()
            if client.description.name
        }
    except Exception as ex:  # noqa: BLE001
        _LOGGER.warning(
            "Cannot get clients for %s: %s", configs[CONF_HOST], ex
        )
        return {}


async def _async_get_network_interfaces(
    hass: HomeAssistant,
    configs: dict[str, Any],
    options: dict[str, Any],
) -> list[str]:
    """Return list of possible to monitor network interfaces."""

    bridge = ARBridge(hass, configs, options)

    try:
        if not bridge.connected:
            await bridge.async_connect()
        labels = convert_to_ha_sensors_group(
            await bridge.api.async_get_data(AsusData.NETWORK)
        )
        await bridge.async_disconnect()
        _LOGGER.debug("Found network interfaces: %s", labels)
        return labels
    except Exception as ex:  # noqa: BLE001
        _LOGGER.warning(
            "Cannot get available network interfaces for %s: %s",
            configs[CONF_HOST],
            ex,
        )
        return CONF_DEFAULT_INTERFACES


async def _async_check_connection(  # noqa: C901, PLR0911, PLR0912
    hass: HomeAssistant,
    configs: dict[str, Any],
    options: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Check connection to the device with provided configurations."""

    configs_to_use = configs.copy()
    if options:
        configs_to_use.update(options)
    if CONF_HOST not in configs_to_use:
        return {
            ERRORS: RESULT_ERROR,
        }
    host = configs_to_use[CONF_HOST]

    result = {}
    _LOGGER.debug("Setup initiated")

    # Initialize bridge
    bridge = ARBridge(hass, configs_to_use)

    # Connect
    try:
        await bridge.async_connect()
    # Access error
    except AsusRouterAccessError as ex:
        args = ex.args
        # Wrong credentials
        if args[1] == AccessError.CREDENTIALS:
            _LOGGER.error(
                "Error during connection to `%s`. Wrong credentials", host
            )
            return {
                ERRORS: RESULT_WRONG_CREDENTIALS,
            }
        # Try again later / too many attempts
        if args[1] == AccessError.TRY_AGAIN:
            timeout = args[2].get("timeout")
            _LOGGER.error(
                "Device `%s` has reported block for the login "
                "(to many wrong attempts were made). Please try again "
                "in `%s` seconds",
                host,
                timeout,
            )
            return {
                ERRORS: RESULT_LOGIN_BLOCKED,
            }
        # Reset required
        if args[1] == AccessError.RESET_REQUIRED:
            _LOGGER.error(
                "Device `%s` requires a reset. Please reset the device. "
                "You won't be able to login to the device until the reset "
                "is done.",
                host,
            )
            return {
                ERRORS: RESULT_LOGIN_BLOCKED,
            }
        # Captcha required
        if args[1] == AccessError.CAPTCHA:
            _LOGGER.error(
                "Device `%s` requires a captcha. Please login to the device "
                "and complete the captcha. Integration cannot proceed with "
                "the captcha request. You need either to disable captcha or "
                "login via Web UI from the HA IP address, complete it and try "
                "again. Sometimes, you can also reboot the device to fix this "
                "issue.",
                host,
            )
            return {
                ERRORS: RESULT_LOGIN_BLOCKED,
            }
        # Another error
        if args[1] == AccessError.ANOTHER:
            _LOGGER.error("Device `%s` has reported `another` error.", host)
            return {
                ERRORS: RESULT_ERROR,
            }
        # Unknown error
        if args[1] == AccessError.UNKNOWN:
            _LOGGER.error("Device `%s` has reported `unknown` error.", host)
            return {
                ERRORS: RESULT_UNKNOWN,
            }
        # Anything else
        _LOGGER.error(
            "Error during connection to `%s`. Original exception: %s", host, ex
        )
        return {
            ERRORS: RESULT_ACCESS_ERROR,
        }

    # Timeout error
    except AsusRouterTimeoutError as ex:
        _LOGGER.error(
            "Timeout error during connection to `%s`: Original exception: %s",
            host,
            ex,
        )
        return {
            ERRORS: RESULT_TIMEOUT,
        }

    # Connection error
    except AsusRouterConnectionError as ex:
        _LOGGER.error(
            "Connection error during connection to `%s`: "
            "Original exception: %s",
            host,
            ex,
        )
        return {
            ERRORS: RESULT_CONNECTION_ERROR,
        }

    # Anything else
    except Exception as ex:  # noqa: BLE001
        _LOGGER.error(
            "Unknown error of type '%s' during connection to `%s`: %s",
            type(ex),
            host,
            ex,
        )
        return {
            ERRORS: RESULT_UNKNOWN,
        }
    # Cleanup, so no unclosed sessions will be reported
    finally:
        await bridge.async_clean()

    # Serial number of the device is the best unique_id
    # API provides it all the time for all the devices.
    # MAC as an alternative might not be used / found on some
    # older devices and some Merlin-builds of FW
    result[UNIQUE_ID] = bridge.identity.serial
    await bridge.async_disconnect()
    for item in configs:
        configs_to_use.pop(item)

    result[CONFIGS] = configs_to_use

    _LOGGER.debug("Setup successful")

    return result


async def _async_process_step(
    steps: dict[str, dict[str, Any]],
    step: str | None = None,
    errors: dict[str, Any] | None = None,
    redirect: bool = False,
) -> FlowResult:
    """Universal step selector.

    When the name of the last step is provided, the next step is initialized.
    On errors the same step will repeat.
    """

    if step and step in steps:
        # Method description
        description = steps[step]
        # On errors or redirect, run the step method
        if _check_errors(errors) or redirect:
            if METHOD in description:
                return await description[METHOD]()
            raise ValueError(f"Step `{step}` is not properly defined")
        # If the next step is defined, move to it
        if NEXT in description and description[NEXT]:
            return await _async_process_step(
                steps, description[NEXT], redirect=True
            )
        raise ValueError(f"Step `{step}` is not properly defined")
    raise ValueError(f"Step `{step}` cannot be found")


# FORMS ->


def _create_form_find(
    user_input: dict[str, Any] | None = None,
) -> vol.Schema:
    """Create a form for the 'find' step."""

    if not user_input:
        user_input = {}

    schema = {
        vol.Required(
            CONF_HOST, default=user_input.get(CONF_HOST, "")
        ): cv.string,
    }

    return vol.Schema(schema)


def _create_form_credentials(
    user_input: dict[str, Any] | None = None,
    mode: str = CONF_DEFAULT_MODE,
) -> vol.Schema:
    """Create a form for the 'credentials' step."""

    if not user_input:
        user_input = {}

    schema = {
        vol.Required(
            CONF_USERNAME,
            default=user_input.get(CONF_USERNAME, CONF_DEFAULT_USERNAME),
        ): cv.string,
        vol.Required(
            CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "")
        ): cv.string,
        vol.Optional(
            CONF_PORT, default=user_input.get(CONF_PORT, CONF_DEFAULT_PORT)
        ): cv.positive_int,
        vol.Optional(
            CONF_SSL, default=user_input.get(CONF_SSL, CONF_DEFAULT_SSL)
        ): cv.boolean,
    }

    return vol.Schema(schema)


def _create_form_operation(
    user_input: dict[str, Any] | None = None,
    mode: str = CONF_DEFAULT_MODE,
) -> vol.Schema:
    """Create a form for the 'operation' step."""

    if not user_input:
        user_input = {}

    schema = {
        vol.Required(
            CONF_MODE, default=user_input.get(CONF_MODE, mode)
        ): vol.In(
            {
                mode: CONF_LABELS_MODE.get(mode, mode)
                for mode in CONF_VALUES_MODE
            }
        ),
        vol.Required(
            CONF_SPLIT_INTERVALS,
            default=user_input.get(
                CONF_SPLIT_INTERVALS, CONF_DEFAULT_SPLIT_INTERVALS
            ),
        ): cv.boolean,
    }

    return vol.Schema(schema)


def _create_form_connected_devices(
    user_input: dict[str, Any] | None = None,
    mode: str = CONF_DEFAULT_MODE,
    default: list[str] | None = None,
) -> vol.Schema:
    """Create a form for the `connected_devices` step."""

    if not user_input:
        user_input = {}

    if not default:
        default = []

    schema = {
        vol.Required(
            CONF_TRACK_DEVICES,
            default=user_input.get(
                CONF_TRACK_DEVICES, CONF_DEFAULT_TRACK_DEVICES
            ),
        ): cv.boolean,
        vol.Required(
            CONF_CLIENT_DEVICE,
            default=user_input.get(
                CONF_CLIENT_DEVICE, CONF_DEFAULT_CLIENT_DEVICE
            ),
        ): cv.boolean,
        vol.Required(
            CONF_CLIENTS_IN_ATTR,
            default=user_input.get(
                CONF_CLIENTS_IN_ATTR, CONF_DEFAULT_CLIENTS_IN_ATTR
            ),
        ): cv.boolean,
        vol.Required(
            CONF_CLIENT_FILTER,
            default=user_input.get(
                CONF_CLIENT_FILTER, CONF_DEFAULT_CLIENT_FILTER
            ),
        ): vol.In(CONF_LABELS_CLIENT_FILTER),
        vol.Optional(
            CONF_CLIENT_FILTER_LIST,
            default=default,
        ): cv.multi_select(
            dict(
                sorted(
                    user_input[ALL_CLIENTS].items(), key=lambda item: item[1]
                )
            )
        ),
        vol.Required(
            CONF_LATEST_CONNECTED,
            default=user_input.get(
                CONF_LATEST_CONNECTED, CONF_DEFAULT_LATEST_CONNECTED
            ),
        ): cv.positive_int,
        vol.Required(
            CONF_INTERVAL_DEVICES,
            default=user_input.get(
                CONF_INTERVAL_DEVICES, CONF_DEFAULT_SCAN_INTERVAL
            ),
        ): cv.positive_int,
    }

    if mode in (ACCESS_POINT, ROUTER):
        schema.update(
            {
                vol.Required(
                    CONF_CONSIDER_HOME,
                    default=user_input.get(
                        CONF_CONSIDER_HOME, CONF_DEFAULT_CONSIDER_HOME
                    ),
                ): cv.positive_int,
            }
        )

    if mode in (ACCESS_POINT, MEDIA_BRIDGE, ROUTER):
        schema.update(
            {
                vol.Required(
                    CONF_CREATE_DEVICES,
                    default=user_input.get(
                        CONF_CREATE_DEVICES, CONF_DEFAULT_CREATE_DEVICES
                    ),
                ): cv.boolean,
            }
        )

    return vol.Schema(schema)


def _create_form_intervals(
    user_input: dict[str, Any] | None = None,
    mode: str = CONF_DEFAULT_MODE,
) -> vol.Schema:
    """Create a form for the 'intervals' step."""

    if not user_input:
        user_input = {}

    schema = {
        vol.Required(
            CONF_CACHE_TIME,
            default=user_input.get(CONF_CACHE_TIME, CONF_DEFAULT_CACHE_TIME),
        ): cv.positive_int,
    }

    split = user_input.get(CONF_SPLIT_INTERVALS, CONF_DEFAULT_SPLIT_INTERVALS)
    conf_scan_interval = user_input.get(
        CONF_SCAN_INTERVAL, CONF_DEFAULT_SCAN_INTERVAL
    )

    if split is False:
        schema.update(
            {
                vol.Optional(
                    CONF_SCAN_INTERVAL,
                    default=conf_scan_interval,
                ): cv.positive_int,
                vol.Optional(
                    CONF_INTERVAL + FIRMWARE,
                    default=user_input.get(
                        CONF_INTERVAL + FIRMWARE,
                        CONF_DEFAULT_INTERVALS[CONF_INTERVAL + FIRMWARE],
                    ),
                ): cv.positive_int,
            }
        )
    elif split is True:
        schema.update(
            {
                vol.Required(
                    conf,
                    default=user_input.get(
                        conf,
                        CONF_DEFAULT_INTERVALS.get(conf, conf_scan_interval),
                    ),
                ): cv.positive_int
                for conf in CONF_INTERVALS
            }
        )

    return vol.Schema(schema)


def _create_form_interfaces(
    user_input: dict[str, Any] | None = None,
    default: list[str] | None = None,
) -> vol.Schema:
    """Create a form for the 'interfaces' step."""

    if not user_input:
        user_input = {}

    if not default:
        default = []

    schema = {
        vol.Optional(
            CONF_INTERFACES,
            default=default,
        ): cv.multi_select(
            {
                interface: CONF_LABELS_INTERFACES.get(interface, interface)
                for interface in user_input[INTERFACES]
            }
        ),
    }

    return vol.Schema(schema)


def _create_form_events(
    user_input: dict[str, Any] | None = None,
) -> vol.Schema:
    """Create a form for the `events` step."""

    if not user_input:
        user_input = {}

    schema = {
        vol.Optional(
            event,
            default=user_input.get(event, default_state),
        ): cv.boolean
        for event, default_state in CONF_DEFAULT_EVENT.items()
    }

    return vol.Schema(schema)


def _create_form_security(
    user_input: dict[str, Any] | None = None,
) -> vol.Schema:
    """Create a form for the `security` step."""

    if not user_input:
        user_input = {}

    schema = {
        vol.Required(
            CONF_HIDE_PASSWORDS,
            default=user_input.get(
                CONF_HIDE_PASSWORDS, CONF_DEFAULT_HIDE_PASSWORDS
            ),
        ): cv.boolean,
    }

    return vol.Schema(schema)


# <- FORMS


class ARFlowHandler(ConfigFlow, domain=DOMAIN):
    """Handle config flow for AsusRouter."""

    VERSION = 5

    def __init__(self) -> None:
        """Initialise config flow."""

        self._configs: dict[str, Any] = {}
        self._options: dict[str, Any] = {}
        self._unique_id: str | None = None
        self._mode = CONF_DEFAULT_MODE
        self.description_placeholders: dict[str, Any] = {}

        # Steps description
        self._steps: dict[str, dict[str, Any]] = {
            STEP_FIND: {METHOD: self.async_step_find, NEXT: STEP_CREDENTIALS},
            STEP_CREDENTIALS: {
                METHOD: self.async_step_credentials,
                NEXT: STEP_OPERATION,
            },
            STEP_OPERATION: {
                METHOD: self.async_step_operation,
                NEXT: STEP_OPTIONS,
            },
            STEP_OPTIONS: {METHOD: self.async_step_options},
        }

    # User setup
    async def async_step_user(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Flow initiated by user."""

        return await self.async_step_find(user_input)

    # Find the device
    async def async_step_find(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Find the device step."""

        step_id = STEP_FIND

        errors = {}

        if user_input:
            # Check if host can be resolved
            ip = await self.hass.async_add_executor_job(
                _check_host, user_input[CONF_HOST]
            )
            if not ip:
                errors[BASE] = RESULT_CANNOT_RESOLVE

            if not errors:
                # Save host to configs
                self._configs.update(user_input)
                # Proceed to the next step
                return await _async_process_step(self._steps, step_id, errors)

        if not user_input:
            user_input = {}

        return self.async_show_form(
            step_id=step_id,
            data_schema=_create_form_find(user_input),
            errors=errors,
        )

    # Credentials & connection
    async def async_step_credentials(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Credentials step."""

        step_id = STEP_CREDENTIALS

        errors = {}

        if user_input:
            # Check credentials and connection
            result = await _async_check_connection(
                self.hass, self._configs, user_input
            )
            # Show errors if any
            if ERRORS in result:
                errors[BASE] = result[ERRORS]
            else:
                # Save the checked settings to the options
                self._options.update(result[CONFIGS])
                # Set unique ID obtained from the device during the check
                await self.async_set_unique_id(result[UNIQUE_ID])
                self._abort_if_unique_id_configured(
                    updates={CONF_HOST: self._configs[CONF_HOST]}
                )
                # Proceed to the next step
                return await _async_process_step(self._steps, step_id)

        if not user_input:
            user_input = self._options.copy()

        return self.async_show_form(
            step_id=step_id,
            data_schema=_create_form_credentials(user_input),
            errors=errors,
        )

    # Operation settings
    async def async_step_operation(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select operation settings."""

        step_id = STEP_OPERATION

        if not user_input:
            user_input = self._options.copy()
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_operation(user_input, self._mode),
            )

        self._options.update(user_input)
        # Save the operation mode
        self._mode = user_input.get(CONF_MODE, CONF_DEFAULT_MODE)

        # Proceed to the next step
        return await _async_process_step(self._steps, step_id)

    # Options
    async def async_step_options(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select options to change."""

        menu_options = [
            STEP_INTERVALS,
            STEP_INTERFACES,
            STEP_EVENTS,
            STEP_SECURITY,
            STEP_FINISH,
        ]

        # Mode-specific
        if self._mode in (ACCESS_POINT, MEDIA_BRIDGE, ROUTER):
            menu_options.insert(0, STEP_CONNECTED_DEVICES)

        return self.async_show_menu(
            step_id=STEP_OPTIONS,
            menu_options=menu_options,
        )

    # Connected devices
    async def async_step_connected_devices(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select connected devices settings."""

        step_id = STEP_CONNECTED_DEVICES

        if not user_input:
            user_input = self._options.copy()
            user_input[ALL_CLIENTS] = await _async_get_clients(
                self.hass, self._configs, self._options
            )
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_connected_devices(
                    user_input, self._mode
                ),
            )
        self._options.update(user_input)

        return await self.async_step_options()

    # Time intervals
    async def async_step_intervals(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select intervals."""

        step_id = STEP_INTERVALS

        if not user_input:
            user_input = self._options.copy()
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_intervals(user_input, self._mode),
            )

        self._options.update(user_input)

        return await self.async_step_options()

    # Network monitoring
    async def async_step_interfaces(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select interfaces for traffic monitoring."""

        step_id = STEP_INTERFACES

        if not user_input:
            user_input = self._options.copy()
            user_input[INTERFACES] = await _async_get_network_interfaces(
                self.hass, self._configs, self._options
            )
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_interfaces(user_input),
            )

        self._options.update(user_input)

        return await self.async_step_options()

    # HA events
    async def async_step_events(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Events step."""

        step_id = STEP_EVENTS

        if not user_input:
            user_input = self._options.copy()
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_events(user_input),
            )

        self._options.update(user_input)

        return await self.async_step_options()

    # Security
    async def async_step_security(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Security step."""

        step_id = STEP_SECURITY

        if not user_input:
            user_input = {}
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_security(user_input),
            )

        self._options.update(user_input)

        return await self.async_step_options()

    # Step Finish
    async def async_step_finish(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Finish setup."""

        return self.async_create_entry(
            title=self._configs[CONF_HOST],
            data=self._configs,
            options=self._options,
        )

    @staticmethod
    @callback
    def async_get_options_flow(
        config_entry: ConfigEntry,
    ) -> AROptionsFlowHandler:
        """Get the options flow."""

        return AROptionsFlowHandler()


class AROptionsFlowHandler(OptionsFlow):
    """Options flow for AsusRouter."""

    def __init__(
        self,
    ) -> None:
        """Initialize options flow."""

    async def async_step_init(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Options flow."""

        self._selection: dict[str, Any] = {}
        self._configs: dict[str, Any] = self.config_entry.data.copy()
        self._host: str = self._configs[CONF_HOST]
        self._options: dict[str, Any] = self.config_entry.options.copy()
        self._mode = self._options.get(CONF_MODE, CONF_DEFAULT_MODE)

        return await self.async_step_options(user_input)

    async def async_step_options(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select options to change."""

        menu_options = [
            STEP_CREDENTIALS,
            STEP_OPERATION,
            STEP_INTERVALS,
            STEP_INTERFACES,
            STEP_EVENTS,
            STEP_SECURITY,
            STEP_FINISH,
        ]

        # Mode-specific
        if self._mode in (ACCESS_POINT, MEDIA_BRIDGE, ROUTER):
            menu_options.insert(2, STEP_CONNECTED_DEVICES)

        return self.async_show_menu(
            step_id=STEP_OPTIONS,
            menu_options=menu_options,
        )

    # Credentials
    async def async_step_credentials(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select credentials."""

        step_id = STEP_CREDENTIALS

        errors = {}

        if user_input:
            if (
                user_input[CONF_USERNAME] != self._options[CONF_USERNAME]
                or user_input[CONF_PASSWORD] != self._options[CONF_PASSWORD]
                or user_input[CONF_PORT] != self._options[CONF_PORT]
                or user_input[CONF_SSL] != self._options[CONF_SSL]
            ):
                self._options.update(user_input)
                result = await _async_check_connection(
                    self.hass, self._configs, self._options
                )
                if ERRORS in result:
                    errors[BASE] = result[ERRORS]
                else:
                    self._options.update(result[CONFIGS])
                    return await self.async_step_options()
            return await self.async_step_options()

        if not user_input:
            user_input = self._options.copy()

        return self.async_show_form(
            step_id=step_id,
            data_schema=_create_form_credentials(user_input, self._mode),
            errors=errors,
        )

    # Operation mode
    async def async_step_operation(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select operation mode."""

        step_id = STEP_OPERATION

        if not user_input:
            user_input = self._options.copy()
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_operation(user_input, self._mode),
            )

        self._options.update(user_input)
        # Save the operation mode
        self._mode = user_input.get(CONF_MODE, CONF_DEFAULT_MODE)

        return await self.async_step_options()

    # Connected devices
    async def async_step_connected_devices(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select connected devices settings."""

        step_id = STEP_CONNECTED_DEVICES

        if not user_input:
            user_input = self._options.copy()
            # Get the selected clients
            selected = user_input.get(CONF_CLIENT_FILTER_LIST, []).copy()
            # Get all the clients
            all_clients = await _async_get_clients(
                self.hass, self._configs, self._options
            )
            # If client was in the list, but cannot be found now, still add it
            for client in selected:
                if client not in all_clients:
                    all_clients[client] = client.upper()
            # Save the clients as options
            user_input[ALL_CLIENTS] = all_clients
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_connected_devices(
                    user_input, self._mode, default=selected
                ),
            )
        self._options.update(user_input)

        return await self.async_step_options()

    # Update intervals
    async def async_step_intervals(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select intervals."""

        step_id = STEP_INTERVALS

        if not user_input:
            user_input = self._options.copy()
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_intervals(user_input, self._mode),
            )

        self._options.update(user_input)

        return await self.async_step_options()

    # Interfaces to monitor
    async def async_step_interfaces(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Step to select network interfaces."""

        step_id = STEP_INTERFACES

        if not user_input:
            user_input = self._options.copy()
            if user_input.get(INTERFACES) is None:
                user_input[INTERFACES] = []
            selected = user_input[INTERFACES].copy()
            interfaces = await _async_get_network_interfaces(
                self.hass, self._configs, self._options
            )
            # If interface was tracked, but cannot be found now, still add it
            for interface in interfaces:
                if interface not in user_input[INTERFACES]:
                    user_input[INTERFACES].append(interface)
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_interfaces(
                    user_input, default=selected
                ),
            )

        self._options.update(user_input)

        return await self.async_step_options()

    # HA events
    async def async_step_events(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Events step."""

        step_id = STEP_EVENTS

        if not user_input:
            user_input = self._options.copy()
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_events(user_input),
            )

        self._options.update(user_input)

        return await self.async_step_options()

    # Security options
    async def async_step_security(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Security step."""

        step_id = STEP_SECURITY

        if not user_input:
            user_input = self._options.copy()
            return self.async_show_form(
                step_id=step_id,
                data_schema=_create_form_security(user_input),
            )

        self._options.update(user_input)

        return await self.async_step_options()

    # Step Finish
    async def async_step_finish(
        self,
        user_input: dict[str, Any] | None = None,
    ) -> FlowResult:
        """Finish setup."""

        return self.async_create_entry(
            title=self.config_entry.title,
            data=self._options,
        )
