import importlib.util
import io
import pathlib
import types


def load_listener_module(monkeypatch):
    monkeypatch.setenv("MAILU_SYNC_WAIT_TIMEOUT_SEC", "0")
    module_path = (
        pathlib.Path(__file__).resolve().parents[2]
        / "services"
        / "mailu"
        / "scripts"
        / "mailu_sync_listener.py"
    )
    spec = importlib.util.spec_from_file_location("mailu_sync_listener_testmod", module_path)
    module = importlib.util.module_from_spec(spec)
    assert spec.loader is not None
    spec.loader.exec_module(module)
    return module


def _handler_for(listener, body):
    handler = listener.Handler.__new__(listener.Handler)
    raw = body if isinstance(body, bytes) else body.encode()
    handler.headers = {"Content-Length": str(len(raw))}
    handler.rfile = io.BytesIO(raw)
    handler.responses = []
    handler.headers_ended = 0
    handler.send_response = lambda code: handler.responses.append(code)
    handler.end_headers = lambda: setattr(handler, "headers_ended", handler.headers_ended + 1)
    return handler


def test_listener_run_sync_blocking_updates_state(monkeypatch):
    listener = load_listener_module(monkeypatch)
    monkeypatch.setattr(listener, "time", lambda: 42.0)
    monkeypatch.setattr(
        listener.subprocess,
        "run",
        lambda command, check: types.SimpleNamespace(returncode=3),
    )

    assert listener._run_sync_blocking() == 3
    assert listener.last_rc == 3
    assert listener.last_run == 42.0
    assert listener.sync_done.is_set()

    listener.sync_running = True
    assert listener._run_sync_blocking() == 0


def test_listener_trigger_sync_async_honors_running_and_debounce(monkeypatch):
    listener = load_listener_module(monkeypatch)
    starts = []

    class _Thread:
        def __init__(self, target, daemon):
            self.target = target
            self.daemon = daemon

        def start(self):
            starts.append((self.target, self.daemon))

    monkeypatch.setattr(listener.threading, "Thread", _Thread)
    monkeypatch.setattr(listener, "time", lambda: 100.0)

    listener.sync_running = True
    assert listener._trigger_sync_async() is False

    listener.sync_running = False
    listener.last_run = 95.0
    assert listener._trigger_sync_async() is False

    assert listener._trigger_sync_async(force=True) is True
    assert starts and starts[0][1] is True


def test_listener_post_rejects_invalid_json(monkeypatch):
    listener = load_listener_module(monkeypatch)
    handler = _handler_for(listener, b"{not-json")

    handler.do_POST()

    assert handler.responses == [400]
    assert handler.headers_ended == 1


def test_listener_post_triggers_async_without_wait(monkeypatch):
    listener = load_listener_module(monkeypatch)
    called = []
    monkeypatch.setattr(listener, "_trigger_sync_async", lambda force=False: called.append(force) or True)
    handler = _handler_for(listener, '{"force": true}')

    handler.do_POST()

    assert called == [True]
    assert handler.responses == [202]


def test_listener_post_wait_returns_success_or_failure(monkeypatch):
    listener = load_listener_module(monkeypatch)
    called = []
    monkeypatch.setattr(listener, "_trigger_sync_async", lambda force=False: called.append(force) or True)
    listener.sync_running = False
    listener.last_rc = 0
    handler = _handler_for(listener, '{"wait": true, "force": true}')

    handler.do_POST()

    assert called == [True]
    assert handler.responses == [200]

    listener.last_rc = 2
    handler = _handler_for(listener, '{"wait": true}')
    handler.do_POST()
    assert handler.responses == [500]


def test_listener_post_wait_keeps_running_request_successful(monkeypatch):
    listener = load_listener_module(monkeypatch)
    listener.sync_running = True
    handler = _handler_for(listener, '{"wait": true}')

    handler.do_POST()

    assert handler.responses == [200]


def test_listener_log_message_is_quiet(monkeypatch):
    listener = load_listener_module(monkeypatch)
    handler = listener.Handler.__new__(listener.Handler)

    assert handler.log_message("ignored %s", "value") is None
