"""Minio component."""
from __future__ import annotations

import logging
import os
from queue import Queue
import threading

import voluptuous as vol

from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType

from .minio_helper import MinioEventThread, create_minio_client

_LOGGER = logging.getLogger(__name__)

DOMAIN = "minio"
CONF_HOST = "host"
CONF_PORT = "port"
CONF_ACCESS_KEY = "access_key"
CONF_SECRET_KEY = "secret_key"
CONF_SECURE = "secure"
CONF_LISTEN = "listen"
CONF_LISTEN_BUCKET = "bucket"
CONF_LISTEN_PREFIX = "prefix"
CONF_LISTEN_SUFFIX = "suffix"
CONF_LISTEN_EVENTS = "events"

ATTR_BUCKET = "bucket"
ATTR_KEY = "key"
ATTR_FILE_PATH = "file_path"

DEFAULT_LISTEN_PREFIX = ""
DEFAULT_LISTEN_SUFFIX = ".*"
DEFAULT_LISTEN_EVENTS = "s3:ObjectCreated:*"

CONFIG_SCHEMA = vol.Schema(
    {
        DOMAIN: vol.Schema(
            {
                vol.Required(CONF_HOST): cv.string,
                vol.Required(CONF_PORT): cv.port,
                vol.Required(CONF_ACCESS_KEY): cv.string,
                vol.Required(CONF_SECRET_KEY): cv.string,
                vol.Required(CONF_SECURE): cv.boolean,
                vol.Optional(CONF_LISTEN, default=[]): vol.All(
                    cv.ensure_list,
                    [
                        vol.Schema(
                            {
                                vol.Required(CONF_LISTEN_BUCKET): cv.string,
                                vol.Optional(
                                    CONF_LISTEN_PREFIX, default=DEFAULT_LISTEN_PREFIX
                                ): cv.string,
                                vol.Optional(
                                    CONF_LISTEN_SUFFIX, default=DEFAULT_LISTEN_SUFFIX
                                ): cv.string,
                                vol.Optional(
                                    CONF_LISTEN_EVENTS, default=DEFAULT_LISTEN_EVENTS
                                ): cv.string,
                            }
                        )
                    ],
                ),
            }
        )
    },
    extra=vol.ALLOW_EXTRA,
)

BUCKET_KEY_SCHEMA = vol.Schema(
    {vol.Required(ATTR_BUCKET): cv.template, vol.Required(ATTR_KEY): cv.template}
)

BUCKET_KEY_FILE_SCHEMA = BUCKET_KEY_SCHEMA.extend(
    {vol.Required(ATTR_FILE_PATH): cv.template}
)


