# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

import contextlib
import pathlib
import sys
from types import ModuleType
from typing import Optional

import spack.llnl.util.tty.log as log
from spack.llnl.util.filesystem import working_dir
from spack.util.executable import Executable

termios: Optional[ModuleType] = None
try:
    import termios as term_mod

    termios = term_mod
except ImportError:
    pass


@contextlib.contextmanager
def nullcontext():
    yield


def test_log_python_output_with_echo(capfd, tmp_path: pathlib.Path):
    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt", echo=True):
            print("logged")

        # foo.txt has output
        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "logged\n"

        # output is also echoed.
        assert capfd.readouterr()[0] == "logged\n"


def test_log_python_output_without_echo(capfd, tmp_path: pathlib.Path):
    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt"):
            print("logged")

        # foo.txt has output
        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "logged\n"

        # nothing on stdout or stderr
        assert capfd.readouterr()[0] == ""


def test_log_python_output_with_invalid_utf8(capfd, tmp_path: pathlib.Path):
    tmp_file = str(tmp_path / "foo.txt")
    with log.log_output(tmp_file, echo=True):
        sys.stdout.buffer.write(b"\xc3helloworld\n")

    # we should be able to read this as valid utf-8
    with open(tmp_file, "r", encoding="utf-8") as f:
        assert f.read() == "�helloworld\n"

    assert capfd.readouterr().out == "�helloworld\n"


def test_log_python_output_and_echo_output(capfd, tmp_path: pathlib.Path):
    with working_dir(str(tmp_path)):
        # echo two lines
        with log.log_output("foo.txt") as logger:
            with logger.force_echo():
                print("force echo")
            print("logged")

        # log file contains everything
        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "force echo\nlogged\n"

        # only force-echo'd stuff is in output
        assert capfd.readouterr()[0] == "force echo\n"


def test_log_output_with_control_codes(capfd, tmp_path: pathlib.Path):
    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt"):
            # Print a sample of formatted GCC error output
            # Line obtained from the file generated by running gcc on a nonexistent file:
            #   gcc -fdiagnostics-color=always ./test.cpp 2>test.log
            csi = "\x1b["
            print(
                f"{csi}01m{csi}Kgcc:{csi}m{csi}K {csi}01;31m{csi}Kerror: {csi}m{csi}K./test.cpp:"
            )

        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "gcc: error: ./test.cpp:\n"


def _log_filter_fn(string):
    return string.replace("foo", "bar")


def test_log_output_with_filter(capfd, tmp_path: pathlib.Path):
    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt", filter_fn=_log_filter_fn):
            print("foo blah")
            print("blah foo")
            print("foo foo")

        # foo.txt output is not filtered
        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "foo blah\nblah foo\nfoo foo\n"

    # output is not echoed
    assert capfd.readouterr()[0] == ""

    # now try with echo
    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt", echo=True, filter_fn=_log_filter_fn):
            print("foo blah")
            print("blah foo")
            print("foo foo")

        # foo.txt output is still not filtered
        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "foo blah\nblah foo\nfoo foo\n"

    # echoed output is filtered.
    assert capfd.readouterr()[0] == "bar blah\nblah bar\nbar bar\n"


def test_log_output_with_filter_and_append(capfd, tmp_path: pathlib.Path):
    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt", filter_fn=_log_filter_fn):
            print("foo blah")
            print("blah foo")
            print("foo foo")

        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "foo blah\nblah foo\nfoo foo\n"

        with log.log_output("foo.txt", filter_fn=_log_filter_fn, append=True):
            print("more foo more blah")

        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "foo blah\nblah foo\nfoo foo\nmore foo more blah\n"


def test_log_subproc_and_echo_output(capfd, tmp_path: pathlib.Path):
    python = Executable(sys.executable)

    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt") as logger:
            with logger.force_echo():
                python("-c", "print('echo')")
            print("logged")

        # Check log file content
        with open("foo.txt", encoding="utf-8") as f:
            assert f.read() == "echo\nlogged\n"

        # Check captured output (echoed content)
        # Note: 'logged' is not echoed because force_echo() scope ended
        # Note: "print(echo)" above automatically uses an "\r\n" on Windows
        # and will replace any \n with \r\n (so end=\n does not work)
        # \r\n is expected and correct here
        # Note: the above line ending constraint is an artifact of
        # pytest's capfd. This is potentially (however unlikely)
        # subject to change with future versions of pytest.
        # if this test suddenly starts failing, verifying the line
        # endings from capfd is a good starting place.
        assert capfd.readouterr()[0] == "echo\n"


def test_nested_logging_contexts(capfd, tmp_path):
    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt"):
            with log.log_output("bar.txt"):
                print("inner")
            print("outer")

        with open("foo.txt", "r", encoding="utf-8") as f:
            log_captured_out = f.read()
            assert "outer\n" in log_captured_out
            assert "inner\n" not in log_captured_out
        with open("bar.txt", "r", encoding="utf-8") as f:
            log_captured_out = f.read()
            assert "inner\n" in log_captured_out
            assert "outer\n" not in log_captured_out
