"""Config flow for InfluxDB integration."""

import logging
from pathlib import Path
import shutil
from typing import Any, override

import voluptuous as vol
from yarl import URL

from homeassistant.components.file_upload import process_uploaded_file
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import (
    CONF_HOST,
    CONF_PASSWORD,
    CONF_PATH,
    CONF_PORT,
    CONF_SSL,
    CONF_TOKEN,
    CONF_URL,
    CONF_USERNAME,
    CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.selector import (
    FileSelector,
    FileSelectorConfig,
    TextSelector,
    TextSelectorConfig,
    TextSelectorType,
)
from homeassistant.helpers.storage import STORAGE_DIR

from . import DOMAIN, create_influx_url, get_influx_connection
from .const import (
    API_VERSION_2,
    CONF_API_VERSION,
    CONF_BUCKET,
    CONF_DB_NAME,
    CONF_ORG,
    CONF_SSL_CA_CERT,
    DEFAULT_API_VERSION,
    DEFAULT_BUCKET,
    DEFAULT_DATABASE,
    DEFAULT_HOST,
    DEFAULT_PORT,
    DEFAULT_VERIFY_SSL,
)

_LOGGER = logging.getLogger(__name__)

INFLUXDB_V1_SCHEMA = vol.Schema(
    {
        vol.Required(
            CONF_URL, default=f"http://{DEFAULT_HOST}:{DEFAULT_PORT}"
        ): TextSelector(
            TextSelectorConfig(
                type=TextSelectorType.URL,
                autocomplete="url",
            ),
        ),
        vol.Required(CONF_VERIFY_SSL, default=False): bool,
        vol.Required(CONF_DB_NAME): TextSelector(
            TextSelectorConfig(
                type=TextSelectorType.TEXT,
            ),
        ),
        vol.Optional(CONF_USERNAME): TextSelector(
            TextSelectorConfig(
                type=TextSelectorType.TEXT,
                autocomplete="username",
            ),
        ),
        vol.Optional(CONF_PASSWORD): TextSelector(
            TextSelectorConfig(
                type=TextSelectorType.PASSWORD,
                autocomplete="current-password",
            ),
        ),
        vol.Optional(CONF_SSL_CA_CERT): FileSelector(
            FileSelectorConfig(accept=".pem,.crt,.cer,.der")
        ),
    }
)

INFLUXDB_V2_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_URL, default="https://"): TextSelector(
            TextSelectorConfig(
                type=TextSelectorType.URL,
                autocomplete="url",
            ),
        ),
        vol.Required(CONF_VERIFY_SSL, default=False): bool,
        vol.Required(CONF_ORG): TextSelector(
            TextSelectorConfig(
                type=TextSelectorType.TEXT,
            ),
        ),
        vol.Required(CONF_BUCKET): TextSelector(
            TextSelectorConfig(
                type=TextSelectorType.TEXT,
            ),
        ),
        vol.Required(CONF_TOKEN): TextSelector(
            TextSelectorConfig(
                type=TextSelectorType.PASSWORD,
            ),
        ),
        vol.Optional(CONF_SSL_CA_CERT): FileSelector(
            FileSelectorConfig(accept=".pem,.crt,.cer,.der")
        ),
    }
)


async def _validate_influxdb_connection(
    hass: HomeAssistant, data: dict[str, Any]
) -> dict[str, str]:
    """Validate connection to influxdb."""

    def _test_connection() -> None:
        influx = get_influx_connection(data, test_write=True)
        influx.close()

    errors = {}

    try:
        await hass.async_add_executor_job(_test_connection)
    except ConnectionError as ex:
        _LOGGER.error(ex)
        if "SSLError" in ex.args[0]:
            errors = {"base": "ssl_error"}
        elif "database not found" in ex.args[0]:
            errors = {"base": "invalid_database"}
        elif "authorization failed" in ex.args[0]:
            errors = {"base": "invalid_auth"}
        elif "token" in ex.args[0]:
            errors = {"base": "invalid_config"}
        else:
            errors = {"base": "cannot_connect"}
    except Exception:
        _LOGGER.exception("Unknown error")
        errors = {"base": "unknown"}

    return errors


async def _save_uploaded_cert_file(hass: HomeAssistant, uploaded_file_id: str) -> Path:
    """Move the uploaded file to storage directory."""

    def _process_upload() -> Path:
        with process_uploaded_file(hass, uploaded_file_id) as file_path:
            dest_path = Path(hass.config.path(STORAGE_DIR, DOMAIN))
            dest_path.mkdir(exist_ok=True)
            file_name = f"influxdb{file_path.suffix}"
            dest_file = dest_path / file_name
            shutil.move(file_path, dest_file)
        return dest_file

    return await hass.async_add_executor_job(_process_upload)


