import cherrypy
import threading
import logging
from typing import TYPE_CHECKING

from cephadm.agent import AgentEndpoint
from cephadm.service_discovery import ServiceDiscovery
from mgr_util import test_port_allocation, PortAlreadyInUse
from orchestrator import OrchestratorError

if TYPE_CHECKING:
    from cephadm.module import CephadmOrchestrator


def cherrypy_filter(record: logging.LogRecord) -> int:
    blocked = [
        'TLSV1_ALERT_DECRYPT_ERROR'
    ]
    msg = record.getMessage()
    return not any([m for m in blocked if m in msg])


logging.getLogger('cherrypy.error').addFilter(cherrypy_filter)
cherrypy.log.access_log.propagate = False


class CephadmHttpServer(threading.Thread):
    def __init__(self, mgr: "CephadmOrchestrator") -> None:
        self.mgr = mgr
        self.agent = AgentEndpoint(mgr)
        self.service_discovery = ServiceDiscovery(mgr)
        self.cherrypy_shutdown_event = threading.Event()
        self._service_discovery_port = self.mgr.service_discovery_port
        self.secure_monitoring_stack = self.mgr.secure_monitoring_stack
        super().__init__(target=self.run)

    def configure_cherrypy(self) -> None:
        cherrypy.config.update({
            'environment': 'production',
            'engine.autoreload.on': False,
        })

    def configure(self) -> None:
        self.configure_cherrypy()
        self.agent.configure()
        self.service_discovery.configure(self.mgr.service_discovery_port,
                                         self.mgr.get_mgr_ip(),
                                         self.secure_monitoring_stack)

    def config_update(self) -> None:
        self.service_discovery_port = self.mgr.service_discovery_port
        if self.secure_monitoring_stack != self.mgr.secure_monitoring_stack:
            self.secure_monitoring_stack = self.mgr.secure_monitoring_stack
            self.restart()

    @property
    def service_discovery_port(self) -> int:
        return self._service_discovery_port

    @service_discovery_port.setter
    def service_discovery_port(self, value: int) -> None:
        if self._service_discovery_port == value:
            return

        try:
            test_port_allocation(self.mgr.get_mgr_ip(), value)
        except PortAlreadyInUse:
            raise OrchestratorError(f'Service discovery port {value} is already in use. Listening on old port {self._service_discovery_port}.')
        except Exception as e:
            raise OrchestratorError(f'Cannot check service discovery port ip:{self.mgr.get_mgr_ip()} port:{value} error:{e}')

        self.mgr.log.info(f'Changing service discovery port from {self._service_discovery_port} to {value}...')
        self._service_discovery_port = value
        self.restart()

    def restart(self) -> None:
        cherrypy.engine.stop()
        cherrypy.server.httpserver = None
        self.configure()
        cherrypy.engine.start()

    def run(self) -> None:
        try:
            self.mgr.log.debug('Starting cherrypy engine...')
            self.configure()
            cherrypy.server.unsubscribe()  # disable default server
            cherrypy.engine.start()
            self.mgr.log.debug('Cherrypy engine started.')
            self.mgr._kick_serve_loop()
            # wait for the shutdown event
            self.cherrypy_shutdown_event.wait()
            self.cherrypy_shutdown_event.clear()
            cherrypy.engine.stop()
            cherrypy.server.httpserver = None
            self.mgr.log.debug('Cherrypy engine stopped.')
        except Exception as e:
            self.mgr.log.error(f'Failed to run cephadm http server: {e}')

    def shutdown(self) -> None:
        self.mgr.log.debug('Stopping cherrypy engine...')
        self.cherrypy_shutdown_event.set()
