# 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 pytest

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

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

    termios = term_mod
except ImportError:
    pass


pytestmark = pytest.mark.not_on_windows("does not run on windows")


@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"


@pytest.mark.skipif(not which("echo"), reason="needs echo command")
def test_log_subproc_and_echo_output_no_capfd(capfd, tmp_path: pathlib.Path):
    echo = which("echo", required=True)

    # this is split into two tests because capfd interferes with the
    # output logged to file when using a subprocess.  We test the file
    # here, and echoing in test_log_subproc_and_echo_output_capfd below.
    with capfd.disabled():
        with working_dir(str(tmp_path)):
            with log.log_output("foo.txt") as logger:
                with logger.force_echo():
                    echo("echo")
                print("logged")

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


@pytest.mark.skipif(not which("echo"), reason="needs echo command")
def test_log_subproc_and_echo_output_capfd(capfd, tmp_path: pathlib.Path):
    echo = which("echo", required=True)

    # This tests *only* what is echoed when using a subprocess, as capfd
    # interferes with the logged data. See
    # test_log_subproc_and_echo_output_no_capfd for tests on the logfile.
    with working_dir(str(tmp_path)):
        with log.log_output("foo.txt") as logger:
            with logger.force_echo():
                echo("echo")
            print("logged")

        assert capfd.readouterr()[0] == "echo\n"
