"""Regression tests for dashboard cron job profile routing."""

import pytest
from fastapi import HTTPException


@pytest.fixture()
def isolated_profiles(tmp_path, monkeypatch):
    """Give profile discovery an isolated default home with one named profile."""
    from hermes_cli import profiles

    default_home = tmp_path / ".hermes"
    profiles_root = default_home / "profiles"
    worker_home = profiles_root / "worker_alpha"

    for home in (default_home, worker_home):
        (home / "cron").mkdir(parents=True, exist_ok=True)
        (home / "config.yaml").write_text("model: test-model\n", encoding="utf-8")

    monkeypatch.setattr(profiles, "_get_default_hermes_home", lambda: default_home)
    monkeypatch.setattr(profiles, "_get_profiles_root", lambda: profiles_root)
    return {"default": default_home, "worker_alpha": worker_home}


def test_call_cron_for_profile_routes_storage_and_restores_globals(isolated_profiles):
    from cron import jobs as cron_jobs
    from hermes_cli import web_server

    old_cron_dir = cron_jobs.CRON_DIR
    old_jobs_file = cron_jobs.JOBS_FILE
    old_output_dir = cron_jobs.OUTPUT_DIR

    job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="run scheduled task",
        schedule="every 1h",
        name="worker-alpha-scan",
    )

    assert job["profile"] == "worker_alpha"
    assert job["profile_name"] == "worker_alpha"
    assert job["hermes_home"] == str(isolated_profiles["worker_alpha"])
    assert job["is_default_profile"] is False
    assert (isolated_profiles["worker_alpha"] / "cron" / "jobs.json").exists()
    assert not (isolated_profiles["default"] / "cron" / "jobs.json").exists()

    assert cron_jobs.CRON_DIR == old_cron_dir
    assert cron_jobs.JOBS_FILE == old_jobs_file
    assert cron_jobs.OUTPUT_DIR == old_output_dir


@pytest.mark.asyncio
async def test_list_cron_jobs_all_includes_default_and_named_profiles(isolated_profiles):
    from hermes_cli import web_server

    default_job = web_server._call_cron_for_profile(
        "default",
        "create_job",
        prompt="default heartbeat",
        schedule="every 2h",
        name="default-heartbeat",
    )
    worker_job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="worker heartbeat",
        schedule="every 3h",
        name="worker-alpha-heartbeat",
    )

    jobs = await web_server.list_cron_jobs(profile="all")
    by_id = {job["id"]: job for job in jobs}

    assert set(by_id) >= {default_job["id"], worker_job["id"]}
    assert by_id[default_job["id"]]["profile"] == "default"
    assert by_id[default_job["id"]]["is_default_profile"] is True
    assert by_id[default_job["id"]]["hermes_home"] == str(isolated_profiles["default"])
    assert by_id[worker_job["id"]]["profile"] == "worker_alpha"
    assert by_id[worker_job["id"]]["is_default_profile"] is False
    assert by_id[worker_job["id"]]["hermes_home"] == str(isolated_profiles["worker_alpha"])


@pytest.mark.asyncio
async def test_list_cron_jobs_specific_profile_filters_results(isolated_profiles):
    from hermes_cli import web_server

    web_server._call_cron_for_profile(
        "default",
        "create_job",
        prompt="default only",
        schedule="every 2h",
        name="default-only",
    )
    worker_job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="worker only",
        schedule="every 3h",
        name="worker-only",
    )

    jobs = await web_server.list_cron_jobs(profile="worker_alpha")

    assert [job["id"] for job in jobs] == [worker_job["id"]]
    assert jobs[0]["profile"] == "worker_alpha"


