"""Regression coverage for issue #617 scheduled-job profile selection."""

import io
import json
import sys
import types
from pathlib import Path

import pytest

REPO = Path(__file__).resolve().parent.parent


class _JSONHandler:
    def __init__(self):
        self.status = None
        self.headers = {}
        self.response_headers = []
        self.wfile = io.BytesIO()

    def send_response(self, status):
        self.status = status

    def send_header(self, key, value):
        self.response_headers.append((key, value))

    def end_headers(self):
        pass


def _payload(handler):
    return json.loads(handler.wfile.getvalue().decode("utf-8"))


def test_cron_api_serializes_legacy_profile_as_explicit_server_default():
    from api.routes import _cron_job_for_api

    legacy = {"id": "legacy", "name": "Legacy job"}
    payload = _cron_job_for_api(legacy)

    assert payload["profile"] is None
    assert "profile" not in legacy, "API serialization must not mutate stored legacy jobs"


def test_cron_profile_value_validates_against_existing_profiles(monkeypatch):
    import api.profiles as profiles
    from api.routes import _normalize_cron_profile_value

    monkeypatch.setattr(
        profiles,
        "list_profiles_api",
        lambda: [
            {"name": "default"},
            {"name": "research"},
        ],
    )

    assert _normalize_cron_profile_value(" research ") == "research"
    assert _normalize_cron_profile_value("") is None
    assert _normalize_cron_profile_value(None) is None
    with pytest.raises(ValueError, match="Unknown profile: missing"):
        _normalize_cron_profile_value("missing")


def test_cron_create_api_persists_profile_and_returns_it(monkeypatch):
    import api.profiles as profiles
    import api.routes as routes

    created = {
        "id": "job617",
        "name": "Profiled job",
        "prompt": "ping",
        "schedule": {"kind": "interval", "minutes": 60},
    }
    updated = {**created, "profile": "research"}
    calls = []

    cron_pkg = types.ModuleType("cron")
    cron_pkg.__path__ = []
    cron_jobs = types.ModuleType("cron.jobs")
    cron_jobs.create_job = lambda **kwargs: calls.append(("create", kwargs)) or dict(created)
    cron_jobs.update_job = lambda job_id, updates: calls.append(("update", job_id, updates)) or dict(updated)

    monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}])
    monkeypatch.setitem(sys.modules, "cron", cron_pkg)
    monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs)

    handler = _JSONHandler()
    routes._handle_cron_create(
        handler,
        {
            "name": "Profiled job",
            "prompt": "ping",
            "schedule": "every 60m",
            "deliver": "local",
            "profile": "research",
        },
    )

    body = _payload(handler)
    assert handler.status == 200
    assert body["ok"] is True
    assert body["job"]["profile"] == "research"
    assert calls[0][0] == "create"
    assert calls[1] == ("update", "job617", {"profile": "research"})


def test_cron_create_api_rejects_unknown_profile_before_persisting(monkeypatch):
    import api.profiles as profiles
    import api.routes as routes

    cron_pkg = types.ModuleType("cron")
    cron_pkg.__path__ = []
    cron_jobs = types.ModuleType("cron.jobs")
    cron_jobs.create_job = lambda **kwargs: pytest.fail("invalid profiles must not create jobs")
    cron_jobs.update_job = lambda *args, **kwargs: pytest.fail("invalid profiles must not update jobs")

    monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}])
    monkeypatch.setitem(sys.modules, "cron", cron_pkg)
    monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs)

    handler = _JSONHandler()
    routes._handle_cron_create(
        handler,
        {"prompt": "ping", "schedule": "every 60m", "profile": "missing"},
    )

    assert handler.status == 400
    assert "Unknown profile: missing" in _payload(handler)["error"]


def test_cron_update_api_accepts_profile_clear_and_rejects_unknown(monkeypatch):
    import api.profiles as profiles
    import api.routes as routes

    calls = []
    cron_pkg = types.ModuleType("cron")
    cron_pkg.__path__ = []
    cron_jobs = types.ModuleType("cron.jobs")

    def update_job(job_id, updates):
        calls.append((job_id, updates))
        return {"id": job_id, "name": "Updated", **updates}

    cron_jobs.update_job = update_job
    monkeypatch.setattr(profiles, "list_profiles_api", lambda: [{"name": "research"}])
    monkeypatch.setitem(sys.modules, "cron", cron_pkg)
    monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs)

    handler = _JSONHandler()
    routes._handle_cron_update(handler, {"job_id": "job617", "profile": ""})
    assert handler.status == 200
    assert _payload(handler)["job"]["profile"] is None
    assert calls == [("job617", {"profile": None})]

    bad_handler = _JSONHandler()
    routes._handle_cron_update(bad_handler, {"job_id": "job617", "profile": "ghost"})
    assert bad_handler.status == 400
    assert "Unknown profile: ghost" in _payload(bad_handler)["error"]
    assert calls == [("job617", {"profile": None})]


def test_manual_cron_run_uses_execution_profile_but_persists_to_owning_store(monkeypatch):
    import api.profiles as profiles
    import api.routes as routes

    events = []

    class Ctx:
        def __init__(self, home):
            self.home = str(home)

        def __enter__(self):
            events.append(("enter", self.home))

        def __exit__(self, exc_type, exc, tb):
            events.append(("exit", self.home))

    cron_pkg = types.ModuleType("cron")
    cron_pkg.__path__ = []
    cron_jobs = types.ModuleType("cron.jobs")
    cron_jobs.save_job_output = lambda job_id, output: events.append(("save", job_id, output))
    cron_jobs.mark_job_run = lambda job_id, success, error=None: events.append(("mark", job_id, success, error))
    cron_scheduler = types.ModuleType("cron.scheduler")
    cron_scheduler.run_job = lambda job: events.append(("run", job["id"])) or (True, "output", "final", None)

    def fake_subprocess_run(job, execution_profile_home):
        events.append(("run", job["id"], str(execution_profile_home)))
        return True, "output", "final", None

    monkeypatch.setattr(profiles, "cron_profile_context_for_home", Ctx)
    monkeypatch.setattr(routes, "_run_cron_job_in_profile_subprocess", fake_subprocess_run)
    monkeypatch.setitem(sys.modules, "cron", cron_pkg)
    monkeypatch.setitem(sys.modules, "cron.jobs", cron_jobs)
    monkeypatch.setitem(sys.modules, "cron.scheduler", cron_scheduler)

    routes._mark_cron_running("job617")
    routes._run_cron_tracked(
        {"id": "job617"},
        profile_home="/hermes/default",
        execution_profile_home="/hermes/profiles/research",
    )

    assert events == [
        ("run", "job617", "/hermes/profiles/research"),
        ("enter", "/hermes/default"),
        ("save", "job617", "output"),
        ("mark", "job617", True, None),
        ("exit", "/hermes/default"),
    ]
    assert routes._is_cron_running("job617") == (False, 0.0)


def test_cron_profile_selector_source_hooks_present():
    panels = (REPO / "static" / "panels.js").read_text(encoding="utf-8")
    css = (REPO / "static" / "style.css").read_text(encoding="utf-8")
    i18n = (REPO / "static" / "i18n.js").read_text(encoding="utf-8")

    assert "async function loadCronProfiles()" in panels
    assert "api('/api/profiles')" in panels
    assert "id=\"cronFormProfile\"" in panels
    assert "profile: profile" in panels
    assert "job.profile" in panels
    assert "cron-profile-badge" in panels
    assert ".cron-profile-badge" in css
    assert "cron_profile_server_default" in i18n
    assert "cron_profile_server_default_hint" in i18n
