from __future__ import annotations

import json
import subprocess
from pathlib import Path

import pytest

from scripts import leo_behavior_manifest as manifest

ROOT = Path(__file__).resolve().parents[1]


def _write(path: Path, value: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(value, encoding="utf-8")


def test_behavior_manifest_is_deterministic_and_never_emits_config_secrets(tmp_path: Path) -> None:
    profile = tmp_path / "profile"
    hermes = tmp_path / "hermes"
    deployment = tmp_path / "deployment"
    _write(
        profile / "config.yaml",
        """model:
  provider: openrouter
  default: anthropic/claude-sonnet-4-6
  smart_routing: true
  smart_routing_model: google/gemini-2.5-flash
memory:
  enabled: true
  search: sqlite_fts5
  nudge_interval: 0
skills:
  creation_nudge_interval: 0
gateway:
  telegram:
    bot_token: super-secret-token
""",
    )
    _write(profile / "SOUL.md", "# Leo\n")
    _write(profile / "skills" / "teleo-kb-bridge" / "SKILL.md", "# Bridge\n")
    _write(profile / "plugins" / "leo-db-context" / "__init__.py", "# plugin\n")
    _write(profile / "bin" / "kb_tool.py", "# tool\n")
    _write(profile / "memories" / "USER.md", "private participant fact\n")
    _write(profile / "sessions" / "one.jsonl", '{"private":"conversation"}\n')
    _write(profile / "state.db", "runtime-state")
    _write(hermes / "run_agent.py", "# runtime\n")
    _write(hermes / "agent" / "prompt_builder.py", "# prompts\n")
    _write(deployment / "scripts" / "leo_behavior_manifest.py", "# manifest\n")
    _write(deployment / "scripts" / "leo_tool_trace.py", "# tool trace\n")

    first = manifest.build_manifest(profile, hermes, deployment)
    second = manifest.build_manifest(profile, hermes, deployment)

    assert first["behavior_sha256"] == second["behavior_sha256"]
    assert first["model_runtime"]["default_model"] == "anthropic/claude-sonnet-4-6"
    assert first["model_runtime"]["automatic_memory_review_interval"] == 0
    assert first["model_runtime"]["automatic_skill_review_interval"] == 0
    assert first["teleo_infrastructure_runtime"]["source_tree"]["file_count"] == 2
    assert first["components"]["persistent_memory"]["content"]["file_count"] == 1
    encoded = json.dumps(first)
    assert "super-secret-token" not in encoded
    assert "private participant fact" not in encoded
    assert '{"private":"conversation"}' not in encoded


def test_identity_config_rejects_nonboolean_smart_model_routing() -> None:
    with pytest.raises(ValueError, match="smart_model_routing must be boolean"):
        manifest.identity_config_projection({"smart_model_routing": {"route": "untrusted"}})


def test_behavior_manifest_changes_when_a_behavior_layer_changes(tmp_path: Path) -> None:
    profile = tmp_path / "profile"
    hermes = tmp_path / "hermes"
    deployment = tmp_path / "deployment"
    _write(profile / "config.yaml", "model:\n  provider: openrouter\n  default: model-a\n")
    _write(profile / "SOUL.md", "identity-a\n")
    _write(hermes / "run_agent.py", "runtime\n")
    _write(hermes / "agent" / "prompt_builder.py", "prompts\n")
    _write(deployment / "scripts" / "leo_behavior_manifest.py", "manifest\n")
    _write(deployment / "scripts" / "leo_tool_trace.py", "tool trace\n")

    before = manifest.build_manifest(profile, hermes, deployment)
    _write(profile / "SOUL.md", "identity-b\n")
    after = manifest.build_manifest(profile, hermes, deployment)

    assert before["behavior_sha256"] != after["behavior_sha256"]
    assert (
        before["components"]["runtime_identity"]["content"]["sha256"]
        != after["components"]["runtime_identity"]["content"]["sha256"]
    )


def test_behavior_manifest_hashes_content_behind_symlinks(tmp_path: Path) -> None:
    profile = tmp_path / "profile"
    hermes = tmp_path / "hermes"
    deployment = tmp_path / "deployment"
    target = tmp_path / "deployed-skill.md"
    _write(profile / "config.yaml", "model:\n  provider: test\n")
    _write(profile / "SOUL.md", "identity\n")
    _write(target, "version-a\n")
    (profile / "skills").mkdir(parents=True)
    (profile / "skills" / "SKILL.md").symlink_to(target)
    _write(hermes / "run_agent.py", "runtime\n")
    _write(hermes / "agent" / "prompt_builder.py", "prompts\n")
    _write(deployment / "scripts" / "leo_behavior_manifest.py", "manifest\n")
    _write(deployment / "scripts" / "leo_tool_trace.py", "tool trace\n")

    before = manifest.build_manifest(profile, hermes, deployment)
    _write(target, "version-b\n")
    after = manifest.build_manifest(profile, hermes, deployment)

    assert before["behavior_sha256"] != after["behavior_sha256"]
    symlinks = after["components"]["procedural_skills"]["content"]["symlinks"]
    assert symlinks[0]["path"] == "skills/SKILL.md"
    assert symlinks[0]["target_fingerprint"]["kind"] == "symlink"
    assert symlinks[0]["target_fingerprint"]["resolved"]["kind"] == "file"


def test_live_handler_harness_excludes_prior_dynamic_state_and_compares_live_fingerprints() -> None:
    source = (ROOT / "scripts" / "run_leo_direct_claim_handler_suite.py").read_text(encoding="utf-8")

    for directory in ("sessions", "state", "memories"):
        assert f'"{directory}"' in source
    assert '".skills_prompt_snapshot.json"' in source
    assert "build_behavior_manifest" in source
    assert 'report["changed_live_profile"] =' in source
    assert '"changed_live_profile": False' not in source


def test_runtime_source_manifest_covers_nested_executable_sources(tmp_path: Path) -> None:
    profile = tmp_path / "profile"
    hermes = tmp_path / "hermes"
    deployment = tmp_path / "deployment"
    _write(profile / "config.yaml", "model:\n  provider: test\n")
    _write(profile / "SOUL.md", "identity\n")
    _write(hermes / "run_agent.py", "runtime\n")
    _write(hermes / "gateway" / "run.py", "gateway-a\n")
    _write(hermes / "tools" / "terminal.py", "terminal-a\n")
    _write(deployment / "scripts" / "leo_tool_trace.py", "trace-a\n")
    _write(deployment / "ansible" / "roles" / "leo" / "tasks" / "main.yml", "---\n")

    before = manifest.build_manifest(profile, hermes, deployment)
    _write(deployment / "ops" / "new_runtime_helper.py", "helper-b\n")
    after = manifest.build_manifest(profile, hermes, deployment)

    assert before["hermes_runtime"]["source_tree"]["file_count"] == 3
    assert before["teleo_infrastructure_runtime"]["source_tree"]["file_count"] == 2
    assert after["teleo_infrastructure_runtime"]["source_tree"]["file_count"] == 3
    assert (
        before["teleo_infrastructure_runtime"]["source_tree"]["sha256"]
        != after["teleo_infrastructure_runtime"]["source_tree"]["sha256"]
    )


def test_git_state_marks_untracked_files_dirty(tmp_path: Path) -> None:
    subprocess.run(["git", "init", "-q", str(tmp_path)], check=True)
    subprocess.run(["git", "-C", str(tmp_path), "config", "user.email", "test@example.com"], check=True)
    subprocess.run(["git", "-C", str(tmp_path), "config", "user.name", "Test"], check=True)
    _write(tmp_path / "tracked.py", "tracked\n")
    subprocess.run(["git", "-C", str(tmp_path), "add", "tracked.py"], check=True)
    subprocess.run(["git", "-C", str(tmp_path), "commit", "-qm", "fixture"], check=True)

    assert manifest.git_state(tmp_path)["worktree_clean"] is True
    _write(tmp_path / "untracked.py", "untracked\n")
    state = manifest.git_state(tmp_path)

    assert state["worktree_clean"] is False
    assert len(str(state["status_sha256"])) == 64


def test_identity_runtime_omits_secret_values_but_pins_semantic_model_settings(tmp_path: Path) -> None:
    profile = tmp_path / "profile"
    hermes = tmp_path / "hermes"
    deployment = tmp_path / "deployment"
    config = """model:
  provider: fixture
  default: identity-v1
  max_tokens: 512
gateway:
  telegram:
    bot_token: token-a
    botToken: camel-token-a
provider:
  apiKey: camel-api-key-a
transport:
  endpoint: https://fixture-user:fixture-password-a@example.invalid/v1
  headers: ['Authorization: Bearer fixture-header-a']
tools:
  allowed: [identity_readback]
identity_runtime:
  network_send_enabled: false
  database_write_enabled: false
  allowed_tools: [identity_readback]
"""
    _write(profile / "config.yaml", config)
    _write(profile / "skills" / "identity" / "SKILL.md", "identity\n")
    _write(profile / "plugins" / "identity" / "__init__.py", "BOUNDARY = True\n")
    _write(profile / "bin" / "identity.py", "READ_ONLY = True\n")
    _write(hermes / "run_agent.py", "runtime\n")
    _write(hermes / "agent" / "prompt_builder.py", "prompts\n")
    for name in (
        "leo_behavior_manifest.py",
        "leo_identity_manifest.py",
        "leo_identity_profile.py",
        "run_leo_identity_reconstruction_canary.py",
    ):
        _write(deployment / "scripts" / name, f"# {name}\n")

    before = manifest.build_manifest(profile, hermes, deployment)
    _write(
        profile / "config.yaml",
        config.replace("token-a", "token-b")
        .replace("camel-api-key-a", "camel-api-key-b")
        .replace("fixture-password-a", "fixture-password-b")
        .replace("fixture-header-a", "fixture-header-b"),
    )
    secret_rotated = manifest.build_manifest(profile, hermes, deployment)
    _write(profile / "config.yaml", config.replace("max_tokens: 512", "max_tokens: 1024"))
    model_changed = manifest.build_manifest(profile, hermes, deployment)

    assert before["behavior_sha256"] != secret_rotated["behavior_sha256"]
    assert before["identity_runtime_sha256"] == secret_rotated["identity_runtime_sha256"]
    assert before["identity_runtime_sha256"] != model_changed["identity_runtime_sha256"]
    encoded = json.dumps(before)
    assert "token-a" not in encoded
    assert "camel-token-a" not in encoded
    assert "camel-api-key-a" not in encoded
    assert "fixture-password-a" not in encoded
    assert "fixture-header-a" not in encoded
