"""
Optimus Research Center - Mission ORC-29 Candidate Certification Test Suite
Validates:
1. Formal separation of the 4 SQLite database roles (Protected Canonical, Candidate Initial Baseline,
   Experimental Test DB, and Portable Clean Install DB).
2. Strict code & bytecode provenance verification (blocking silent imports from production).
3. Checkpoint queue recovery (REQUEUED, resumed=False) vs verifiable engine state restoration
   (resumed_from_checkpoint=True) and tamper/ownership/idempotency rejection.
4. ORC-29 versioned candidate manifest SHA-256 verification.
"""

import os
import sys
import json
import types
import hashlib
from pathlib import Path
import pytest

from app.config import settings
from app.core.database import SessionLocal, init_db
from app.core.coordinator import ScientificCoordinator
from app.lab_algorithms.service import AlgorithmLabService
from tests.conftest import (
    assert_candidate_provenance_clean,
    PROTECTED_CANONICAL_DB_PATH,
    CANDIDATE_INITIAL_BASELINE_DB_PATH,
    EXPERIMENTAL_TEST_DB_PATH
)

BASE_DIR = Path(__file__).resolve().parent.parent
EXPECTED_CANONICAL_DB_SHA256 = "e4a5a8dae3b9bf9c24df50abc243cb4969f4cf4b29cc98f1013091cf01f9cde9"


def compute_sha256(file_path: Path) -> str:
    h = hashlib.sha256()
    with open(file_path, "rb") as f:
        while chunk := f.read(65536):
            h.update(chunk)
    return h.hexdigest().lower()


def test_orc29_four_sqlite_database_roles_separation():
    """
    Verifies formal separation between:
      1. Protected Canonical DB (Production)
      2. Candidate Initial Baseline DB (Candidate storage/db/research_center.db)
      3. Experimental Test DB (ORC_TEST_DB_PATH in temp dir)
    """
    # Role 3 must be active and distinct from Role 1 and Role 2
    active_db = Path(settings.db_path).resolve()
    assert active_db == EXPERIMENTAL_TEST_DB_PATH.resolve()
    assert active_db != CANDIDATE_INITIAL_BASELINE_DB_PATH.resolve()
    if PROTECTED_CANONICAL_DB_PATH.exists():
        assert active_db != PROTECTED_CANONICAL_DB_PATH.resolve()

    # Record hashes of Role 1 and Role 2 before writing an experiment to Role 3
    if PROTECTED_CANONICAL_DB_PATH.is_file():
        assert compute_sha256(PROTECTED_CANONICAL_DB_PATH) == EXPECTED_CANONICAL_DB_SHA256

    assert CANDIDATE_INITIAL_BASELINE_DB_PATH.is_file()
    assert compute_sha256(CANDIDATE_INITIAL_BASELINE_DB_PATH) == EXPECTED_CANONICAL_DB_SHA256

    # Execute a real scientific write into Role 3 (Experimental DB)
    init_db()
    db = SessionLocal()
    try:
        svc = AlgorithmLabService(db)
        res = svc.run_optimization_experiment(
            project_id=1,
            hypothesis_id=None,
            algorithm_names=["SimulatedAnnealing"],
            problem_type="sphere",
            dim=3,
            random_seed=42
        )
        assert res["status"] == "COMPLETED"
    finally:
        db.close()

    # Role 3 hash has legitimately evolved from the baseline, while Role 1 and Role 2 remain 100% immutable
    assert compute_sha256(active_db) != EXPECTED_CANONICAL_DB_SHA256
    assert compute_sha256(CANDIDATE_INITIAL_BASELINE_DB_PATH) == EXPECTED_CANONICAL_DB_SHA256
    if PROTECTED_CANONICAL_DB_PATH.is_file():
        assert compute_sha256(PROTECTED_CANONICAL_DB_PATH) == EXPECTED_CANONICAL_DB_SHA256


def test_orc29_code_provenance_and_anti_contamination_guard():
    """
    Verifies all loaded app.*, scripts.*, and tests.* modules originate from BASE_DIR
    and that simulated contamination from production is immediately blocked.
    """
    report = assert_candidate_provenance_clean(BASE_DIR)
    assert report["provenance_clean"] is True
    assert report["violations_count"] == 0
    assert report["checked_modules_count"] >= 10

    # Negative test: inject synthetic module claiming __file__ in protected production installation
    fake_mod_name = "app._synthetic_contaminated_probe"
    fake_mod = types.ModuleType(fake_mod_name)
    fake_mod.__file__ = r"C:\xampp\htdocs\centro de investigacion\app\main.py"
    sys.modules[fake_mod_name] = fake_mod
    try:
        if BASE_DIR.resolve() != Path(r"C:\xampp\htdocs\centro de investigacion").resolve():
            with pytest.raises(RuntimeError, match="PROVENANCE_CONTAMINATION_DETECTED"):
                assert_candidate_provenance_clean(BASE_DIR)
    finally:
        sys.modules.pop(fake_mod_name, None)