def setup(hass: HomeAssistant, config: ConfigType) -> bool:
    """Set up MinioClient and event listeners."""
    conf = config[DOMAIN]

    host = conf[CONF_HOST]
    port = conf[CONF_PORT]
    access_key = conf[CONF_ACCESS_KEY]
    secret_key = conf[CONF_SECRET_KEY]
    secure = conf[CONF_SECURE]

    queue_listener = QueueListener(hass)
    queue = queue_listener.queue

    hass.bus.listen_once(EVENT_HOMEASSISTANT_START, queue_listener.start_handler)
    hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, queue_listener.stop_handler)

    def _setup_listener(listener_conf):
        bucket = listener_conf[CONF_LISTEN_BUCKET]
        prefix = listener_conf[CONF_LISTEN_PREFIX]
        suffix = listener_conf[CONF_LISTEN_SUFFIX]
        events = listener_conf[CONF_LISTEN_EVENTS]

        minio_listener = MinioListener(
            queue,
            get_minio_endpoint(host, port),
            access_key,
            secret_key,
            secure,
            bucket,
            prefix,
            suffix,
            events,
        )

        hass.bus.listen_once(EVENT_HOMEASSISTANT_START, minio_listener.start_handler)
        hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, minio_listener.stop_handler)

    for listen_conf in conf[CONF_LISTEN]:
        _setup_listener(listen_conf)

    minio_client = create_minio_client(
        get_minio_endpoint(host, port), access_key, secret_key, secure
    )

    def _render_service_value(service, key):
        value = service.data[key]
        value.hass = hass
        return value.async_render(parse_result=False)

    def put_file(service: ServiceCall) -> None:
        """Upload file service."""
        bucket = _render_service_value(service, ATTR_BUCKET)
        key = _render_service_value(service, ATTR_KEY)
        file_path = _render_service_value(service, ATTR_FILE_PATH)

        if not hass.config.is_allowed_path(file_path):
            _LOGGER.error("Invalid file_path %s", file_path)
            return

        minio_client.fput_object(bucket, key, file_path)

    def get_file(service: ServiceCall) -> None:
        """Download file service."""
        bucket = _render_service_value(service, ATTR_BUCKET)
        key = _render_service_value(service, ATTR_KEY)
        file_path = _render_service_value(service, ATTR_FILE_PATH)

        if not hass.config.is_allowed_path(file_path):
            _LOGGER.error("Invalid file_path %s", file_path)
            return

        minio_client.fget_object(bucket, key, file_path)

    def remove_file(service: ServiceCall) -> None:
        """Delete file service."""
        bucket = _render_service_value(service, ATTR_BUCKET)
        key = _render_service_value(service, ATTR_KEY)

        minio_client.remove_object(bucket, key)

    hass.services.register(DOMAIN, "put", put_file, schema=BUCKET_KEY_FILE_SCHEMA)
    hass.services.register(DOMAIN, "get", get_file, schema=BUCKET_KEY_FILE_SCHEMA)
    hass.services.register(DOMAIN, "remove", remove_file, schema=BUCKET_KEY_SCHEMA)

    return True


def get_minio_endpoint(host: str, port: int) -> str:
    """Create minio endpoint from host and port."""
    return f"{host}:{port}"


class QueueListener(threading.Thread):
    """Forward events from queue into Home Assistant event bus."""

    def __init__(self, hass):
        """Create queue."""
        super().__init__()
        self._hass = hass
        self._queue = Queue()

    def run(self):
        """Listen to queue events, and forward them to Home Assistant event bus."""
        _LOGGER.info("Running QueueListener")
        while True:
            if (event := self._queue.get()) is None:
                break

            _, file_name = os.path.split(event[ATTR_KEY])

            _LOGGER.debug(
                "Sending event %s, %s, %s",
                event["event_name"],
                event[ATTR_BUCKET],
                event[ATTR_KEY],
            )
            self._hass.bus.fire(DOMAIN, {"file_name": file_name, **event})

    @property
    def queue(self):
        """Return wrapped queue."""
        return self._queue

    def stop(self):
        """Stop run by putting None into queue and join the thread."""
        _LOGGER.info("Stopping QueueListener")
        self._queue.put(None)
        self.join()
        _LOGGER.info("Stopped QueueListener")

    def start_handler(self, _):
        """Start handler helper method."""
        self.start()

    def stop_handler(self, _):
        """Stop handler helper method."""
        self.stop()


class MinioListener:
    """MinioEventThread wrapper with helper methods."""

    def __init__(
        self,
        queue: Queue,
        endpoint: str,
        access_key: str,
        secret_key: str,
        secure: bool,
        bucket_name: str,
        prefix: str,
        suffix: str,
        events: list[str],
    ) -> None:
        """Create Listener."""
        self._queue = queue
        self._endpoint = endpoint
        self._access_key = access_key
        self._secret_key = secret_key
        self._secure = secure
        self._bucket_name = bucket_name
        self._prefix = prefix
        self._suffix = suffix
        self._events = events
        self._minio_event_thread = None

    def start_handler(self, _):
        """Create and start the event thread."""
        self._minio_event_thread = MinioEventThread(
            self._queue,
            self._endpoint,
            self._access_key,
            self._secret_key,
            self._secure,
            self._bucket_name,
            self._prefix,
            self._suffix,
            self._events,
        )
        self._minio_event_thread.start()

    def stop_handler(self, _):
        """Issue stop and wait for thread to join."""
        if self._minio_event_thread is not None:
            self._minio_event_thread.stop()
