"""Create-time delivery resolution for cron-context job creation.

A cron-run agent (gate: cron.allow_agent_scheduling) creating a job must
never produce a job whose deliver field points at the creating run itself:
the run's session is ephemeral, so a stored literal 'origin' would resolve
against a session that no longer exists by fire time. The cronjob tool
therefore resolves 'origin' (or an omitted deliver) AT CREATE TIME to the
creating job's own concrete persistent target, read from the
HERMES_CRON_AUTO_DELIVER_* contextvars that run_job publishes per run.
Non-cron sessions keep the literal 'origin' (live chat sessions have a real
origin to resolve at fire time).
"""

import json

import pytest

from gateway.session_context import _VAR_MAP, clear_session_vars, set_session_vars


@pytest.fixture
def temp_cron_home(tmp_path, monkeypatch):
    from cron import jobs as cron_jobs

    with cron_jobs.use_cron_store(tmp_path):
        cron_jobs.ensure_dirs()
        yield tmp_path


def _enter_cron_context(platform=None, chat_id=None, thread_id=None):
    """Simulate the contextvar state run_job() establishes for a cron run."""
    tokens = set_session_vars(
        platform="",
        chat_id="",
        chat_name="",
        cron_session="1",
    )
    extra = []
    if platform is not None:
        extra.append(
            (_VAR_MAP["HERMES_CRON_AUTO_DELIVER_PLATFORM"],
             _VAR_MAP["HERMES_CRON_AUTO_DELIVER_PLATFORM"].set(platform))
        )
        extra.append(
            (_VAR_MAP["HERMES_CRON_AUTO_DELIVER_CHAT_ID"],
             _VAR_MAP["HERMES_CRON_AUTO_DELIVER_CHAT_ID"].set(str(chat_id)))
        )
        if thread_id is not None:
            extra.append(
                (_VAR_MAP["HERMES_CRON_AUTO_DELIVER_THREAD_ID"],
                 _VAR_MAP["HERMES_CRON_AUTO_DELIVER_THREAD_ID"].set(str(thread_id)))
            )
    return tokens, extra


def _exit_cron_context(tokens, extra):
    for var, token in reversed(extra):
        var.reset(token)
    clear_session_vars(tokens)


def _create(deliver=None):
    from tools.cronjob_tools import cronjob

    return json.loads(
        cronjob(
            action="create",
            schedule="every 4h",
            prompt="check the thing",
            deliver=deliver,
        )
    )


class TestCronContextDeliveryResolution:
    def test_omitted_deliver_resolves_to_creator_target(self, temp_cron_home):
        tokens, extra = _enter_cron_context("telegram", "-100123456", "17")
        try:
            result = _create()
        finally:
            _exit_cron_context(tokens, extra)
        assert result["success"] is True
        assert result["deliver"] == "telegram:-100123456:17"

    def test_literal_origin_resolves_to_creator_target(self, temp_cron_home):
        tokens, extra = _enter_cron_context("telegram", "-100123456", "17")
        try:
            result = _create(deliver="origin")
        finally:
            _exit_cron_context(tokens, extra)
        assert result["deliver"] == "telegram:-100123456:17"

    def test_no_thread_id_omits_thread_segment(self, temp_cron_home):
        tokens, extra = _enter_cron_context("slack", "C0ABC")
        try:
            result = _create(deliver="origin")
        finally:
            _exit_cron_context(tokens, extra)
        assert result["deliver"] == "slack:C0ABC"

    def test_creator_without_delivery_target_falls_back_to_local(self, temp_cron_home):
        # Creator job delivers nowhere concrete (deliver='local' run):
        # AUTO_DELIVER vars unset. Child must store 'local', never 'origin'.
        tokens, extra = _enter_cron_context()
        try:
            result = _create(deliver="origin")
        finally:
            _exit_cron_context(tokens, extra)
        assert result["deliver"] == "local"

    def test_comma_list_resolves_only_origin_element(self, temp_cron_home):
        tokens, extra = _enter_cron_context("telegram", "-100123456", "17")
        try:
            result = _create(deliver="origin,all")
        finally:
            _exit_cron_context(tokens, extra)
        assert result["deliver"] == "telegram:-100123456:17,all"

    def test_explicit_target_passes_through_verbatim(self, temp_cron_home):
        tokens, extra = _enter_cron_context("telegram", "-100999", "3")
        try:
            result = _create(deliver="discord:#engineering")
        finally:
            _exit_cron_context(tokens, extra)
        assert result["deliver"] == "discord:#engineering"

    def test_local_passes_through(self, temp_cron_home):
        tokens, extra = _enter_cron_context("telegram", "-100999")
        try:
            result = _create(deliver="local")
        finally:
            _exit_cron_context(tokens, extra)
        assert result["deliver"] == "local"

    def test_stored_deliver_never_literal_origin_in_cron_context(self, temp_cron_home):
        from cron.jobs import get_job

        tokens, extra = _enter_cron_context("telegram", "-100123456")
        try:
            result = _create(deliver="origin")
        finally:
            _exit_cron_context(tokens, extra)
        job = get_job(result["job_id"])
        stored = str(job.get("deliver", ""))
        assert "origin" not in [p.strip() for p in stored.split(",")]


class TestCronContextUpdatePath:
    def test_update_deliver_origin_resolves_in_cron_context(self, temp_cron_home):
        """The update action must apply the same resolution as create — a
        cron agent updating deliver='origin' would otherwise recreate the
        dangling literal-origin shape on an origin-less job."""
        from tools.cronjob_tools import cronjob
        from cron.jobs import get_job

        tokens, extra = _enter_cron_context("telegram", "-100123456", "17")
        try:
            created = _create(deliver="local")
            result = json.loads(
                cronjob(action="update", job_id=created["job_id"], deliver="origin")
            )
        finally:
            _exit_cron_context(tokens, extra)
        assert result["success"] is True
        job = get_job(created["job_id"])
        stored = str(job.get("deliver", ""))
        assert "origin" not in [p.strip() for p in stored.split(",")]
        assert stored == "telegram:-100123456:17"

    def test_update_deliver_outside_cron_context_unchanged(self, temp_cron_home):
        from tools.cronjob_tools import cronjob
        from cron.jobs import get_job

        created = _create(deliver="local")
        result = json.loads(
            cronjob(action="update", job_id=created["job_id"], deliver="origin")
        )
        assert result["success"] is True
        assert get_job(created["job_id"]).get("deliver") == "origin"


class TestNonCronContextUnchanged:
    def test_chat_session_create_keeps_literal_origin(self, temp_cron_home):
        # No cron_session var — ordinary chat/CLI create. Existing semantics:
        # 'origin' stays literal and resolves at fire time.
        result = _create(deliver="origin")
        assert result["deliver"] == "origin"

    def test_chat_session_omitted_deliver_unchanged(self, temp_cron_home):
        # Outside cron context the resolution helper must be a no-op so the
        # ordinary chat/CLI create path is byte-identical to before.
        from tools.cronjob_tools import _resolve_cron_context_deliver

        assert _resolve_cron_context_deliver(None) is None
        assert _resolve_cron_context_deliver("origin") == "origin"
        assert _resolve_cron_context_deliver("origin,all") == "origin,all"
