from __future__ import annotations
import importlib.util
import json
import sys
from pathlib import Path
from typing import Any

import pytest

HERE = Path(__file__).parent
SPEC = importlib.util.spec_from_file_location(
    "continuous", HERE / "continuous_lifecycle.py"
)
assert SPEC and SPEC.loader
m = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = m
SPEC.loader.exec_module(m)


def base() -> dict[str, Any]:
    return {
        "bootstrap": {
            "state": "AWAITING_ACTIVATION",
            "generation": 5,
            "session_id": m.SESSION,
            "sid_hash": m.SID,
        },
        "customer": {"enabled": False, "route": [m.CUSTOMER, m.CUSTOMER, "0"]},
        "workflow": {
            "state": "collecting",
            "cursor": 0,
            "answers": {},
            "consumed_updates": [159],
        },
        "publication": {
            "generation": 0,
            "state": "COMMITTED",
            "message_id": 161,
            "payload": {"state": "collecting"},
        },
        "outbox": [
            {
                "session_id": m.SESSION,
                "generation": 0,
                "state": "COMMITTED",
                "message_id": 161,
                "role": "customer",
                "route": [m.CUSTOMER, "0"],
                "payload": {"state": "collecting"},
            }
        ],
        "owner_callbacks": [],
        "wizard": None,
        "generations": [],
        "cards": [],
        "drafts": {},
        "deliveries": {},
        "activation": [],
    }


def test_current_prompt_and_successor_binding():
    p = m.classify(base())
    assert p.status == "READY_CUSTOMER_ONBOARDING"
    assert p.message_id == "161"
    assert p.action == "submit_answer:date_of_birth"
    assert m.SUCCESSOR.startswith("30bcd663")


def test_onboarding_handoffs_learn_committed_messages():
    s = base()
    s["workflow"].update(state="customer_attestation", cursor=22)
    s["publication"].update(
        generation=23, message_id=190, payload={"state": "customer_attestation"}
    )
    s["outbox"][-1] = dict(
        s["outbox"][-1],
        generation=23,
        message_id=190,
        payload={"state": "customer_attestation"},
    )
    p = m.classify(s)
    assert p.status == "READY_CUSTOMER_ATTEST"
    assert p.message_id == "190"
    assert p.action.startswith("non2:attest:23:")
    s["workflow"]["state"] = "owner_review"
    s["publication"].update(
        generation=25, message_id=191, payload={"state": "owner_review"}
    )
    s["outbox"][-1] = dict(
        s["outbox"][-1],
        generation=25,
        message_id=191,
        role="owner",
        route=[m.OWNER, "0"],
        payload={"state": "owner_review"},
    )
    p = m.classify(s)
    assert p.status == "READY_OWNER_ONBOARDING_APPROVE"
    assert p.actor == m.OWNER


def test_ready_emits_exact_operator_cutover_gate():
    s = base()
    s["workflow"] = {
        "state": "ready",
        "cursor": 22,
        "answers": {},
        "consumed_updates": [159, 200],
    }
    s["publication"].update(generation=26, message_id=192, payload={"state": "ready"})
    s["outbox"][-1] = dict(
        s["outbox"][-1],
        generation=26,
        message_id=192,
        role="owner",
        route=[m.OWNER, "0"],
        payload={"state": "ready"},
    )
    p = m.classify(s)
    assert p.status == "READY_OPERATOR_ACTIVATION_CUTOVER"
    assert "--bootstrap-session " + m.SESSION in p.command
    assert "--expected-generation 5" in p.command
    assert "<CHECKLIST_EVIDENCE>" in p.command


def test_active_checkin_generation_review_delivery_cleanup():
    s = base()
    s["bootstrap"]["state"] = "ACTIVE"
    s["bootstrap"]["generation"] = 6
    s["customer"]["enabled"] = True
    assert m.classify(s).status == "READY_CUSTOMER_START_CHECKIN"
    s["wizard"] = {
        "session_id": "wiz1",
        "version": 3,
        "step": "calories",
        "message_id": "201",
        "route": [m.CUSTOMER, "0"],
        "finalized_event_id": None,
    }
    assert m.classify(s).status == "READY_CUSTOMER_CHECKIN_ANSWER"
    s["wizard"].update(step="summary", version=13, finalized_event_id="evt1")
    assert m.classify(s).status == "WAIT_AUTOMATIC_GENERATION"
    s["generations"] = [
        {
            "token": "tok1",
            "customer_key": m.KEY,
            "session_id": "wiz1",
            "state": "generated",
            "generation": 3,
        }
    ]
    s["cards"] = [
        {
            "token": "tok1",
            "state": "published",
            "message_id": "210",
            "destination": {"user_id": m.OWNER, "chat_id": m.OWNER, "topic_id": "0"},
            "generation": 3,
        }
    ]
    s["drafts"] = {
        "draft1": {"customer_key": m.KEY, "session_id": "wiz1", "status": "created"}
    }
    p = m.classify(s)
    assert p.status == "READY_OWNER_DRAFT_REVIEW"
    assert p.message_id == "210"
    s["drafts"]["draft1"]["status"] = "approved"
    assert m.classify(s).status == "READY_OWNER_EXPLICIT_SEND"
    s["deliveries"] = {
        "d": {
            "customer_key": m.KEY,
            "session_id": "wiz1",
            "draft_id": "draft1",
            "status": "sent_audited",
            "message_id": "220",
            "provider_chat_id": m.CUSTOMER,
            "provider_topic_id": "0",
        }
    }
    assert m.classify(s).status == "READY_OPERATOR_DISABLE"
    s["customer"]["enabled"] = False
    assert m.classify(s).status == "READY_CLEANUP_HANDOFF"


def test_monotonic_guard_rejects_stale_duplicate_wrong_route():
    a = base()
    b = json.loads(json.dumps(a))
    b["workflow"]["cursor"] = 1
    b["publication"]["generation"] = 1
    m.validate_transition(a, b)
    with pytest.raises(m.LifecycleError):
        m.validate_transition(b, a)
    bad = json.loads(json.dumps(b))
    bad["outbox"].append(dict(bad["outbox"][0]))
    with pytest.raises(m.LifecycleError):
        m.validate_snapshot(bad)
    bad = json.loads(json.dumps(b))
    bad["outbox"][0]["route"] = ["9", "0"]
    with pytest.raises(m.LifecycleError):
        m.validate_snapshot(bad)


def test_append_ledger_is_private_and_hash_chained(tmp_path: Path):
    p = tmp_path / "events.jsonl"
    m.append_event(p, {"status": "A"})
    m.append_event(p, {"status": "B"})
    assert oct(p.stat().st_mode & 0o777) == "0o600"
    rows = [json.loads(x) for x in p.read_text().splitlines()]
    assert rows[1]["previous_event_sha256"] == rows[0]["event_sha256"]