class InfluxDBConfigFlow(ConfigFlow, domain=DOMAIN):
    """Handle a config flow for InfluxDB."""

    @override
    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Step when user initializes an integration."""
        return self.async_show_menu(
            step_id="user",
            menu_options=["configure_v1", "configure_v2"],
        )

    async def async_step_configure_v1(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Step when user configures InfluxDB v1."""
        errors: dict[str, str] = {}

        if user_input is not None:
            url = URL(user_input[CONF_URL])
            data = {
                CONF_API_VERSION: DEFAULT_API_VERSION,
                CONF_HOST: url.host,
                CONF_PORT: url.port,
                CONF_USERNAME: user_input.get(CONF_USERNAME),
                CONF_PASSWORD: user_input.get(CONF_PASSWORD),
                CONF_DB_NAME: user_input[CONF_DB_NAME],
                CONF_SSL: url.scheme == "https",
                CONF_PATH: url.path,
                CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
            }
            if (cert := user_input.get(CONF_SSL_CA_CERT)) is not None:
                path = await _save_uploaded_cert_file(self.hass, cert)
                data[CONF_SSL_CA_CERT] = str(path)
            errors = await _validate_influxdb_connection(self.hass, data)

            if not errors:
                title = f"{data[CONF_DB_NAME]} ({data[CONF_HOST]})"
                return self.async_create_entry(title=title, data=data)

        schema = INFLUXDB_V1_SCHEMA

        return self.async_show_form(
            step_id="configure_v1",
            data_schema=self.add_suggested_values_to_schema(schema, user_input),
            errors=errors,
        )

    async def async_step_configure_v2(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Step when user configures InfluxDB v2."""
        errors: dict[str, str] = {}

        if user_input is not None:
            data = {
                CONF_API_VERSION: API_VERSION_2,
                CONF_URL: user_input[CONF_URL],
                CONF_TOKEN: user_input[CONF_TOKEN],
                CONF_ORG: user_input[CONF_ORG],
                CONF_BUCKET: user_input[CONF_BUCKET],
                CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
            }
            if (cert := user_input.get(CONF_SSL_CA_CERT)) is not None:
                path = await _save_uploaded_cert_file(self.hass, cert)
                data[CONF_SSL_CA_CERT] = str(path)
            errors = await _validate_influxdb_connection(self.hass, data)

            if not errors:
                title = f"{data[CONF_BUCKET]} ({data[CONF_URL]})"
                return self.async_create_entry(title=title, data=data)

        schema = INFLUXDB_V2_SCHEMA

        return self.async_show_form(
            step_id="configure_v2",
            data_schema=self.add_suggested_values_to_schema(schema, user_input),
            errors=errors,
        )

    async def async_step_reconfigure(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle reconfiguration."""
        entry = self._get_reconfigure_entry()
        if entry.data[CONF_API_VERSION] == API_VERSION_2:
            return await self.async_step_reconfigure_v2(user_input)
        return await self.async_step_reconfigure_v1(user_input)

    async def async_step_reconfigure_v1(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle reconfiguration of InfluxDB v1."""
        errors: dict[str, str] = {}
        entry = self._get_reconfigure_entry()

        if user_input is not None:
            url = URL(user_input[CONF_URL])
            data = {
                CONF_API_VERSION: DEFAULT_API_VERSION,
                CONF_HOST: url.host,
                CONF_PORT: url.port,
                CONF_USERNAME: user_input.get(CONF_USERNAME),
                CONF_PASSWORD: user_input.get(CONF_PASSWORD),
                CONF_DB_NAME: user_input[CONF_DB_NAME],
                CONF_SSL: url.scheme == "https",
                CONF_PATH: url.path,
                CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
            }
            if (cert := user_input.get(CONF_SSL_CA_CERT)) is not None:
                path = await _save_uploaded_cert_file(self.hass, cert)
                data[CONF_SSL_CA_CERT] = str(path)
            elif CONF_SSL_CA_CERT in entry.data:
                data[CONF_SSL_CA_CERT] = entry.data[CONF_SSL_CA_CERT]
            errors = await _validate_influxdb_connection(self.hass, data)

            if not errors:
                title = f"{data[CONF_DB_NAME]} ({data[CONF_HOST]})"
                return self.async_update_reload_and_abort(
                    entry, title=title, data_updates=data
                )

        suggested_values = dict(entry.data) | (user_input or {})
        if user_input is None:
            suggested_values[CONF_URL] = str(
                URL.build(
                    scheme="https" if entry.data.get(CONF_SSL) else "http",
                    host=entry.data.get(CONF_HOST, ""),
                    port=entry.data.get(CONF_PORT),
                    path=""
                    if entry.data.get(CONF_PATH) is None
                    else entry.data[CONF_PATH],
                )
            )

        return self.async_show_form(
            step_id="reconfigure_v1",
            data_schema=self.add_suggested_values_to_schema(
                INFLUXDB_V1_SCHEMA, suggested_values
            ),
            errors=errors,
        )

    async def async_step_reconfigure_v2(
        self, user_input: dict[str, Any] | None = None
    ) -> ConfigFlowResult:
        """Handle reconfiguration of InfluxDB v2."""
        errors: dict[str, str] = {}
        entry = self._get_reconfigure_entry()

        if user_input is not None:
            data = {
                CONF_API_VERSION: API_VERSION_2,
                CONF_URL: user_input[CONF_URL],
                CONF_TOKEN: user_input[CONF_TOKEN],
                CONF_ORG: user_input[CONF_ORG],
                CONF_BUCKET: user_input[CONF_BUCKET],
                CONF_VERIFY_SSL: user_input[CONF_VERIFY_SSL],
            }
            if (cert := user_input.get(CONF_SSL_CA_CERT)) is not None:
                path = await _save_uploaded_cert_file(self.hass, cert)
                data[CONF_SSL_CA_CERT] = str(path)
            elif CONF_SSL_CA_CERT in entry.data:
                data[CONF_SSL_CA_CERT] = entry.data[CONF_SSL_CA_CERT]
            errors = await _validate_influxdb_connection(self.hass, data)

            if not errors:
                title = f"{data[CONF_BUCKET]} ({data[CONF_URL]})"
                return self.async_update_reload_and_abort(
                    entry, title=title, data_updates=data
                )

        return self.async_show_form(
            step_id="reconfigure_v2",
            data_schema=self.add_suggested_values_to_schema(
                INFLUXDB_V2_SCHEMA, entry.data | (user_input or {})
            ),
            errors=errors,
        )

    async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
        """Handle the initial step."""
        import_data = {**import_data}
        import_data.setdefault(CONF_API_VERSION, DEFAULT_API_VERSION)
        import_data.setdefault(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)
        import_data.setdefault(CONF_DB_NAME, DEFAULT_DATABASE)
        import_data.setdefault(CONF_BUCKET, DEFAULT_BUCKET)

        api_version = import_data[CONF_API_VERSION]

        if api_version == DEFAULT_API_VERSION:
            host = import_data.get(CONF_HOST, DEFAULT_HOST)
            database = import_data[CONF_DB_NAME]
            title = f"{database} ({host})"
            data = {
                CONF_API_VERSION: api_version,
                CONF_HOST: host,
                CONF_PORT: import_data.get(CONF_PORT),
                CONF_USERNAME: import_data.get(CONF_USERNAME),
                CONF_PASSWORD: import_data.get(CONF_PASSWORD),
                CONF_DB_NAME: database,
                CONF_SSL: import_data.get(CONF_SSL),
                CONF_PATH: import_data.get(CONF_PATH),
                CONF_VERIFY_SSL: import_data[CONF_VERIFY_SSL],
                CONF_SSL_CA_CERT: import_data.get(CONF_SSL_CA_CERT),
            }
        else:
            create_influx_url(import_data)  # Only modifies dict for api_version == 2
            bucket = import_data[CONF_BUCKET]
            url = import_data.get(CONF_URL)
            title = f"{bucket} ({url})"
            data = {
                CONF_API_VERSION: api_version,
                CONF_URL: url,
                CONF_TOKEN: import_data.get(CONF_TOKEN),
                CONF_ORG: import_data.get(CONF_ORG),
                CONF_BUCKET: bucket,
                CONF_VERIFY_SSL: import_data[CONF_VERIFY_SSL],
                CONF_SSL_CA_CERT: import_data.get(CONF_SSL_CA_CERT),
            }

        errors = await _validate_influxdb_connection(self.hass, data)
        if errors:
            return self.async_abort(reason=errors["base"])

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