import logging
import time

import pytest

from soniclue_basekit.exec_timer import ExecTimer


# --- Context manager basics ---


def test_interval_none_before_entering():
    t = ExecTimer()
    assert t.interval is None


def test_interval_set_after_exit():
    with ExecTimer() as t:
        pass
    assert t.interval is not None
    assert t.interval >= 0


def test_interval_reflects_elapsed_time():
    with ExecTimer() as t:
        time.sleep(0.02)
    assert t.interval >= 0.02


def test_text_property_returns_logging_text():
    t = ExecTimer("my task")
    assert t.text == "my task"


def test_text_property_none_by_default():
    t = ExecTimer()
    assert t.text is None


# --- Logging behavior ---


def test_no_log_when_logging_text_is_none(caplog):
    with caplog.at_level(logging.INFO):
        with ExecTimer() as t:
            pass
    assert caplog.text == ""


def test_logs_at_exit_when_logging_text_set(caplog):
    with caplog.at_level(logging.INFO):
        with ExecTimer("my task"):
            pass
    assert "my task" in caplog.text
    assert "Took" in caplog.text


def test_no_entry_log_by_default(caplog):
    with caplog.at_level(logging.INFO):
        with ExecTimer("something"):
            assert "something..." not in caplog.text


def test_entry_log_when_log_at_entry(caplog):
    with caplog.at_level(logging.INFO):
        with ExecTimer("running step", log_at_entry=True):
            assert "Running step..." in caplog.text


def test_entry_log_capitalizes_text(caplog):
    with caplog.at_level(logging.INFO):
        with ExecTimer("lower case", log_at_entry=True):
            assert "Lower case..." in caplog.text


def test_custom_log_level(caplog):
    with caplog.at_level(logging.DEBUG):
        with ExecTimer("dbg", log_level=logging.DEBUG):
            pass
    assert "dbg" in caplog.text


# --- post_action ---


def test_post_action_called_with_interval():
    received = []
    with ExecTimer(post_action=received.append):
        pass
    assert len(received) == 1
    assert received[0] >= 0


def test_post_action_none_does_not_raise():
    with ExecTimer(post_action=None):
        pass


# --- Exception safety ---


def test_interval_set_even_on_exception():
    with pytest.raises(ValueError):
        with ExecTimer() as t:
            raise ValueError("oops")
    assert t.interval is not None


# --- Decorator usage ---


def test_decorator_runs_function():
    result = []

    @ExecTimer()
    def add(a, b):
        result.append(a + b)
        return a + b

    ret = add(2, 3)
    assert ret == 5
    assert result == [5]


def test_decorator_uses_func_name_when_no_text(caplog):
    @ExecTimer()
    def compute():
        pass

    with caplog.at_level(logging.INFO):
        compute()
    assert "running compute" in caplog.text


def test_decorator_uses_provided_text(caplog):
    @ExecTimer("custom label")
    def work():
        pass

    with caplog.at_level(logging.INFO):
        work()
    assert "custom label" in caplog.text


def test_decorator_preserves_function_name():
    @ExecTimer()
    def my_func():
        pass

    assert my_func.__name__ == "my_func"


# --- stack_level ---


def test_stack_level_context_manager_points_to_caller(caplog):
    # stacklevel=2 (default) should report this test function, not __exit__.
    with caplog.at_level(logging.INFO):
        with ExecTimer("task"):
            pass
    assert caplog.records[0].funcName == "test_stack_level_context_manager_points_to_caller"


def test_stack_level_decorator_points_to_caller(caplog):
    # stacklevel=3 (hardcoded in __call__) should report the call site, not wrapper.
    @ExecTimer("task")
    def _inner():
        pass

    with caplog.at_level(logging.INFO):
        _inner()
    assert caplog.records[0].funcName == "test_stack_level_decorator_points_to_caller"
