#!/usr/bin/env python3

from io import StringIO
import os
from datetime import datetime
import zdbg

# Lines marked with <-- are referenced in messages (for context).

THIS_FILE = "test_zdbg.py"


class TTY(StringIO):
    """A StringIO that pretends to be a TTY."""

    def isatty(self) -> bool:
        return True
    

_ERROR_PREFIX = "Error: "
_DEBUG_PREFIX = "DEBUG: "
_COLOR_ERROR_PREFIX = "\x1b[1m\x1b[31mError\x1b[0m\x1b[1m: "
_COLOR_DEBUG_PREFIX = "\x1b[32mDEBUG\x1b[0m: "


#================================================================================
# Tests without color support
#================================================================================


def test_with_context() -> None:
    """Test with context."""

    # Keep this high in the file so the line number is predictable.
    buf = StringIO()
    zdbg.configure(["debug", "context", "noflush_debug", "noflush_error"],
                   debugout=buf, errorout=buf)
    zdbg.debug("testing context") # <--
    output = buf.getvalue()
    assert "testing context" in output
    assert f"{THIS_FILE}:38" in output

    # Reset the buffer.
    buf.truncate(0)
    buf.seek(0)
    zdbg.error("testing error with context") # <--
    err_output: str = buf.getvalue()
    assert "testing error with context" in err_output
    assert f"{THIS_FILE}:46" in err_output


def test_timestamp_in_debug_output() -> None:
    """Test timestamp in debug output."""
    buf = StringIO()
    zdbg.configure(["debug", "timestamp"], debugout=buf)
    start = datetime.now()
    zdbg.debug("hello")
    output = buf.getvalue()
    _check_timestamp(_DEBUG_PREFIX, start, output)


def test_timestamp_in_error_output() -> None:
    """Test timestamp in error output."""
    buf = StringIO()
    zdbg.configure(["timestamp"], errorout=buf)
    start = datetime.now()
    zdbg.error("check")
    output = buf.getvalue()
    _check_timestamp(_ERROR_PREFIX, start, output)


def test_timestamp_context_in_debug_output() -> None:
    """Test timestamp in debug output."""
    buf = StringIO()
    zdbg.configure(["debug", "timestamp", "context"], debugout=buf)
    start = datetime.now()
    zdbg.debug("hello")
    output = buf.getvalue()
    _check_timestamp(_DEBUG_PREFIX, start, output)


def test_timestamp_context_in_error_output() -> None:
    """Test timestamp in error output."""
    buf = StringIO()
    zdbg.configure(["timestamp", "context"], errorout=buf)
    start = datetime.now()
    zdbg.error("check")
    output = buf.getvalue()
    _check_timestamp(_ERROR_PREFIX, start, output)


def test_debug_disabled_is_noop() -> None:
    """Test debug when not enabled is a no-op."""
    buf = StringIO()
    zdbg.configure([], debugout=buf, errorout=buf)
    zdbg.debug("hello")
    assert buf.getvalue() == ""
    buf.truncate(0)
    buf.seek(0)
    zdbg.error("error message")
    assert "error message" in buf.getvalue()


def test_debug_enabled_writes() -> None:
    """Test debug when enabled writes to output."""
    buf = StringIO()
    zdbg.configure(["debug"], debugout=buf, errorout=buf)
    zdbg.debug("hello")
    assert "hello" in buf.getvalue()
    buf.truncate(0)
    buf.seek(0)
    zdbg.error("error message")
    assert "error message" in buf.getvalue()


def test_environment_variable() -> None:
    """Test debug enabled via environment variable."""
    buf = StringIO()
    os.environ["ZDBG_DEBUG"] = "1"
    os.environ["DEBUG"] = ""
    # Using the default gets DEBUG, which is empty.
    zdbg.configure([], debugout=buf)
    zdbg.debug("world")
    assert buf.getvalue() == ""
    buf.truncate(0)
    buf.seek(0)
    # Configuring from ZDBG_DEBUG enables debugging.
    zdbg.configure(None, envvar="ZDBG_DEBUG", debugout=buf)
    zdbg.debug("world")
    assert "world" in buf.getvalue()
    del os.environ["ZDBG_DEBUG"]
    del os.environ["DEBUG"]


#================================================================================
# Tests with color support
#================================================================================


