from __future__ import annotations

from scripts import leo_tool_trace
from scripts.run_gcp_generated_db_blind_claim_canary import (
    DISCOVERY_SUBCOMMANDS,
    EXPECTED_CLAIM_ID,
    EXPECTED_SOURCE_IDS,
    EXPECTED_SUBCOMMANDS,
    PROMPT,
    reply_outcomes,
    sanitize_gateway,
    should_reexec_in_hermes,
    summarize_wrapper_tool_proof,
    trace_facts,
    transcript_events_to_messages,
)


def retrieval_content(*row_ids: str) -> str:
    return "\n".join(
        [
            "schema: livingip.teleoKbRetrievalReceipt.v1",
            f"semantic context SHA-256: `{'1' * 64}`",
            f"artifact state SHA-256: `{'2' * 64}`",
            "DB read consistency: `stable_wal_marker`",
            *row_ids,
        ]
    )


def test_blind_prompt_contains_no_row_identifier() -> None:
    assert EXPECTED_CLAIM_ID not in PROMPT
    assert not EXPECTED_SOURCE_IDS.intersection(PROMPT.split())


def test_blind_runner_reexecs_when_venv_python_symlinks_to_system_python(tmp_path) -> None:
    system_python = tmp_path / "system-python"
    system_python.touch()
    hermes_python = tmp_path / "hermes-python"
    hermes_python.symlink_to(system_python)

    assert should_reexec_in_hermes(system_python, hermes_python) is True
    assert should_reexec_in_hermes(hermes_python, hermes_python) is False


def test_transcript_proves_three_receipted_read_calls_and_expected_rows() -> None:
    source_ids = sorted(EXPECTED_SOURCE_IDS)
    events = []
    for index, (subcommand, content) in enumerate(
        (
            ("search", retrieval_content(EXPECTED_CLAIM_ID)),
            ("show", retrieval_content(EXPECTED_CLAIM_ID)),
            ("evidence", retrieval_content(EXPECTED_CLAIM_ID, *source_ids)),
        )
    ):
        call_id = f"call-{index}"
        events.extend(
            [
                {
                    "phase": "call",
                    "tool_call_id": call_id,
                    "tool_name": "terminal",
                    "arguments": {"command": f"teleo-kb {subcommand} query"},
                },
                {
                    "phase": "result",
                    "tool_call_id": call_id,
                    "tool_name": "terminal",
                    "content": content,
                },
            ]
        )

    trace = leo_tool_trace.extract_kb_tool_trace(transcript_events_to_messages(events))
    subcommands, row_ids, receipt_count = trace_facts(trace)

    assert subcommands >= EXPECTED_SUBCOMMANDS
    assert subcommands & DISCOVERY_SUBCOMMANDS
    assert EXPECTED_CLAIM_ID in row_ids
    assert row_ids >= EXPECTED_SOURCE_IDS
    assert receipt_count == 3
    assert trace["database_tool_calls_read_only"] is True


def test_gateway_sanitization_hashes_events_without_retaining_them() -> None:
    secret = "raw-secret-that-must-not-survive"
    sanitized = sanitize_gateway(
        {
            "reply": "safe reply",
            "transcript_tool_trace": {
                "event_count": 1,
                "events": [{"content": secret}],
            },
        }
    )

    assert sanitized["reply"] == "safe reply"
    assert sanitized["transcript_tool_trace"]["event_count"] == 1
    assert sanitized["transcript_tool_trace"]["raw_events_retained"] is False
    assert len(sanitized["transcript_tool_trace"]["events_sha256"]) == 64
    assert secret not in str(sanitized)


def test_wrapper_summary_drops_raw_arguments_but_keeps_database_binding() -> None:
    secret_argument = "do-not-retain-this-query"
    summarized = summarize_wrapper_tool_proof(
        {
            "event_count": 2,
            "invocation_count": 1,
            "complete_start_end_pairing": True,
            "all_bound_to_supplied_target": True,
            "database_read_only_required": True,
            "default_read_only_required": True,
            "all_completed_successfully": True,
            "invocations": [
                {
                    "argv": ["search", secret_argument],
                    "container": "teleo-prod-1",
                    "database": "teleo_clone_test",
                    "database_identity": {"ssl": True},
                    "returncode": 0,
                }
            ],
        }
    )

    assert summarized["invocations"][0]["subcommand"] == "search"
    assert summarized["invocations"][0]["argument_count"] == 1
    assert summarized["invocations"][0]["database"] == "teleo_clone_test"
    assert summarized["raw_argv_retained"] is False
    assert secret_argument not in str(summarized)


def test_reply_outcomes_accepts_reasoned_alternative_decomposition() -> None:
    reply = """
    The support is thin: the two sources do not establish the full legal conclusion.
    I would split it into candidate claims about acquisition due diligence and valuation risk,
    plus a separate representations and warranties claim. Please provide the filing or URL,
    then review and confirm the candidates before any apply step or database write goes live.
    """

    assert all(reply_outcomes(reply).values())


def test_reply_outcomes_rejects_assertion_without_decomposition_or_iteration() -> None:
    reply = "The claim is weak. I propose a better claim."

    outcomes = reply_outcomes(reply)

    assert outcomes["challenges_weak_support"] is False
    assert outcomes["decomposes_the_bundled_legal_claim"] is False
    assert outcomes["asks_for_user_iteration_before_live"] is False


def test_reply_outcomes_rejects_forbidden_stakeholder_alias() -> None:
    otherwise_valid = """
    The evidence is thin. I propose candidate claims about acquisition due diligence and
    representations and warranties. Please review the source URL before any apply step goes live.
    Next Cory-style follow-up: choose the claim framing.
    """

    assert reply_outcomes(otherwise_valid)["uses_only_m3taversal_handle"] is False
