"""Config flow to configure the LG Netcast TV integration."""

from __future__ import annotations

import contextlib
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any

from pylgnetcast import AccessTokenError, LgNetCastClient, SessionIdError
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.const import (
    CONF_ACCESS_TOKEN,
    CONF_HOST,
    CONF_ID,
    CONF_MODEL,
    CONF_NAME,
)
from homeassistant.core import CALLBACK_TYPE, DOMAIN as HOMEASSISTANT_DOMAIN, callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.util.network import is_host_valid

from .const import DEFAULT_NAME, DOMAIN
from .helpers import LGNetCastDetailDiscoveryError, async_discover_netcast_details

DISPLAY_ACCESS_TOKEN_INTERVAL = timedelta(seconds=1)


class LGNetCast(config_entries.ConfigFlow, domain=DOMAIN):
    """Handle a config flow for LG Netcast TV integration."""

    VERSION = 1

    def __init__(self) -> None:
        """Initialize config flow."""
        self.client: LgNetCastClient | None = None
        self.device_config: dict[str, Any] = {}
        self._discovered_devices: dict[str, Any] = {}
        self._track_interval: CALLBACK_TYPE | None = None

    def create_client(self) -> None:
        """Create LG Netcast client from config."""
        host = self.device_config[CONF_HOST]
        access_token = self.device_config.get(CONF_ACCESS_TOKEN)
        self.client = LgNetCastClient(host, access_token)

    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle the initial step."""
        errors: dict[str, str] = {}

        if user_input is not None:
            host = user_input[CONF_HOST]
            if is_host_valid(host):
                self.device_config[CONF_HOST] = host
                return await self.async_step_authorize()

            errors[CONF_HOST] = "invalid_host"

        return self.async_show_form(
            step_id="user",
            data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
            errors=errors,
        )

    async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
        """Import configuration from yaml."""
        self.device_config = {
            CONF_HOST: import_data[CONF_HOST],
            CONF_NAME: import_data[CONF_NAME],
        }

        def _create_issue():
            async_create_issue(
                self.hass,
                HOMEASSISTANT_DOMAIN,
                f"deprecated_yaml_{DOMAIN}",
                breaks_in_ha_version="2024.11.0",
                is_fixable=False,
                issue_domain=DOMAIN,
                severity=IssueSeverity.WARNING,
                translation_key="deprecated_yaml",
                translation_placeholders={
                    "domain": DOMAIN,
                    "integration_title": "LG Netcast",
                },
            )

        try:
            result: ConfigFlowResult = await self.async_step_authorize(import_data)
        except AbortFlow as err:
            if err.reason != "already_configured":
                async_create_issue(
                    self.hass,
                    DOMAIN,
                    "deprecated_yaml_import_issue_{err.reason}",
                    breaks_in_ha_version="2024.11.0",
                    is_fixable=False,
                    issue_domain=DOMAIN,
                    severity=IssueSeverity.WARNING,
                    translation_key=f"deprecated_yaml_import_issue_{err.reason}",
                    translation_placeholders={
                        "domain": DOMAIN,
                        "integration_title": "LG Netcast",
                        "error_type": err.reason,
                    },
                )
            else:
                _create_issue()
            raise

        _create_issue()

        return result

    async def async_discover_client(self):
        """Handle Discovery step."""
        self.create_client()

        if TYPE_CHECKING:
            assert self.client is not None

        if self.device_config.get(CONF_ID):
            return

        try:
            details = await async_discover_netcast_details(self.hass, self.client)
        except LGNetCastDetailDiscoveryError as err:
            raise AbortFlow("cannot_connect") from err

        if (unique_id := details["uuid"]) is None:
            raise AbortFlow("invalid_host")

        self.device_config[CONF_ID] = unique_id
        self.device_config[CONF_MODEL] = details["model_name"]

        if CONF_NAME not in self.device_config:
            self.device_config[CONF_NAME] = details["friendly_name"] or DEFAULT_NAME

    async def async_step_authorize(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle Authorize step."""
        errors: dict[str, str] = {}
        self.async_stop_display_access_token()

        if user_input is not None and user_input.get(CONF_ACCESS_TOKEN) is not None:
            self.device_config[CONF_ACCESS_TOKEN] = user_input[CONF_ACCESS_TOKEN]

        await self.async_discover_client()
        assert self.client is not None

        await self.async_set_unique_id(self.device_config[CONF_ID])
        self._abort_if_unique_id_configured(
            updates={CONF_HOST: self.device_config[CONF_HOST]}
        )

        try:
            await self.hass.async_add_executor_job(
                self.client._get_session_id  # noqa: SLF001
            )
        except AccessTokenError:
            if user_input is not None:
                errors[CONF_ACCESS_TOKEN] = "invalid_access_token"
        except SessionIdError:
            errors["base"] = "cannot_connect"
        else:
            return await self.async_create_device()

        self._track_interval = async_track_time_interval(
            self.hass,
            self.async_display_access_token,
            DISPLAY_ACCESS_TOKEN_INTERVAL,
            cancel_on_shutdown=True,
        )

        return self.async_show_form(
            step_id="authorize",
            data_schema=vol.Schema(
                {
                    vol.Optional(CONF_ACCESS_TOKEN): vol.All(str, vol.Length(max=6)),
                }
            ),
            errors=errors,
        )

    async def async_display_access_token(self, _: datetime | None = None):
        """Display access token on screen."""
        assert self.client is not None
        with contextlib.suppress(AccessTokenError, SessionIdError):
            await self.hass.async_add_executor_job(
                self.client._get_session_id  # noqa: SLF001
            )

    @callback
    def async_remove(self):
        """Terminate Access token display if flow is removed."""
        self.async_stop_display_access_token()

    def async_stop_display_access_token(self):
        """Stop Access token request if running."""
        if self._track_interval is not None:
            self._track_interval()
            self._track_interval = None

    async def async_create_device(self) -> ConfigFlowResult:
        """Create LG Netcast TV Device from config."""
        assert self.client

        return self.async_create_entry(
            title=self.device_config[CONF_NAME], data=self.device_config
        )
