#!/usr/bin/env python3
"""Prove manifest-driven Leo identity reconstruction across a cold restart."""

from __future__ import annotations

import argparse
import json
import os
import shutil
import signal
import subprocess
import sys
import tempfile
import time
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath, PureWindowsPath
from typing import Any

if __package__ in {None, ""}:
    sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from scripts import leo_behavior_manifest as behavior
from scripts import leo_identity_manifest as identity
from scripts import leo_identity_profile as profile_runtime

SCHEMA = "livingip.leoIdentityReconstructionReceipt.v1"
PRIVATE_PATH_PREFIXES = tuple("/" + value for value in ("Users/", "private/tmp/", "var/folders/"))


class CanaryError(RuntimeError):
    """The disposable identity runtime did not satisfy its T2 contract."""


def _require(condition: bool, message: str) -> None:
    if not condition:
        raise CanaryError(message)


def _receipt_strings(value: object) -> list[str]:
    if isinstance(value, dict):
        keys = [key for key in value if isinstance(key, str)]
        return keys + [text for item in value.values() for text in _receipt_strings(item)]
    if isinstance(value, list):
        return [text for item in value for text in _receipt_strings(item)]
    return [value] if isinstance(value, str) else []


def _validate_portable_receipt(value: object) -> None:
    for text in _receipt_strings(value):
        if (
            any(prefix in text for prefix in PRIVATE_PATH_PREFIXES)
            or PurePosixPath(text).is_absolute()
            or PureWindowsPath(text).is_absolute()
        ):
            raise CanaryError("receipt contains a private or absolute filesystem path")


def _process_group_absent(process_group: int) -> bool:
    try:
        os.killpg(process_group, 0)
    except ProcessLookupError:
        return True
    except PermissionError:
        return False
    return False


def _wait_for_process_group_absence(process_group: int, timeout_seconds: float = 5) -> bool:
    deadline = time.monotonic() + timeout_seconds
    while time.monotonic() < deadline:
        if _process_group_absent(process_group):
            return True
        time.sleep(0.02)
    return _process_group_absent(process_group)


def _signal_process_group(process_group: int, signal_number: signal.Signals) -> bool:
    try:
        os.killpg(process_group, signal_number)
        return True
    except (ProcessLookupError, PermissionError):
        return False


def _cleanup_remaining_process_group(process_group: int) -> str | None:
    if not _signal_process_group(process_group, signal.SIGTERM):
        return None
    if _wait_for_process_group_absence(process_group, timeout_seconds=1):
        return "SIGTERM"
    if not _signal_process_group(process_group, signal.SIGKILL):
        return "SIGTERM"
    return "SIGKILL"


def _logical_repo_path(path: Path, *, deployment_root: Path, placeholder: str) -> str:
    """Return a stable repo-relative path or a non-identifying placeholder."""

    try:
        relative = path.resolve().relative_to(deployment_root.resolve())
    except (OSError, ValueError):
        return placeholder
    value = relative.as_posix()
    return value if value != "." else "."


def _logical_query_command(
    *,
    deployment_root: Path,
    source_root: Path,
    hermes_root: Path,
) -> list[str]:
    return [
        "<python>",
        "-m",
        "scripts.leo_identity_profile",
        "query",
        "--profile",
        "<temporary-profile>",
        "--source-root",
        _logical_repo_path(source_root, deployment_root=deployment_root, placeholder="<source-root>"),
        "--hermes-root",
        _logical_repo_path(hermes_root, deployment_root=deployment_root, placeholder="<hermes-root>"),
        "--deployment-root",
        ".",
        "--query",
        profile_runtime.FIXED_QUERY,
    ]