def test_orc29_checkpoint_tamper_and_ownership_rejection():
    """
    Verifies that tampered SHA-256 checkpoints or mismatched task_id ownership
    are rejected both at queue recovery time and at engine restoration time.
    """
    init_db()
    db = SessionLocal()
    try:
        coord = ScientificCoordinator(db)

        # 1. Tampered checkpoint hash in recover_interrupted_tasks -> RECONCILED_AS_FAILED
        t_tampered = coord.submit_task(
            task_type="DISCOVERY_CYCLE",
            payload={
                "problem_statement": "Tampered checkpoint test",
                "checkpoint": {
                    "resumable": True,
                    "idempotent": True,
                    "state": {"round_completed": 1, "r1_metric": -0.05},
                    "checkpoint_sha256": "0" * 64
                }
            },
            priority=2
        )
        coord.update_task_status(t_tampered.id, "RUNNING")

        # 2. Mismatched task_id ownership in recover_interrupted_tasks -> RECONCILED_AS_FAILED
        valid_state = {"round_completed": 1, "r1_metric": -0.05}
        valid_hash = hashlib.sha256(json.dumps(valid_state, sort_keys=True).encode("utf-8")).hexdigest()
        t_wrong_owner = coord.submit_task(
            task_type="DISCOVERY_CYCLE",
            payload={
                "problem_statement": "Wrong owner checkpoint test",
                "checkpoint": {
                    "resumable": True,
                    "idempotent": True,
                    "task_id": 999999,
                    "task_type": "DISCOVERY_CYCLE",
                    "state": valid_state,
                    "checkpoint_sha256": valid_hash
                }
            },
            priority=2
        )
        coord.update_task_status(t_wrong_owner.id, "RUNNING")

        recovered = coord.recover_interrupted_tasks()
        rec_map = {r["task_id"]: r for r in recovered}

        assert rec_map[t_tampered.id]["recovery_action"] == "RECONCILED_AS_FAILED"
        assert rec_map[t_tampered.id]["requeued"] is False
        assert rec_map[t_tampered.id]["resumed"] is False

        assert rec_map[t_wrong_owner.id]["recovery_action"] == "RECONCILED_AS_FAILED"
        assert rec_map[t_wrong_owner.id]["requeued"] is False
        assert rec_map[t_wrong_owner.id]["resumed"] is False
        assert rec_map[t_wrong_owner.id]["ownership_verified"] is False

        # 3. Direct call to run_discovery_cycle with tampered checkpoint raises ValueError
        with pytest.raises(ValueError, match="INVALID_CHECKPOINT_HASH"):
            coord.run_discovery_cycle(
                problem_statement="Direct tamper test",
                lab_type="ALGORITHM",
                benchmark_target="sphere",
                dimension=3,
                checkpoint={
                    "resumable": True,
                    "idempotent": True,
                    "state": valid_state,
                    "checkpoint_sha256": "f" * 64
                }
            )
    finally:
        db.close()


def test_orc29_versioned_manifest_integrity():
    """Verifies ORC29_CANDIDATE_MANIFEST.json exists and matches all tracked files."""
    manifest_file = BASE_DIR / "ORC29_CANDIDATE_MANIFEST.json"
    assert manifest_file.is_file(), "ORC29_CANDIDATE_MANIFEST.json must exist in candidate root"
    manifest = json.loads(manifest_file.read_text(encoding="utf-8"))
    assert manifest.get("mission") == "ORC-29"
    tracked = manifest.get("core_and_modified_files_sha256", {})
    assert len(tracked) >= 15
    for rel_path, expected_sha in tracked.items():
        fp = BASE_DIR / rel_path
        assert fp.is_file(), f"Tracked file in ORC29_CANDIDATE_MANIFEST.json missing: {rel_path}"
        actual_sha = compute_sha256(fp)
        assert actual_sha == expected_sha.lower(), f"Hash mismatch for {rel_path}: expected {expected_sha}, got {actual_sha}"