@pytest.mark.asyncio
async def test_create_cron_job_normalizes_representative_core_fields(
    isolated_profiles, tmp_path
):
    from hermes_cli import web_server

    scripts_dir = isolated_profiles["worker_alpha"] / "scripts"
    scripts_dir.mkdir()
    (scripts_dir / "collect-status.py").write_text("print('ok')\n", encoding="utf-8")

    job = await web_server.create_cron_job(
        web_server.CronJobCreate(
            prompt="summarize upstream status",
            schedule="every 1h",
            name="full-core-mapping",
            base_url="https://example.invalid/v1/",
            script=str(scripts_dir / "collect-status.py"),
            no_agent=True,
        ),
        profile="worker_alpha",
    )

    assert job["name"] == "full-core-mapping"
    assert job["base_url"] == "https://example.invalid/v1"
    assert job["script"] == "collect-status.py"
    assert job["no_agent"] is True


@pytest.mark.asyncio
async def test_cron_mutation_without_profile_finds_named_profile_job(isolated_profiles):
    from hermes_cli import web_server

    worker_job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="managed by named profile",
        schedule="every 1h",
        name="named-profile-job",
    )

    paused = await web_server.pause_cron_job(worker_job["id"])
    assert paused["profile"] == "worker_alpha"
    assert paused["enabled"] is False

    default_jobs = await web_server.list_cron_jobs(profile="default")
    worker_jobs = await web_server.list_cron_jobs(profile="worker_alpha")

    assert default_jobs == []
    assert len(worker_jobs) == 1
    assert worker_jobs[0]["id"] == worker_job["id"]
    assert worker_jobs[0]["enabled"] is False


@pytest.mark.asyncio
async def test_update_cron_job_normalizes_dashboard_core_fields(isolated_profiles, tmp_path):
    from hermes_cli import web_server

    scripts_dir = isolated_profiles["worker_alpha"] / "scripts"
    scripts_dir.mkdir()
    (scripts_dir / "collect.py").write_text("print('ok')\n", encoding="utf-8")
    job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="managed by named profile",
        schedule="every 1h",
        name="normalizes-dashboard-fields",
    )

    updated = await web_server.update_cron_job(
        job["id"],
        web_server.CronJobUpdate(
            updates={
                "base_url": "https://example.invalid/v1/",
                "script": str(scripts_dir / "collect.py"),
                "context_from": "",
                "no_agent": True,
            }
        ),
        profile="worker_alpha",
    )

    assert updated["base_url"] == "https://example.invalid/v1"
    assert updated["script"] == "collect.py"
    assert updated["context_from"] is None
    assert updated["no_agent"] is True


@pytest.mark.asyncio
async def test_create_cron_job_rejects_script_outside_profile_scripts(
    isolated_profiles, tmp_path
):
    from hermes_cli import web_server

    outside = tmp_path / "outside.py"
    outside.write_text("print('nope')\n", encoding="utf-8")

    with pytest.raises(HTTPException) as exc:
        await web_server.create_cron_job(
            web_server.CronJobCreate(
                schedule="every 1h",
                script=str(outside),
                no_agent=True,
            ),
            profile="worker_alpha",
        )

    assert exc.value.status_code == 400
    assert "inside" in exc.value.detail


@pytest.mark.asyncio
async def test_create_cron_job_rejects_empty_agent_job(isolated_profiles):
    from hermes_cli import web_server

    with pytest.raises(HTTPException) as exc:
        await web_server.create_cron_job(
            web_server.CronJobCreate(schedule="every 1h"),
            profile="worker_alpha",
        )

    assert exc.value.status_code == 400
    assert "prompt, skill, or script" in exc.value.detail


@pytest.mark.asyncio
async def test_update_cron_job_no_agent_reuses_existing_script(isolated_profiles):
    from hermes_cli import web_server

    scripts_dir = isolated_profiles["worker_alpha"] / "scripts"
    scripts_dir.mkdir()
    (scripts_dir / "collect.py").write_text("print('ok')\n", encoding="utf-8")

    job = await web_server.create_cron_job(
        web_server.CronJobCreate(
            schedule="every 1h",
            script=str(scripts_dir / "collect.py"),
        ),
        profile="worker_alpha",
    )

    updated = await web_server.update_cron_job(
        job["id"],
        web_server.CronJobUpdate(updates={"no_agent": True}),
        profile="worker_alpha",
    )

    assert updated["no_agent"] is True
    assert updated["script"] == "collect.py"


