"""Config Flow for ICS Calendar."""

import logging
import re
from typing import Any, Dict, Optional, Self
from urllib.parse import quote

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import (
    CONF_EXCLUDE,
    CONF_INCLUDE,
    CONF_NAME,
    CONF_PASSWORD,
    CONF_PREFIX,
    CONF_URL,
    CONF_USERNAME,
)
from homeassistant.helpers.selector import selector

from . import (
    CONF_ACCEPT_HEADER,
    CONF_ADV_CONNECT_OPTS,
    CONF_CONNECTION_TIMEOUT,
    CONF_DAYS,
    CONF_DOWNLOAD_INTERVAL,
    CONF_INCLUDE_ALL_DAY,
    CONF_OFFSET_HOURS,
    CONF_PARSER,
    CONF_REQUIRES_AUTH,
    CONF_SET_TIMEOUT,
    CONF_SUMMARY_DEFAULT,
    CONF_USER_AGENT,
)
from .const import CONF_SUMMARY_DEFAULT_DEFAULT, DOMAIN

_LOGGER = logging.getLogger(__name__)

CALENDAR_NAME_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_NAME): cv.string,
        vol.Optional(CONF_DAYS, default=1): cv.positive_int,
        vol.Optional(CONF_INCLUDE_ALL_DAY, default=False): cv.boolean,
    }
)

CALENDAR_OPTS_SCHEMA = vol.Schema(
    {
        vol.Optional(CONF_EXCLUDE, default=""): cv.string,
        vol.Optional(CONF_INCLUDE, default=""): cv.string,
        vol.Optional(CONF_PREFIX, default=""): cv.string,
        vol.Optional(CONF_DOWNLOAD_INTERVAL, default=15): cv.positive_int,
        vol.Optional(CONF_OFFSET_HOURS, default=0): int,
        vol.Optional(CONF_PARSER, default="rie"): selector(
            {"select": {"options": ["rie", "ics"], "mode": "dropdown"}}
        ),
        vol.Optional(
            CONF_SUMMARY_DEFAULT, default=CONF_SUMMARY_DEFAULT_DEFAULT
        ): cv.string,
    }
)

CONNECT_OPTS_SCHEMA = vol.Schema(
    {
        vol.Required(CONF_URL): cv.string,
        vol.Optional(CONF_REQUIRES_AUTH, default=False): cv.boolean,
        vol.Optional(CONF_ADV_CONNECT_OPTS, default=False): cv.boolean,
    }
)

AUTH_OPTS_SCHEMA = vol.Schema(
    {
        vol.Optional(CONF_USERNAME, default=""): cv.string,
        vol.Optional(CONF_PASSWORD, default=""): cv.string,
    }
)

ADVANCED_CONNECT_OPTS_SCHEMA = vol.Schema(
    {
        vol.Optional(CONF_ACCEPT_HEADER, default=""): cv.string,
        vol.Optional(CONF_USER_AGENT, default=""): cv.string,
        vol.Optional(CONF_SET_TIMEOUT, default=False): cv.boolean,
    }
)

TIMEOUT_OPTS_SCHEMA = vol.Schema(
    {vol.Optional(CONF_CONNECTION_TIMEOUT, default=None): cv.positive_float}
)

FILTER_DOC_URL = (
    "https://github.com/franc6/ics_calendar/blob/releases/README.md#filters"
)


def is_array_string(arr_str: str) -> bool:
    """Return true if arr_str starts with [ and ends with ]."""
    return arr_str.startswith("[") and arr_str.endswith("]")


def format_url(url: str) -> str:
    """Format a URL using quote() and ensure any templates are not quoted."""
    is_quoted = bool(re.search("%[0-9A-Fa-f][0-9A-Fa-f]", url))
    if not is_quoted:
        year_match = re.search("\\{(year([-+][0-9]+)?)\\}", url)
        month_match = re.search("\\{(month([-+][0-9]+)?)\\}", url)
        has_template: bool = year_match or month_match
        url = quote(url, safe=":/?&=")
        if has_template:
            year_template = year_match.group(1)
            month_template = month_match.group(1)
            year_template1 = year_template.replace("+", "%2[Bb]")
            month_template1 = month_template.replace("+", "%2[Bb]")
            url = re.sub(
                f"%7[Bb]{year_template1}%7[Dd]",
                f"{{{year_template}}}",
                url,
            )
            url = re.sub(
                f"%7[Bb]{month_template1}%7[Dd]",
                f"{{{month_template}}}",
                url,
            )
    if url.startswith("webcal://"):
        url = re.sub("^webcal://", "https://", url)

    return url


class ICSCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
    """Config Flow for ICS Calendar."""

    VERSION = 1
    MINOR_VERSION = 0

    data: Optional[Dict[str, Any]]

    def __init__(self):
        """Construct ICSCalendarConfigFlow."""
        self.data = {}

    def is_matching(self, _other_flow: Self) -> bool:
        """Match discovery method.

        This method doesn't do anything, because this integration has no
        discoverable components.
        """
        return False

    async def async_step_reauth(self, user_input=None):
        """Re-authenticateon auth error."""
        # self.reauth_entry = self.hass.config_entries.async_get_entry(
        # self.context["entry_id"]
        # )
        return await self.async_step_reauth_confirm(user_input)

    async def async_step_reauth_confirm(
        self, user_input=None
    ) -> ConfigFlowResult:
        """Dialog to inform user that reauthentication is required."""
        if user_input is None:
            return self.async_show_form(
                step_id="reauth_confirm", data_schema=vol.Schema({})
            )
        return await self.async_step_user()

    # Don't allow reconfigure for now!
    # async def async_step_reconfigure(
    #    self, user_input: dict[str, Any] | None = None
    # ) -> ConfigFlowResult:
    #    """Reconfigure entry."""
    #    return await self.async_step_user(user_input)

    async def async_step_user(
        self, user_input: Optional[Dict[str, Any]] = None
    ) -> ConfigFlowResult:
        """Start of Config Flow."""
        errors = {}
        if user_input is not None:
            user_input[CONF_NAME] = user_input[CONF_NAME].strip()
            if not user_input[CONF_NAME]:
                errors[CONF_NAME] = "empty_name"
            else:
                self._async_abort_entries_match(
                    {CONF_NAME: user_input[CONF_NAME]}
                )

            if not errors:
                self.data = user_input
                return await self.async_step_calendar_opts()

        return self.async_show_form(
            step_id="user",
            data_schema=CALENDAR_NAME_SCHEMA,
            errors=errors,
            last_step=False,
        )

    async def async_step_calendar_opts(  # noqa: R701,C901
        self, user_input: Optional[Dict[str, Any]] = None
    ):
        """Calendar Options step for ConfigFlow."""
        errors = {}
        if user_input is not None:
            user_input[CONF_EXCLUDE] = user_input[CONF_EXCLUDE].strip()
            user_input[CONF_INCLUDE] = user_input[CONF_INCLUDE].strip()
            if (
                user_input[CONF_EXCLUDE]
                and user_input[CONF_EXCLUDE] == user_input[CONF_INCLUDE]
            ):
                errors[CONF_EXCLUDE] = "exclude_include_cannot_be_the_same"
            else:
                if user_input[CONF_EXCLUDE] and not is_array_string(
                    user_input[CONF_EXCLUDE]
                ):
                    errors[CONF_EXCLUDE] = "exclude_must_be_array"
                if user_input[CONF_INCLUDE] and not is_array_string(
                    user_input[CONF_INCLUDE]
                ):
                    errors[CONF_INCLUDE] = "include_must_be_array"

            if user_input[CONF_DOWNLOAD_INTERVAL] < 15:
                _LOGGER.error("download_interval_too_small error")
                errors[CONF_DOWNLOAD_INTERVAL] = "download_interval_too_small"

            if not user_input[CONF_SUMMARY_DEFAULT]:
                user_input[CONF_SUMMARY_DEFAULT] = CONF_SUMMARY_DEFAULT_DEFAULT

            if not errors:
                self.data.update(user_input)
                return await self.async_step_connect_opts()

        return self.async_show_form(
            step_id="calendar_opts",
            data_schema=CALENDAR_OPTS_SCHEMA,
            errors=errors,
            last_step=False,
            description_placeholders={"filterdoc": FILTER_DOC_URL},
        )

    async def async_step_connect_opts(
        self, user_input: Optional[Dict[str, Any]] = None
    ):
        """Connect Options step for ConfigFlow."""
        errors = {}
        if user_input is not None:
            user_input[CONF_URL] = user_input[CONF_URL].strip()
            if not user_input[CONF_URL]:
                errors[CONF_URL] = "empty_url"

            if not errors:
                user_input[CONF_URL] = format_url(user_input[CONF_URL])

                self.data.update(user_input)
                if user_input.get(CONF_REQUIRES_AUTH, False):
                    return await self.async_step_auth_opts()
                if user_input.get(CONF_ADV_CONNECT_OPTS, False):
                    return await self.async_step_adv_connect_opts()
                return self.async_create_entry(
                    title=self.data[CONF_NAME],
                    data=self.data,
                )

        return self.async_show_form(
            step_id="connect_opts",
            data_schema=CONNECT_OPTS_SCHEMA,
            errors=errors,
        )

    async def async_step_auth_opts(
        self, user_input: Optional[Dict[str, Any]] = None
    ):
        """Auth Options step for ConfigFlow."""
        if user_input is not None:
            self.data.update(user_input)
            if self.data.get(CONF_ADV_CONNECT_OPTS, False):
                return await self.async_step_adv_connect_opts()
            return self.async_create_entry(
                title=self.data[CONF_NAME],
                data=self.data,
            )

        return self.async_show_form(
            step_id="auth_opts", data_schema=AUTH_OPTS_SCHEMA
        )

    async def async_step_adv_connect_opts(
        self, user_input: Optional[Dict[str, Any]] = None
    ):
        """Advanced Connection Options step for ConfigFlow."""
        errors = {}
        if user_input is not None:

            # if not errors:
            self.data.update(user_input)
            if user_input.get(CONF_SET_TIMEOUT, False):
                return await self.async_step_timeout_opts()
            return self.async_create_entry(
                title=self.data[CONF_NAME],
                data=self.data,
            )

        return self.async_show_form(
            step_id="adv_connect_opts",
            data_schema=ADVANCED_CONNECT_OPTS_SCHEMA,
            errors=errors,
        )

    async def async_step_timeout_opts(
        self, user_input: Optional[Dict[str, Any]] = None
    ):
        """Timeout Options step for ConfigFlow."""
        errors = {}
        if user_input is not None:

            # if not errors:
            self.data.update(user_input)
            return self.async_create_entry(
                title=self.data[CONF_NAME],
                data=self.data,
            )

        return self.async_show_form(
            step_id="timeout_opts",
            data_schema=TIMEOUT_OPTS_SCHEMA,
            errors=errors,
            last_step=True,
        )

    async def async_step_import(self, import_data):
        """Import config from configuration.yaml."""
        return self.async_create_entry(
            title=import_data[CONF_NAME],
            data=import_data,
        )