def _run_query_child(
    *,
    deployment_root: Path,
    profile: Path,
    source_root: Path,
    hermes_root: Path,
    profile_role: str,
    timeout_seconds: float = 60,
) -> dict[str, Any]:
    deployment_root = deployment_root.resolve()
    profile = profile.resolve()
    source_root = source_root.resolve()
    hermes_root = hermes_root.resolve()
    actual_argv = [
        sys.executable,
        "-m",
        "scripts.leo_identity_profile",
        "query",
        "--profile",
        str(profile),
        "--source-root",
        str(source_root),
        "--hermes-root",
        str(hermes_root),
        "--deployment-root",
        str(deployment_root),
        "--query",
        profile_runtime.FIXED_QUERY,
    ]
    process = subprocess.Popen(
        actual_argv,
        cwd=deployment_root,
        env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"},
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        start_new_session=True,
    )
    timed_out = False
    terminated_with: str | None = None
    stdout = ""
    stderr = ""
    process_group_absent_after_wait = False
    orphan_cleanup_required = False
    try:
        try:
            stdout, stderr = process.communicate(timeout=timeout_seconds)
        except subprocess.TimeoutExpired:
            timed_out = True
            if _signal_process_group(process.pid, signal.SIGTERM):
                terminated_with = "SIGTERM"
            try:
                stdout, stderr = process.communicate(timeout=5)
            except subprocess.TimeoutExpired:
                if _signal_process_group(process.pid, signal.SIGKILL):
                    terminated_with = "SIGKILL"
                stdout, stderr = process.communicate(timeout=5)
    finally:
        if process.poll() is None:
            if _signal_process_group(process.pid, signal.SIGTERM):
                terminated_with = terminated_with or "SIGTERM"
            try:
                process.communicate(timeout=5)
            except subprocess.TimeoutExpired:
                if _signal_process_group(process.pid, signal.SIGKILL):
                    terminated_with = "SIGKILL"
                process.communicate(timeout=5)
        orphan_cleanup_required = not _process_group_absent(process.pid)
        if orphan_cleanup_required:
            terminated_with = _cleanup_remaining_process_group(process.pid) or terminated_with
        process_group_absent_after_wait = _wait_for_process_group_absence(process.pid)
    try:
        payload = json.loads(stdout.strip().splitlines()[-1]) if stdout.strip() else {}
    except json.JSONDecodeError as exc:
        raise CanaryError("identity child returned invalid JSON") from exc
    return {
        "logical_command": _logical_query_command(
            deployment_root=deployment_root,
            source_root=source_root,
            hermes_root=hermes_root,
        ),
        "command_serialization": "logical_redacted_v1",
        "profile_role": profile_role,
        "actual_argv_persisted": False,
        "launcher_pid": process.pid,
        "returncode": process.returncode,
        "timed_out": timed_out,
        "terminated_with": terminated_with,
        "orphan_cleanup_required": orphan_cleanup_required,
        "stdout": payload,
        "stderr_sha256": behavior.sha256_bytes(stderr.encode("utf-8")),
        "process_group_absent_after_wait": process_group_absent_after_wait,
    }


def _query_passed(child: dict[str, Any]) -> bool:
    payload = child.get("stdout") or {}
    return bool(
        child.get("returncode") == 0
        and child.get("timed_out") is False
        and child.get("orphan_cleanup_required") is False
        and child.get("process_group_absent_after_wait") is True
        and payload.get("schema") == profile_runtime.QUERY_RECEIPT_SCHEMA
        and payload.get("status") == "pass"
    )


def _drift_attempt(
    *,
    deployment_root: Path,
    source_root: Path,
    hermes_root: Path,
    drift_profile: Path,
) -> dict[str, Any]:
    with (drift_profile / "SOUL.md").open("a", encoding="utf-8") as handle:
        handle.write("\nInjected drift must fail closed.\n")
    child = _run_query_child(
        deployment_root=deployment_root,
        profile=drift_profile,
        source_root=source_root,
        hermes_root=hermes_root,
        profile_role="drift_negative",
    )
    child["fail_closed"] = bool(
        child["returncode"] == 3
        and child["timed_out"] is False
        and child["orphan_cleanup_required"] is False
        and child["process_group_absent_after_wait"] is True
        and (child.get("stdout") or {}).get("error") == "identity_drift"
        and "compiled view drift detected" in str((child.get("stdout") or {}).get("message"))
    )
    child["answer_returned"] = bool((child.get("stdout") or {}).get("answer"))
    return child