@pytest.mark.asyncio
async def test_dashboard_cron_rejects_missing_context_from(isolated_profiles):
    from hermes_cli import web_server

    with pytest.raises(HTTPException) as create_exc:
        await web_server.create_cron_job(
            web_server.CronJobCreate(
                prompt="process missing upstream",
                schedule="every 1h",
                context_from=["missing-job-id"],
            ),
            profile="worker_alpha",
        )

    assert create_exc.value.status_code == 400
    assert "missing-job-id" in create_exc.value.detail

    job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="managed by named profile",
        schedule="every 1h",
        name="context-update-target",
    )

    with pytest.raises(HTTPException) as update_exc:
        await web_server.update_cron_job(
            job["id"],
            web_server.CronJobUpdate(
                updates={
                    "context_from": ["missing-job-id"],
                }
            ),
            profile="worker_alpha",
        )

    assert update_exc.value.status_code == 400
    assert "missing-job-id" in update_exc.value.detail


@pytest.mark.asyncio
async def test_dashboard_cron_context_from_is_profile_scoped(isolated_profiles):
    from hermes_cli import web_server

    default_job = web_server._call_cron_for_profile(
        "default",
        "create_job",
        prompt="default upstream",
        schedule="every 1h",
        name="default-upstream",
    )
    worker_upstream = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="worker upstream",
        schedule="every 1h",
        name="worker-upstream",
    )

    with pytest.raises(HTTPException):
        await web_server.create_cron_job(
            web_server.CronJobCreate(
                prompt="worker downstream",
                schedule="every 1h",
                context_from=[default_job["id"]],
            ),
            profile="worker_alpha",
        )

    job = await web_server.create_cron_job(
        web_server.CronJobCreate(
            prompt="worker downstream",
            schedule="every 1h",
            context_from=[worker_upstream["id"]],
        ),
        profile="worker_alpha",
    )

    assert job["context_from"] == [worker_upstream["id"]]


@pytest.mark.asyncio
async def test_update_cron_job_refreshes_snapshots_when_unpinning(
    isolated_profiles,
    monkeypatch,
):
    from hermes_cli import runtime_provider, web_server

    monkeypatch.setattr(
        runtime_provider,
        "resolve_runtime_provider",
        lambda **kwargs: {"provider": "worker-provider"},
    )

    job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="managed by named profile",
        schedule="every 1h",
        name="pinned-job",
        provider="fixed-provider",
        model="fixed-model",
    )

    assert job["provider_snapshot"] is None
    assert job["model_snapshot"] is None

    updated = await web_server.update_cron_job(
        job["id"],
        web_server.CronJobUpdate(
            updates={
                "provider": None,
                "model": None,
            }
        ),
        profile="worker_alpha",
    )

    assert updated["provider"] is None
    assert updated["model"] is None
    assert updated["provider_snapshot"] == "worker-provider"
    assert updated["model_snapshot"] == "test-model"


@pytest.mark.asyncio
async def test_dashboard_cron_noop_inference_fields_keep_existing_snapshots(
    isolated_profiles,
    monkeypatch,
):
    from hermes_cli import runtime_provider, web_server

    current_provider = {"name": "initial-provider"}
    monkeypatch.setattr(
        runtime_provider,
        "resolve_runtime_provider",
        lambda **kwargs: {"provider": current_provider["name"]},
    )

    job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="managed by named profile",
        schedule="every 1h",
        name="dashboard-edit-job",
    )

    assert job["provider_snapshot"] == "initial-provider"
    assert job["model_snapshot"] == "test-model"

    current_provider["name"] = "changed-provider"
    (isolated_profiles["worker_alpha"] / "config.yaml").write_text(
        "model: changed-model\n",
        encoding="utf-8",
    )

    updated = await web_server.update_cron_job(
        job["id"],
        web_server.CronJobUpdate(
            updates={
                "name": "dashboard-edit-job-renamed",
                "provider": None,
                "model": None,
                "base_url": None,
                "no_agent": False,
            }
        ),
        profile="worker_alpha",
    )

    assert updated["name"] == "dashboard-edit-job-renamed"
    assert updated["provider_snapshot"] == "initial-provider"
    assert updated["model_snapshot"] == "test-model"


