import contextlib
import logging.config
import sys
import warnings
from io import StringIO
from datetime import datetime
from pathlib import Path
from typing import ContextManager, TextIO, Optional, Dict, Any

import colorlog


class LogIO(StringIO):
    """IO sink that passes written buffers to the given logger."""

    def __init__(self, logger: logging.Logger, level: int) -> None:
        self._logger: logging.Logger = logger
        self._level: int = level
        super().__init__()

    def write(self, s: str) -> int:
        for line in str(s).splitlines(keepends=False):
            if line:
                self._logger.log(self._level, line)
        return len(s)

    def isatty(self) -> bool:
        # prevent for example progress bars from ``tqdm``
        return False


def _log_warnings(logger: logging.Logger, level: int) -> None:
    """Enable `warnings <https://docs.python.org/3/library/warnings.html>`__, but forward to the given logger."""

    def log_warning(message, category, filename, lineno, file=None, line=None) -> None:
        logger.log(level, "␤".join(
            warnings.formatwarning(message, category, filename, lineno, line).strip().splitlines(keepends=False)
        ))

    warnings.simplefilter("default")
    warnings.showwarning = log_warning


def setup_logging(config: Optional[Dict], name: str, level: str) -> logging.Logger:
    """Setup for the logging subsystem by the given config or using defaults to ``stderr``."""

    if config is not None:
        logging.config.dictConfig(config)
        logging.getLogger().setLevel(level)
    elif sys.stderr.isatty():
        colorlog.basicConfig(level=logging.getLevelName(level), stream=sys.stderr,
                             format="%(log_color)s%(levelname)-8s%(reset)s %(name)s: %(message)s")
    else:
        logging.basicConfig(level=logging.getLevelName(level), stream=sys.stderr,
                            format="%(levelname)s %(name)s: %(message)s")

    # prevent persistent manipulation of the global log level configuration, for example from:
    # TTS.utils.audio.processor -> scipy.optimize -> numpy.distutils.log -> set_verbosity(0, force=True)
    logging.getLogger().setLevel = lambda _: None  # type: ignore

    logger: logging.Logger = logging.getLogger(name)
    _log_warnings(logger.getChild("warnings"), logging.DEBUG)
    return logger


class redirect_logging(ContextManager[TextIO]):
    """
    Context to intercept standard streams and forward to the given logger instead.
    This works for libraries with the bad habit of directly using ``print`` statements, for native code directly writing
    to file-descriptors, we'd however need to ``dup()`` to a pipe.
    """

    def __init__(self, logger: logging.Logger) -> None:
        self._logger: logging.Logger = logger.getChild("print")
        self._out: ContextManager = contextlib.redirect_stdout(LogIO(self._logger, level=logging.DEBUG))
        self._err: ContextManager = contextlib.redirect_stderr(LogIO(self._logger, level=logging.WARNING))

    def __enter__(self) -> TextIO:
        orig_out: TextIO = sys.stdout
        self._out.__enter__()
        self._err.__enter__()
        return orig_out

    def __exit__(self, *args) -> None:
        self._err.__exit__(*args)
        self._out.__exit__(*args)


class Transcript(ContextManager[None]):
    def __init__(self, path: Optional[Path]) -> None:
        self._path: Optional[Path] = path
        self._file: Optional[TextIO] = None
        self._logger: logging.Logger = logging.getLogger(self.__class__.__name__)

    def __enter__(self) -> None:
        if self._path is not None:
            assert self._file is None
            self._file = self._path.open("a", newline="\n")
            self._logger.info(f"Writing to '{self._path.as_posix()}'")
            self.write_quote(datetime.now().isoformat())

    def write(self, msg: str) -> None:
        if self._file is not None:
            self._file.write(msg)

    def write_quote(self, msg: str) -> None:
        self.write("\n")
        self.write("".join(f"> {_.rstrip()}\n" for _ in msg.splitlines()))
        self.write("\n")

    def __exit__(self, *args: Any) -> None:
        if self._file is not None:
            self._file.close()
            self._file = None