from __future__ import annotations

import select
import subprocess
import sys
from pathlib import Path

import pytest

from pymulator.sidecar import daemon


def test_command_runs_sidecar_variants() -> None:
    assert daemon._command_runs_sidecar("/usr/bin/python3 -m pymulator.sidecar --host 127.0.0.1")
    assert daemon._command_runs_sidecar("/usr/bin/python3 -m pymulator.sidecar.daemon --port 29190")
    assert daemon._command_runs_sidecar("/usr/bin/python3 ./pymulator/sidecar/daemon.py --host 127.0.0.1")
    assert daemon._command_runs_sidecar(
        "/Users/me/Library/Application Support/com.ecumulator.studio/runtime/venv/bin/python3 -m pymulator.sidecar"
    )

    assert not daemon._command_runs_sidecar("/usr/bin/python3 -m pymuclient.epy server --ecuconnect")
    assert not daemon._command_runs_sidecar("/usr/bin/python3 -m pymuclient.epy client --doip")
    assert not daemon._command_runs_sidecar("/usr/bin/python3 -m pymulator.cli --ecuconnect")
    assert not daemon._command_runs_sidecar("/bin/zsh -lc python3 -m pymulator.sidecar --host 127.0.0.1")


def test_find_running_sidecar_process(monkeypatch: pytest.MonkeyPatch) -> None:
    current_pid = 43210
    output = "\n".join(
        [
            "123 /usr/bin/python3 -m pymulator.sidecar --host 127.0.0.1 --port 29190",
            f"{current_pid} /usr/bin/python3 -m pymulator.sidecar --host 127.0.0.1 --port 29190",
            "999 /usr/bin/python3 -m pymuclient.epy server --ecuconnect",
        ]
    )

    class _Result:
        returncode = 0
        stdout = output

    monkeypatch.setattr(daemon.subprocess, "run", lambda *args, **kwargs: _Result())
    found = daemon._find_running_sidecar_process(current_pid=current_pid)
    assert found == (123, "/usr/bin/python3 -m pymulator.sidecar --host 127.0.0.1 --port 29190")


@pytest.mark.skipif(daemon.fcntl is None, reason="POSIX flock unavailable")
def test_sidecar_lock_released_after_kill(tmp_path: Path) -> None:
    lock_path = tmp_path / "sidecar.lock"
    child_code = (
        "import fcntl, os, sys, time\n"
        "fd = os.open(sys.argv[1], os.O_RDWR | os.O_CREAT, 0o666)\n"
        "fcntl.flock(fd, fcntl.LOCK_EX)\n"
        "print('READY', flush=True)\n"
        "time.sleep(60)\n"
    )
    proc = subprocess.Popen(
        [sys.executable, "-c", child_code, str(lock_path)],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    try:
        assert proc.stdout is not None
        ready_streams, _, _ = select.select([proc.stdout], [], [], 5)
        assert ready_streams, "child process did not acquire lock in time"
        assert proc.stdout.readline().strip() == "READY"

        held_fd = daemon._acquire_sidecar_lock_fd(lock_path)
        assert held_fd is None

        proc.kill()
        proc.wait(timeout=5)

        released_fd = daemon._acquire_sidecar_lock_fd(lock_path)
        assert released_fd is not None
        daemon._release_sidecar_lock_fd(released_fd)
    finally:
        if proc.poll() is None:
            proc.kill()
            proc.wait(timeout=5)
