import contextlib
import http.client as httplib
import logging
import socket
import ssl
import sys
from logging import LogRecord
from pathlib import Path

import pygelf


# from https://github.com/keeprocking/pygelf/blob/master/pygelf/handlers.py
class GelfHttpsHandler(logging.Handler, pygelf.handlers.BaseHandler):
    def __init__(  # noqa: PLR0913
        self,
        host: str,
        port: int,
        compress: bool = True,
        path: str = "/gelf",
        timeout: int = 5,
        validate: bool = False,
        ca_certs: str | None = None,
        certfile: str | None = None,
        keyfile: str | None = None,
        keyfile_password: str | None = None,
        **kwargs,  # noqa: ANN003
    ) -> None:

        logging.Handler.__init__(self)
        pygelf.handlers.BaseHandler.__init__(self, compress=compress, **kwargs)

        self.host = host
        self.port = port
        self.path = path
        self.timeout = timeout
        self.headers = {}
        self.ca_certs = ca_certs
        self.keyfile = keyfile
        self.certfile = certfile
        self.keyfile_password = keyfile_password
        self.context = ssl.create_default_context()

        if validate and ca_certs is None:
            msg = "CA bundle file path must be specified"
            raise ValueError(msg)

        if not validate:
            self.context.check_hostname = False
            self.context.verify_mode = ssl.CERT_NONE
        else:
            # Load our CA file
            self.context.load_verify_locations(cafile=self.ca_certs)

        if compress:
            self.headers["Content-Encoding"] = "gzip,deflate"

    def emit(self, record: LogRecord) -> None:
        connection = None
        try:
            data = self.convert_record_to_gelf(record)
            connection = httplib.HTTPSConnection(
                host=self.host,
                port=self.port,
                context=self.context,
                timeout=self.timeout,
            )
            connection.request("POST", self.path, data, self.headers)

            # Ensure the request completes and the socket can be cleanly reused/closed
            resp = connection.getresponse()
            resp.read()

        except (socket.gaierror, OSError, TimeoutError, ssl.SSLError):
            # Network down / DNS fail / timeouts / TLS issues during disconnects
            # -> drop silently
            return
        except Exception:
            self.handleError(record)
        finally:
            if connection is not None:
                with contextlib.suppress(Exception):
                    connection.close()


class ErrorLoggingFilter(logging.Filter):
    def filter(self, record: LogRecord) -> bool:
        return record.levelno == logging.ERROR


class InfoLoggingFilter(logging.Filter):
    def filter(self, record: LogRecord) -> bool:
        return record.levelno in (logging.INFO, logging.ERROR)


class DebugLoggingFilter(logging.Filter):
    def filter(self, record: LogRecord) -> bool:
        return record.levelno in (logging.DEBUG, logging.INFO, logging.ERROR)


def init_logging(output_directory: Path, console_logging_level: int) -> None:
    logger = logging.getLogger()

    # Prevent duplicate handlers if init_logging() is called multiple times
    for h in list(logger.handlers):
        with contextlib.suppress(Exception):
            h.close()
        logger.removeHandler(h)

    logger.setLevel(logging.DEBUG)
    log_formatter = logging.Formatter(
        "%(asctime)s %(name)35s: %(levelname)s: %(message)s",
    )

    # add a logging handler for every error level as we want to put them into their own files  # noqa: E501
    error_log_filename = output_directory / "error.log"
    error_log_handler = logging.FileHandler(error_log_filename)
    error_log_handler.addFilter(ErrorLoggingFilter())
    error_log_handler.setFormatter(log_formatter)
    logger.addHandler(error_log_handler)

    debug_log_filename = output_directory / "debug.log"
    debug_log_handler = logging.FileHandler(debug_log_filename)
    debug_log_handler.addFilter(DebugLoggingFilter())
    debug_log_handler.setFormatter(log_formatter)
    logger.addHandler(debug_log_handler)

    info_log_filename = output_directory / "info.log"
    info_log_handler = logging.FileHandler(info_log_filename)
    info_log_handler.addFilter(InfoLoggingFilter())
    info_log_handler.setFormatter(log_formatter)
    logger.addHandler(info_log_handler)

    console_log_handler = logging.StreamHandler(stream=sys.stdout)
    console_log_handler.setLevel(console_logging_level)
    console_log_handler.setFormatter(log_formatter)
    logger.addHandler(console_log_handler)

    graylog_handler = GelfHttpsHandler(
        host="logger.tuxedo.tools",
        port=12202,
        compress=False,
        _application_name="TAS",
    )
    graylog_handler.setFormatter(log_formatter)
    graylog_handler.addFilter(DebugLoggingFilter())
    logger.addHandler(graylog_handler)
