from io import StringIO
from unittest.mock import patch

import pytest

from esptool import __version__
from esptool.cmds import version
from esptool.logger import EsptoolLogger, TemplateLogger, log


# Custom logger that implements all methods
class CustomLogger(TemplateLogger):
    def print(self, *args, **kwargs):
        print("Custom logger:", *args, **kwargs)

    def note(self, message: str):
        """
        Logs a Note: message.
        """
        pass

    def warning(self, message: str):
        """
        Logs a Warning: message.
        """
        pass

    def error(self, message: str):
        """
        Logs an error message.
        """
        pass

    def stage(self, finish=False):
        pass

    def progress_bar(
        self,
        cur_iter: int,
        total_iters: int,
        prefix: str = "",
        suffix: str = "",
        bar_length: int = 30,
    ):
        pass

    def set_verbosity(self, verbosity: str):
        pass


# Custom logger that doesn't implement all methods
class CustomLoggerIncomplete:
    def print(self, *args, **kwargs):
        pass


@pytest.mark.host_test
class TestLogger:
    @pytest.fixture
    def logger(self):
        log = EsptoolLogger()
        log._set_smart_features(True)
        return log

    def test_singleton(self, logger):
        logger2 = EsptoolLogger()
        assert logger is logger2
        assert logger is log

    def test_print(self, logger):
        with patch("sys.stdout", new=StringIO()) as fake_out:
            logger.print("With newline")
            logger.print("Without newline", end="")
            assert fake_out.getvalue() == "With newline\nWithout newline"

    def test_note_message(self, logger):
        with patch("sys.stdout", new=StringIO()) as fake_out:
            logger.note("This is a note")
            assert (
                fake_out.getvalue()
                == f"{logger.ansi_blue}Note:{logger.ansi_normal} This is a note\n"
            )

    def test_warning_message(self, logger):
        with patch("sys.stdout", new=StringIO()) as fake_out:
            logger.warning("This is a warning")
            assert (
                fake_out.getvalue()
                == f"{logger.ansi_yellow}Warning:{logger.ansi_normal} "
                "This is a warning\n"
            )

    def test_error_message(self, logger):
        with patch("sys.stderr", new=StringIO()) as fake_out:
            logger.error("This is an error")
            assert (
                fake_out.getvalue()
                == f"{logger.ansi_red}This is an error{logger.ansi_normal}\n"
            )

    def test_stage(self, logger):
        with patch("sys.stdout", new=StringIO()) as fake_out:
            logger.stage()
            assert logger._stage_active
            logger.print("Line1")
            logger.print("Line2")
            logger.stage(finish=True)
            assert not logger._stage_active
            logger.print("Line3")

            output = fake_out.getvalue()
            assert f"{logger.ansi_line_up}{logger.ansi_line_clear}" * 2 in output
            assert "Line1\nLine2\n" in output
            assert "Line1\nLine2\nLine3\n" not in output

    def test_progress_bar(self, logger):
        with patch("sys.stdout", new=StringIO()) as fake_out:
            logger.progress_bar(
                cur_iter=2,
                total_iters=4,
                prefix="Progress: ",
                suffix=" (2/4)",
                bar_length=10,
            )
            logger.progress_bar(
                cur_iter=4,
                total_iters=4,
                prefix="Progress: ",
                suffix=" (4/4)",
                bar_length=10,
            )
            output = fake_out.getvalue()
            assert "Progress: [====>     ]  50.0% (2/4)" in output
            assert "Progress: [==========] 100.0% (4/4) \n" in output

    def test_set_incomplete_logger(self, logger):
        with pytest.raises(
            TypeError,
            match="New logger must implement the TemplateLogger interface, "
            "got 'CustomLoggerIncomplete'",
        ):
            logger.set_logger(CustomLoggerIncomplete())

    def test_set_logger(self, logger):
        # Original logger
        with patch("sys.stdout", new=StringIO()) as fake_out:
            version()  # This will log.print the estool version
            output = fake_out.getvalue()
            assert output == f"{__version__}\n"

        # Replace logger with custom one
        with patch("sys.stdout", new=StringIO()) as fake_out:
            logger.set_logger(CustomLogger())
            assert isinstance(logger, CustomLogger)
            version()  # This will use print from CustomLogger
            output = fake_out.getvalue()
            assert output == f"Custom logger: {__version__}\n"
