from __future__ import annotations

import time
from pathlib import Path
from unittest.mock import patch

import pytest

from app.core.models import Job
from app.core.registry import _jobs
from app.pipeline.collect import sweep_old_jobs


@pytest.fixture(autouse=True)
def _isolate_registry():
    _jobs.clear()
    yield
    _jobs.clear()


def _mkdir(jobs_dir: Path, name: str) -> Path:
    d = jobs_dir / name
    d.mkdir(parents=True)
    (d / "marker").write_bytes(b"x")
    return d


def test_skip_active_job_even_if_old(tmp_path: Path):
    """An active (non-terminal) job's directory must never be swept,
    even if its created_at predates the TTL cutoff."""
    d = _mkdir(tmp_path, "abcdefabcdef")
    job = Job(id="abcdefabcdef")
    job.status = "separating"
    job.created_at = time.time() - 999_999  # ancient
    _jobs[job.id] = job

    with patch("app.pipeline.collect.JOB_TTL_SECONDS", 60):
        sweep_old_jobs(tmp_path)

    assert d.is_dir()
    assert job.id in _jobs


def test_sweeps_terminal_old_job(tmp_path: Path):
    d = _mkdir(tmp_path, "abcdefabcdee")
    job = Job(id="abcdefabcdee")
    job.status = "done"
    job.created_at = time.time() - 999_999
    _jobs[job.id] = job

    with patch("app.pipeline.collect.JOB_TTL_SECONDS", 60):
        sweep_old_jobs(tmp_path)

    assert not d.exists()
    assert job.id not in _jobs


def test_sweep_disabled_under_desktop(monkeypatch):
    """The desktop shell (STEMDECK_DESKTOP=1) opts out of the TTL sweep so a
    user's curated library isn't purged; the server/Docker default keeps it."""
    from app.main import _sweep_disabled

    monkeypatch.delenv("STEMDECK_PERSIST_LIBRARY", raising=False)
    monkeypatch.setenv("STEMDECK_DESKTOP", "1")
    assert _sweep_disabled() is True
    monkeypatch.delenv("STEMDECK_DESKTOP", raising=False)
    assert _sweep_disabled() is False


def test_sweep_disabled_under_persistent_library(monkeypatch):
    """A self-hosted server (run.sh) opts into a persistent library
    (STEMDECK_PERSIST_LIBRARY=1) so its processed tracks aren't purged."""
    from app.main import _sweep_disabled

    monkeypatch.delenv("STEMDECK_DESKTOP", raising=False)
    monkeypatch.setenv("STEMDECK_PERSIST_LIBRARY", "1")
    assert _sweep_disabled() is True
    monkeypatch.delenv("STEMDECK_PERSIST_LIBRARY", raising=False)
    assert _sweep_disabled() is False


@pytest.mark.asyncio
async def test_sweep_loop_returns_immediately_under_desktop(monkeypatch):
    """In desktop mode the loop returns at once instead of entering the hourly
    cycle (wait_for would time out if it looped)."""
    import asyncio

    from app.main import _sweep_loop

    monkeypatch.setenv("STEMDECK_DESKTOP", "1")
    await asyncio.wait_for(_sweep_loop(), timeout=2)


def test_keeps_recent_terminal_job(tmp_path: Path):
    d = _mkdir(tmp_path, "abcdefabcded")
    job = Job(id="abcdefabcded")
    job.status = "done"
    job.created_at = time.time()  # fresh
    _jobs[job.id] = job

    with patch("app.pipeline.collect.JOB_TTL_SECONDS", 60):
        sweep_old_jobs(tmp_path)

    assert d.is_dir()
    assert job.id in _jobs


def test_orphan_dir_falls_back_to_mtime(tmp_path: Path):
    """Directories with no registry entry (e.g. left over from a prior
    server run) still get swept by mtime."""
    d = _mkdir(tmp_path, "abcdefabcdec")
    # Backdate the directory.
    old = time.time() - 999_999
    import os

    os.utime(d, (old, old))

    with patch("app.pipeline.collect.JOB_TTL_SECONDS", 60):
        sweep_old_jobs(tmp_path)

    assert not d.exists()
