from datetime import datetime
from logging import Handler, LogRecord
from os import PathLike
from typing import Union

import kombu
import redis
import requests
from loralogger.logger import logger as internal_logger
from loralogger.producer import config, worker
from loralogger.utils import RETRY_HTTP_ADAPTER, separate_log_data


class LogToESHandler(Handler):
    def __init__(self, label: str, skip_queue: bool = False) -> None:
        super().__init__()

        self.skip_queue = skip_queue
        self.label = label

    def __send_http_request(self, index: str, data: dict):
        endpoint: str = f"{config.elasticsearch.url}/{index}/_doc"

        es_username = config.elasticsearch.username
        es_password = config.elasticsearch.password

        session = requests.Session()
        session.mount("http://", RETRY_HTTP_ADAPTER)
        # session.mount("https://", RETRY_HTTP_ADAPTER)

        if es_username or es_password:
            session.auth = (es_username, es_password)  # type: ignore

        response = session.post(endpoint, json=data)
        response.raise_for_status()

    def __send_to_elasticsearch(self, level: str, data: dict, extra: dict) -> None:
        internal_logger.debug("sending log to Elasticsearch directly via http")

        log_data = dict(
            id=data.get("id"),
            level=level,
            message=data.get("msg"),
            event=data.get("event"),
            extra=extra,
            path=data.get("pathname"),
            file_name=data.get("filename"),
            line_no=data.get("lineno"),
            module=data.get("module"),
            function=data.get("funcName"),
        )

        log_data["@timestamp"] = datetime.utcfromtimestamp(
            datetime.timestamp(datetime.now())
        ).isoformat()

        index_name: str = f"logs-loralogger-{self.label}"
        log_data = {k: v for k, v in log_data.items() if v}

        self.__send_http_request(index=index_name, data=log_data)

    def __send_to_logger_service(self, level: str, data: dict, extra: dict) -> None:
        internal_logger.debug("sending log to Elasticsearch via celery")

        worker.send_task(
            "logger.tasks.write_log",
            queue="logger",
            args=(self.label, level, data, extra),
        )

    def __write_log(self, level: str, log_content: dict, extra_data: dict) -> None:
        try:
            if self.skip_queue:
                self.__send_to_elasticsearch(level, log_content, extra_data)
            else:
                self.__send_to_logger_service(level, log_content, extra_data)
        except redis.exceptions.ConnectionError as e:
            internal_logger.warning(f"Redis connection error: {e}")
            internal_logger.error(
                "Cannot connect to Redis, please check your configuration"
            )
        except kombu.exceptions.OperationalError as e:  # type: ignore
            internal_logger.warning(f"RabbitMQ connection error: {e}")
            internal_logger.error(
                "Cannot connect to RabbitMQ, please check your configuration"
            )
        except Exception as e:
            internal_logger.warning(f"Unhandled exception: {e}")
            internal_logger.error("Unknown error, cannot send log to Elasticsearch")

    def log_file_content(self, index: str, file_name: Union[str, bytes, PathLike]):
        try:
            internal_logger.debug(
                f'Logging the content of "{file_name}" to index "{index}" in Elasticsearch'
            )

            with open(file_name) as f:
                file_content = f.read()

                data = dict(log_dump=file_content)
                data["@timestamp"] = datetime.utcfromtimestamp(
                    datetime.timestamp(datetime.now())
                ).isoformat()

                self.__send_http_request(index, data)

        except FileNotFoundError as e:
            internal_logger.warning(f'Cannot find file "{file_name}": {e}')
            internal_logger.error(f'File "{file_name}" cannot be found')
        except requests.exceptions.ConnectionError as e:
            internal_logger.warning(f"Cannot connect to Elasticsearch: {e}")
            internal_logger.error("Cannot send data to Elasticsearch endpoint")
        except Exception as e:
            internal_logger.warning(f'Cannot open file "{file_name}": {e}')
            internal_logger.error(f'Cannot write log from file "{file_name}"')

    def emit(self, record: LogRecord) -> None:
        # get main and extra data from the log record
        main_data, extra_data = separate_log_data(record.__dict__)

        self.__write_log(record.levelname, main_data, extra_data)