def test_color_with_context() -> None:
    """Test with context."""

    # Keep this high in the file so the line number is predictable.
    buf = TTY()
    zdbg.configure(["debug", "context", "noflush_error", "noflush_debug"],
                   debugout=buf, errorout=buf)
    zdbg.debug("testing context") # <--
    output = buf.getvalue()
    assert "testing context" in output
    assert f"{THIS_FILE}:147" in output

    # Reset the buffer.
    buf.truncate(0)
    buf.seek(0)
    zdbg.error("testing error with context") # <--
    err_output: str = buf.getvalue()
    assert "testing error with context" in err_output
    assert f"{THIS_FILE}:155" in err_output


def test_color_timestamp_in_debug_output() -> None:
    """Test timestamp in debug output."""
    buf = TTY()
    zdbg.configure(["debug", "timestamp"], debugout=buf)
    start = datetime.now()
    zdbg.debug("hello")
    output = buf.getvalue()
    _check_timestamp(_COLOR_DEBUG_PREFIX, start, output)


def test_color_timestamp_in_error_output() -> None:
    """Test timestamp in error output."""
    buf = TTY()
    zdbg.configure(["timestamp"], errorout=buf)
    start = datetime.now()
    zdbg.error("check")
    output = buf.getvalue()
    _check_timestamp(_COLOR_ERROR_PREFIX, start, output)


def test_color_timestamp_context_in_debug_output() -> None:
    """Test timestamp in debug output."""
    buf = TTY()
    zdbg.configure(["debug", "timestamp", "context"], debugout=buf)
    start = datetime.now()
    zdbg.debug("hello")
    output = buf.getvalue()
    _check_timestamp(_COLOR_DEBUG_PREFIX, start, output)


def test_color_timestamp_context_in_error_output() -> None:
    """Test timestamp in error output."""
    buf = TTY()
    zdbg.configure(["timestamp", "context"], errorout=buf)
    start = datetime.now()
    zdbg.error("check")
    output = buf.getvalue()
    _check_timestamp(_COLOR_ERROR_PREFIX, start, output)


def test_color_debug_disabled_is_noop() -> None:
    """Test debug when not enabled is a no-op."""
    buf = TTY()
    zdbg.configure([], debugout=buf, errorout=buf)
    zdbg.debug("hello")
    assert buf.getvalue() == ""
    buf.truncate(0)
    buf.seek(0)
    zdbg.error("error message")
    assert "error message" in buf.getvalue()


def test_color_debug_enabled_writes() -> None:
    """Test debug when enabled writes to output."""
    buf = TTY()
    zdbg.configure(["debug"], debugout=buf, errorout=buf)
    zdbg.debug("hello")
    assert "hello" in buf.getvalue()
    buf.truncate(0)
    buf.seek(0)
    zdbg.error("error message")
    assert "error message" in buf.getvalue()


def test_color_environment_variable() -> None:
    """Test debug enabled via environment variable."""
    buf = TTY()
    os.environ["ZDBG_DEBUG"] = "1"
    os.environ["DEBUG"] = ""
    # Using the default gets DEBUG, which is empty.
    zdbg.configure([], debugout=buf)
    zdbg.debug("world")
    assert buf.getvalue() == ""
    buf.truncate(0)
    buf.seek(0)
    # Configuring from ZDBG_DEBUG enables debugging.
    zdbg.configure(None, envvar="ZDBG_DEBUG", debugout=buf)
    zdbg.debug("world")
    assert "world" in buf.getvalue()
    del os.environ["ZDBG_DEBUG"]
    del os.environ["DEBUG"]


def _check_timestamp(prefix: str, start: datetime, output: str) -> None:
    """Test timestamp in debug output."""
    assert output.strip(), "No output produced"

    # Expected format: "DEBUG: 2025-11-19 13:45:21 hello"
    # Extract the timestamp part (between "DEBUG: " and the next space)
    _, _, rest = output.partition(prefix)
    timestamp_str = rest[:19]   # "YYYY-MM-DD HH:MM:SS"
    t_msg = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
    delta = abs((t_msg - start).total_seconds())
    assert delta <= 2.0, f"Timestamp mismatch: {t_msg} vs acquisition time {start}"


if __name__ == "__main__": # pragma: no cover - trivial
    test_with_context()
    test_debug_disabled_is_noop()
    test_debug_enabled_writes()
    test_environment_variable()
    test_timestamp_in_debug_output()
    test_timestamp_in_error_output()
    test_timestamp_context_in_debug_output()
    test_timestamp_context_in_error_output()
    print("All tests passed.")