def run_canary(args: argparse.Namespace) -> tuple[dict[str, Any], int]:
    output = args.output.resolve()
    temporary_root = Path(tempfile.mkdtemp(prefix="leo-identity-reconstruction-"))
    report: dict[str, Any] = {
        "schema": SCHEMA,
        "generated_at_utc": datetime.now(UTC).isoformat(),
        "required_tier": "T2_runtime",
        "mode": "drift_before_restart"
        if args.inject_drift_before_restart
        else "successful_restart_plus_drift_negative",
        "fixed_query": profile_runtime.FIXED_QUERY,
        "fixed_query_sha256": behavior.sha256_bytes(profile_runtime.FIXED_QUERY.encode("utf-8")),
        "production_profile_replaced": False,
        "production_database_contacted": False,
        "telegram_message_sent": False,
        "actual_hosted_model_call_performed": False,
        "errors": [],
    }
    exit_code = 1
    try:
        behavior_manifest = behavior.build_manifest(args.profile_template, args.hermes_root, args.deployment_root)
        identity.validate_behavior_manifest(behavior_manifest)
        manifest = identity.build_identity_manifest(
            behavior_manifest=behavior_manifest,
            database_fingerprint_path=args.database_fingerprint,
            constitution_path=args.constitution,
            database_identity_path=args.database_identity,
            source_root=args.source_root,
        )
        report["manifest"] = manifest
        report["behavior_observation_before_compile"] = {
            "behavior_sha256": behavior_manifest["behavior_sha256"],
            "identity_runtime_sha256": behavior_manifest["identity_runtime_sha256"],
            "source_commit": behavior_manifest["teleo_infrastructure_runtime"]["git_head"],
        }
        compiled_profile = temporary_root / "first-profile"
        report["compile"] = profile_runtime.compile_profile(
            manifest,
            source_root=args.source_root,
            profile_template=args.profile_template,
            profile=compiled_profile,
            hermes_root=args.hermes_root,
            deployment_root=args.deployment_root,
        )
        compiled_behavior_before_query = behavior.build_manifest(
            compiled_profile, args.hermes_root, args.deployment_root
        )
        report["compiled_profile_before_query"] = {
            "behavior_sha256": compiled_behavior_before_query["behavior_sha256"],
            "identity_runtime_sha256": compiled_behavior_before_query["identity_runtime_sha256"],
        }
        first = _run_query_child(
            deployment_root=args.deployment_root,
            profile=compiled_profile,
            source_root=args.source_root,
            hermes_root=args.hermes_root,
            profile_role="first_start",
        )
        report["first_start"] = first
        _require(_query_passed(first), "first disposable identity process did not answer successfully")
        after_first = behavior.build_manifest(compiled_profile, args.hermes_root, args.deployment_root)
        report["session_boundary_readback"] = {
            "full_behavior_changed_after_session": after_first["behavior_sha256"]
            != compiled_behavior_before_query["behavior_sha256"],
            "identity_runtime_unchanged_after_session": after_first["identity_runtime_sha256"]
            == compiled_behavior_before_query["identity_runtime_sha256"],
            "session_file_count": len(list((compiled_profile / "sessions").glob("*.json"))),
            "session_classification": "temporary_noncanonical",
            "session_used_as_output_provenance": False,
        }
        if args.inject_drift_before_restart:
            with (compiled_profile / "SOUL.md").open("a", encoding="utf-8") as handle:
                handle.write("\nInjected pre-restart drift.\n")
            rejected = _run_query_child(
                deployment_root=args.deployment_root,
                profile=compiled_profile,
                source_root=args.source_root,
                hermes_root=args.hermes_root,
                profile_role="pre_restart_drift",
            )
            report["pre_restart_drift"] = rejected
            report["drift_target"] = "compiled SOUL.md generated view"
            report["second_start_attempted"] = False
            report["gates"] = {
                "first_start_passed": True,
                "first_process_stopped": first["process_group_absent_after_wait"],
                "drift_detected_before_restart": rejected["returncode"] == 3
                and (rejected.get("stdout") or {}).get("error") == "identity_drift",
                "drift_returned_no_answer": not bool((rejected.get("stdout") or {}).get("answer")),
                "drift_process_stopped": rejected["process_group_absent_after_wait"],
                "drift_process_had_no_orphan_cleanup": rejected["orphan_cleanup_required"] is False,
                "second_start_not_attempted": True,
            }
            report["status"] = "pass" if all(report["gates"].values()) else "fail"
        else:
            profile_runtime.verify_profile(
                compiled_profile,
                source_root=args.source_root,
                hermes_root=args.hermes_root,
                deployment_root=args.deployment_root,
            )
            report["pre_restart_verification"] = "pass"
            report["second_start_attempted"] = True
            restarted_profile = temporary_root / "restart-profile"
            report["restart_compile"] = profile_runtime.compile_profile(
                manifest,
                source_root=args.source_root,
                profile_template=args.profile_template,
                profile=restarted_profile,
                hermes_root=args.hermes_root,
                deployment_root=args.deployment_root,
            )
            profile_runtime.verify_profile(
                restarted_profile,
                source_root=args.source_root,
                hermes_root=args.hermes_root,
                deployment_root=args.deployment_root,
            )
            second = _run_query_child(
                deployment_root=args.deployment_root,
                profile=restarted_profile,
                source_root=args.source_root,
                hermes_root=args.hermes_root,
                profile_role="restart",
            )
            report["restart"] = second
            _require(_query_passed(second), "restarted disposable identity process did not answer successfully")
            first_payload = first["stdout"]
            second_payload = second["stdout"]
            drift_profile = temporary_root / "drift-profile"
            report["drift_compile"] = profile_runtime.compile_profile(
                manifest,
                source_root=args.source_root,
                profile_template=args.profile_template,
                profile=drift_profile,
                hermes_root=args.hermes_root,
                deployment_root=args.deployment_root,
            )
            drift = _drift_attempt(
                deployment_root=args.deployment_root,
                source_root=args.source_root,
                hermes_root=args.hermes_root,
                drift_profile=drift_profile,
            )
            report["drift_negative"] = drift
            report["drift_target"] = "compiled SOUL.md generated view"
            report["gates"] = {
                "manifest_generated": identity._is_sha256(manifest["manifest_sha256"]),
                "views_compiled_and_bound": report["compile"]["status"] == "pass",
                "first_start_passed": True,
                "first_process_stopped": first["process_group_absent_after_wait"],
                "pre_restart_verification_passed": True,
                "restart_profile_recompiled_from_manifest": report["restart_compile"]["status"] == "pass",
                "restart_profile_started_without_sessions": report["restart_compile"][
                    "session_directories_started_empty"
                ],
                "restart_passed": True,
                "restart_process_is_distinct": first_payload["process_id"] != second_payload["process_id"]
                and first_payload["runtime_instance_id"] != second_payload["runtime_instance_id"],
                "identity_inputs_reproduced": first_payload["identity_inputs_sha256"]
                == second_payload["identity_inputs_sha256"],
                "manifest_reproduced": first_payload["manifest_sha256"] == second_payload["manifest_sha256"],
                "query_reproduced": first_payload["query_sha256"] == second_payload["query_sha256"],
                "answer_and_provenance_explainable": first_payload["answer_sha256"] == second_payload["answer_sha256"]
                and first_payload["output_provenance"] == second_payload["output_provenance"],
                "session_excluded_from_identity": report["session_boundary_readback"][
                    "identity_runtime_unchanged_after_session"
                ],
                "drift_failed_closed": drift["fail_closed"] and not drift["answer_returned"],
                "drift_profile_started_without_sessions": report["drift_compile"]["session_directories_started_empty"],
                "drift_process_stopped": drift["process_group_absent_after_wait"],
                "drift_process_had_no_orphan_cleanup": drift["orphan_cleanup_required"] is False,
                "restart_process_stopped": second["process_group_absent_after_wait"],
            }
            report["status"] = "pass" if all(report["gates"].values()) else "fail"
        if report["status"] == "pass":
            exit_code = 0
    except (CanaryError, identity.IdentityManifestError, OSError, subprocess.SubprocessError) as exc:
        report["status"] = "fail"
        report["errors"].append(
            {
                "type": type(exc).__name__,
                "message": "details_redacted",
                "detail_sha256": behavior.sha256_bytes(str(exc).encode("utf-8")),
            }
        )
    finally:
        shutil.rmtree(temporary_root, ignore_errors=True)
        report["cleanup"] = {
            "temporary_root_removed": not temporary_root.exists(),
            "no_profile_retained": not temporary_root.exists(),
        }
        if not all(report["cleanup"].values()):
            report["status"] = "fail"
            exit_code = 1
        positive_restart_passed = report.get("status") == "pass" and not args.inject_drift_before_restart
        report["current_tier"] = "T2_runtime" if positive_restart_passed else "T1_model"
        report["goal_tier_satisfied"] = positive_restart_passed
        report["runtime_component_proven"] = (
            "fresh_profile_recompile_plus_distinct_process_restart"
            if positive_restart_passed
            else "first_local_process_plus_pre_restart_drift_refusal"
        )
        report["runtime_variant"] = "local_deterministic_identity_compiler_readback"
        report["tier_basis"] = (
            "Capability Tier Proof defines T2_runtime as local API/runtime behavior with restart where relevant; "
            "this is compiler-process runtime proof and explicitly excludes GatewayRunner and hosted-model proof."
            if positive_restart_passed
            else "This negative component does not complete the required restart path, so it remains below T2_runtime."
        )
        report["claim_ceiling"] = (
            "Local disposable identity compilation, fresh-profile reconstruction, and cold-process restart only; "
            "not a live GatewayRunner, hosted-model, Telegram, production database, VPS restart, or T3 proof."
            if positive_restart_passed
            else "Local first-process and pre-restart drift-refusal component only; no successful restart proof, "
            "GatewayRunner, hosted model, production database, VPS, or T3 claim."
        )
        _validate_portable_receipt(report)
        stable = {key: value for key, value in report.items() if key != "receipt_sha256"}
        report["receipt_sha256"] = behavior.canonical_sha256(stable)
        identity.write_json(output, report)
    return report, exit_code


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--profile-template", type=Path, required=True)
    parser.add_argument("--hermes-root", type=Path, required=True)
    parser.add_argument("--deployment-root", type=Path, required=True)
    parser.add_argument("--source-root", type=Path, required=True)
    parser.add_argument("--database-fingerprint", type=Path, required=True)
    parser.add_argument("--constitution", type=Path, required=True)
    parser.add_argument("--database-identity", type=Path, required=True)
    parser.add_argument("--output", type=Path, required=True)
    parser.add_argument("--inject-drift-before-restart", action="store_true")
    args = parser.parse_args()
    report, exit_code = run_canary(args)
    print(
        json.dumps(
            {
                "status": report.get("status"),
                "current_tier": report.get("current_tier"),
                "receipt_sha256": report.get("receipt_sha256"),
                "output": str(args.output),
            },
            sort_keys=True,
        )
    )
    return exit_code


if __name__ == "__main__":
    raise SystemExit(main())
