"""Run Home Assistant."""
import asyncio
from concurrent.futures import ThreadPoolExecutor
import dataclasses
import logging
import sys
import threading
from typing import Any, Dict, Optional

from homeassistant import bootstrap
from homeassistant.core import callback
from homeassistant.helpers.frame import warn_use

#
# Python 3.8 has significantly less workers by default
# than Python 3.7.  In order to be consistent between
# supported versions, we need to set max_workers.
#
# In most cases the workers are not I/O bound, as they
# are sleeping/blocking waiting for data from integrations
# updating so this number should be higher than the default
# use case.
#
MAX_EXECUTOR_WORKERS = 64


@dataclasses.dataclass
class RuntimeConfig:
    """Class to hold the information for running Home Assistant."""

    config_dir: str
    skip_pip: bool = False
    safe_mode: bool = False

    verbose: bool = False

    log_rotate_days: Optional[int] = None
    log_file: Optional[str] = None
    log_no_color: bool = False

    debug: bool = False
    open_ui: bool = False


# In Python 3.8+ proactor policy is the default on Windows
if sys.platform == "win32" and sys.version_info[:2] < (3, 8):
    PolicyBase = asyncio.WindowsProactorEventLoopPolicy
else:
    PolicyBase = asyncio.DefaultEventLoopPolicy  # pylint: disable=invalid-name


class HassEventLoopPolicy(PolicyBase):  # type: ignore
    """Event loop policy for Home Assistant."""

    def __init__(self, debug: bool) -> None:
        """Init the event loop policy."""
        super().__init__()
        self.debug = debug

    @property
    def loop_name(self) -> str:
        """Return name of the loop."""
        return self._loop_factory.__name__  # type: ignore

    def new_event_loop(self) -> asyncio.AbstractEventLoop:
        """Get the event loop."""
        loop: asyncio.AbstractEventLoop = super().new_event_loop()
        loop.set_exception_handler(_async_loop_exception_handler)
        if self.debug:
            loop.set_debug(True)

        executor = ThreadPoolExecutor(
            thread_name_prefix="SyncWorker", max_workers=MAX_EXECUTOR_WORKERS
        )
        loop.set_default_executor(executor)
        loop.set_default_executor = warn_use(  # type: ignore
            loop.set_default_executor, "sets default executor on the event loop"
        )

        # Python 3.9+
        if hasattr(loop, "shutdown_default_executor"):
            return loop

        # Copied from Python 3.9 source
        def _do_shutdown(future: asyncio.Future) -> None:
            try:
                executor.shutdown(wait=True)
                loop.call_soon_threadsafe(future.set_result, None)
            except Exception as ex:  # pylint: disable=broad-except
                loop.call_soon_threadsafe(future.set_exception, ex)

        async def shutdown_default_executor() -> None:
            """Schedule the shutdown of the default executor."""
            future = loop.create_future()
            thread = threading.Thread(target=_do_shutdown, args=(future,))
            thread.start()
            try:
                await future
            finally:
                thread.join()

        setattr(loop, "shutdown_default_executor", shutdown_default_executor)

        return loop


@callback
def _async_loop_exception_handler(_: Any, context: Dict) -> None:
    """Handle all exception inside the core loop."""
    kwargs = {}
    exception = context.get("exception")
    if exception:
        kwargs["exc_info"] = (type(exception), exception, exception.__traceback__)

    logging.getLogger(__package__).error(
        "Error doing job: %s", context["message"], **kwargs  # type: ignore
    )


async def setup_and_run_hass(runtime_config: RuntimeConfig,) -> int:
    """Set up Home Assistant and run."""
    hass = await bootstrap.async_setup_hass(runtime_config)

    if hass is None:
        return 1

    return await hass.async_run()


def run(runtime_config: RuntimeConfig) -> int:
    """Run Home Assistant."""
    asyncio.set_event_loop_policy(HassEventLoopPolicy(runtime_config.debug))
    return asyncio.run(setup_and_run_hass(runtime_config))