@pytest.mark.asyncio
async def test_update_cron_job_clears_snapshots_for_no_agent(
    isolated_profiles,
    monkeypatch,
):
    from hermes_cli import runtime_provider, web_server

    monkeypatch.setattr(
        runtime_provider,
        "resolve_runtime_provider",
        lambda **kwargs: {"provider": "worker-provider"},
    )
    scripts_dir = isolated_profiles["worker_alpha"] / "scripts"
    scripts_dir.mkdir()
    (scripts_dir / "collect.py").write_text("print('ok')\n", encoding="utf-8")

    job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="managed by named profile",
        schedule="every 1h",
        name="agent-to-script-job",
    )

    assert job["provider_snapshot"] == "worker-provider"
    assert job["model_snapshot"] == "test-model"

    updated = await web_server.update_cron_job(
        job["id"],
        web_server.CronJobUpdate(
            updates={
                "script": str(scripts_dir / "collect.py"),
                "no_agent": True,
            }
        ),
        profile="worker_alpha",
    )

    assert updated["provider_snapshot"] is None
    assert updated["model_snapshot"] is None


@pytest.mark.asyncio
async def test_update_cron_job_rejects_id_mutation(isolated_profiles):
    """Dashboard surfaces a 400 (not a 500 or silent rename) when an
    id-mutation attempt is rejected by cron/jobs.update_job."""
    from hermes_cli import web_server

    worker_job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="managed by named profile",
        schedule="every 1h",
        name="immutable-id-job",
    )

    with pytest.raises(HTTPException) as exc:
        await web_server.update_cron_job(
            worker_job["id"],
            web_server.CronJobUpdate(updates={"id": "../escape"}),
            profile="worker_alpha",
        )

    assert exc.value.status_code == 400
    assert "id" in exc.value.detail
    worker_jobs = await web_server.list_cron_jobs(profile="worker_alpha")
    assert [job["id"] for job in worker_jobs] == [worker_job["id"]]


@pytest.mark.asyncio
async def test_cron_delete_with_profile_deletes_only_target_profile(isolated_profiles):
    from hermes_cli import web_server

    default_job = web_server._call_cron_for_profile(
        "default",
        "create_job",
        prompt="same-ish default",
        schedule="every 1h",
        name="shared-name",
    )
    worker_job = web_server._call_cron_for_profile(
        "worker_alpha",
        "create_job",
        prompt="same-ish worker",
        schedule="every 1h",
        name="shared-name-worker",
    )

    deleted = await web_server.delete_cron_job(worker_job["id"], profile="worker_alpha")
    assert deleted == {"ok": True}

    remaining_default = await web_server.list_cron_jobs(profile="default")
    remaining_worker = await web_server.list_cron_jobs(profile="worker_alpha")
    assert [job["id"] for job in remaining_default] == [default_job["id"]]
    assert remaining_worker == []


@pytest.mark.asyncio
async def test_cron_profile_validation_errors(isolated_profiles):
    from hermes_cli import web_server

    with pytest.raises(HTTPException) as bad_name:
        await web_server.list_cron_jobs(profile="../bad")
    assert bad_name.value.status_code == 400

    with pytest.raises(HTTPException) as missing:
        await web_server.list_cron_jobs(profile="missing_profile")
    assert missing.value.status_code == 404
