"""Unit tests for proof_evidence validator module."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest

from mcp_vm_blackbox.coordination_db import _reset_db_instance_for_testing
from mcp_vm_blackbox.demo_manifest import MissingReason
from mcp_vm_blackbox.job_store import JobStore
from mcp_vm_blackbox.proof_evidence import EvidenceCheck, ProofEvidenceResult, validate_proof_evidence

if TYPE_CHECKING:
    from collections.abc import Generator
    from pathlib import Path

# ---------------------------------------------------------------------------
# Test fixtures
# ---------------------------------------------------------------------------


@pytest.fixture(autouse=True)
def reset_db() -> Generator[None, None, None]:
    """Reset the CoordinationDB singleton between tests for isolation."""
    _reset_db_instance_for_testing()
    yield
    _reset_db_instance_for_testing()


@pytest.fixture
def job_store(tmp_path: Path) -> JobStore:
    """Create a JobStore with a tmp_path root."""
    return JobStore(root=tmp_path)


async def _create_completed_job(job_store: JobStore, vm_name: str) -> str:
    """Create a completed job and return its job_id."""
    job_id = await job_store.start(
        vm_name=vm_name, scenario_name="test-proof-scenario", agent_id="test-agent", step="initial step"
    )
    await job_store.set_status(job_id, vm_name, "completed")
    return job_id


def _create_valid_proof_file(proof_file_path: Path) -> None:
    """Create a valid proof file with all required fields."""
    content = """Proof of Execution
==================
Calculated Value: 42

Result: \U0001f44d Success!

hostname: test-vm-hostname
username: test-user
timestamp: 2026-03-15T10:00:00Z
"""
    proof_file_path.parent.mkdir(parents=True, exist_ok=True)
    proof_file_path.write_text(content, encoding="utf-8")


def _create_screenshot(screenshots_dir: Path, vm_name: str, name: str = "001") -> Path:
    """Create a dummy screenshot file."""
    screenshots_dir.mkdir(parents=True, exist_ok=True)
    screenshot_path = screenshots_dir / f"{vm_name}-{name}.png"
    screenshot_path.write_bytes(b"fake png content")
    return screenshot_path


def _create_recording(recordings_dir: Path, vm_name: str, name: str = "001") -> Path:
    """Create a dummy recording file."""
    recordings_dir.mkdir(parents=True, exist_ok=True)
    recording_path = recordings_dir / f"{vm_name}-{name}.webm"
    recording_path.write_bytes(b"fake webm content")
    return recording_path


# ---------------------------------------------------------------------------
# Test: Happy path - all artifacts present
# ---------------------------------------------------------------------------


async def test_validate_proof_evidence_happy_path(tmp_path: Path) -> None:
    """All artifacts present → valid=True, all checks passed."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    recordings_dir = tmp_path / "recordings"
    _create_recording(recordings_dir, vm_name)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
        recordings_dir=recordings_dir,
    )

    assert result.valid is True
    assert result.job_id == job_id
    assert result.vm_name == vm_name
    assert len(result.checks) == 4

    # All checks should pass
    for check in result.checks:
        assert check.passed is True, f"Check {check.name} failed: {check.detail}"
        assert check.missing_reason is None

    # Verify each check by name
    check_names = {c.name for c in result.checks}
    assert check_names == {"job_record", "proof_file", "visual_evidence", "recording"}


# ---------------------------------------------------------------------------
# Test: Missing job record
# ---------------------------------------------------------------------------


async def test_validate_proof_evidence_missing_job_record(tmp_path: Path) -> None:
    """Missing job record → valid=False, job_record check failed."""
    vm_name = "test-vm"
    job_id = "nonexistent-job"

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
    )

    assert result.valid is False
    assert result.job_id == job_id
    assert result.vm_name == vm_name

    # job_record check should fail
    job_check = next(c for c in result.checks if c.name == "job_record")
    assert job_check.passed is False
    assert job_check.missing_reason == MissingReason.PATH_ABSENT
    assert "not found" in job_check.detail.lower()


# ---------------------------------------------------------------------------
# Test: Incomplete proof file (missing emoji)
# ---------------------------------------------------------------------------


async def test_validate_proof_evidence_incomplete_proof_file(tmp_path: Path) -> None:
    """Incomplete proof file (missing emoji) → valid=False, proof_file check failed."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    # Create proof file without emoji
    proof_file_path = tmp_path / "proof.txt"
    incomplete_content = """Proof of Execution
==================
Calculated Value: 42

Result: Success! (no emoji)

hostname: test-vm-hostname
username: test-user
timestamp: 2026-03-15T10:00:00Z
"""
    proof_file_path.parent.mkdir(parents=True, exist_ok=True)
    proof_file_path.write_text(incomplete_content, encoding="utf-8")

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
    )

    assert result.valid is False

    # proof_file check should fail
    proof_check = next(c for c in result.checks if c.name == "proof_file")
    assert proof_check.passed is False
    assert "thumbs-up emoji" in proof_check.detail.lower() or "emoji" in proof_check.detail.lower()


# ---------------------------------------------------------------------------
# Test: No screenshots
# ---------------------------------------------------------------------------


async def test_validate_proof_evidence_no_visual_evidence(tmp_path: Path) -> None:
    """No evidence files in any directory → valid=False, visual_evidence check failed."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    # Create empty screenshots directory
    screenshots_dir = tmp_path / "screenshots"
    screenshots_dir.mkdir(parents=True, exist_ok=True)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
    )

    assert result.valid is False

    # visual_evidence check should fail — no files in screenshots/, terminal/, or tui_captures/
    screenshots_check = next(c for c in result.checks if c.name == "visual_evidence")
    assert screenshots_check.passed is False
    assert screenshots_check.missing_reason == MissingReason.NOT_CAPTURED
    assert "no visual evidence" in screenshots_check.detail.lower()


# ---------------------------------------------------------------------------
# Test: No recording (acceptable but explicit)
# ---------------------------------------------------------------------------


async def test_validate_proof_evidence_no_recording(tmp_path: Path) -> None:
    """No recording → valid=True but recording check has missing_reason=NOT_CAPTURED."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    # No recordings_dir provided
    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
        recordings_dir=None,
    )

    assert result.valid is True  # Recording is optional

    # recording check should pass but have missing_reason
    recording_check = next(c for c in result.checks if c.name == "recording")
    assert recording_check.passed is True  # Recording is optional, so passes
    assert recording_check.missing_reason == MissingReason.NOT_CAPTURED


async def test_validate_proof_evidence_empty_recordings_dir(tmp_path: Path) -> None:
    """Empty recordings directory → valid=True with explicit NOT_CAPTURED."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    # Empty recordings directory
    recordings_dir = tmp_path / "recordings"
    recordings_dir.mkdir(parents=True, exist_ok=True)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
        recordings_dir=recordings_dir,
    )

    assert result.valid is True

    recording_check = next(c for c in result.checks if c.name == "recording")
    assert recording_check.passed is True
    assert recording_check.missing_reason == MissingReason.NOT_CAPTURED


# ---------------------------------------------------------------------------
# Test: Job record not completed
# ---------------------------------------------------------------------------


async def test_validate_proof_evidence_job_not_completed(tmp_path: Path) -> None:
    """Job record exists but status is not completed → valid=False."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    # Create job but don't mark as completed
    job_id = await job_store.start(
        vm_name=vm_name, scenario_name="test-proof-scenario", agent_id="test-agent", step="initial step"
    )

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
    )

    assert result.valid is False

    job_check = next(c for c in result.checks if c.name == "job_record")
    assert job_check.passed is False
    assert "status" in job_check.detail.lower()
    assert "not 'completed'" in job_check.detail


# ---------------------------------------------------------------------------
# Test: Missing proof file
# ---------------------------------------------------------------------------


async def test_validate_proof_evidence_missing_proof_file(tmp_path: Path) -> None:
    """Missing proof file → valid=False, proof_file check failed."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    # Non-existent proof file path
    proof_file_path = tmp_path / "nonexistent" / "proof.txt"

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
    )

    assert result.valid is False

    proof_check = next(c for c in result.checks if c.name == "proof_file")
    assert proof_check.passed is False
    assert proof_check.missing_reason == MissingReason.PATH_ABSENT


# ---------------------------------------------------------------------------
# Test: Model validation
# ---------------------------------------------------------------------------


def test_evidence_check_model() -> None:
    """EvidenceCheck model works as expected."""
    check = EvidenceCheck(name="test_check", passed=True, detail="All good")
    assert check.name == "test_check"
    assert check.passed is True
    assert check.detail == "All good"
    assert check.missing_reason is None


def test_evidence_check_model_with_missing_reason() -> None:
    """EvidenceCheck model accepts missing_reason."""
    check = EvidenceCheck(
        name="test_check", passed=False, detail="Something missing", missing_reason=MissingReason.NOT_CAPTURED
    )
    assert check.missing_reason == MissingReason.NOT_CAPTURED


def test_proof_evidence_result_model() -> None:
    """ProofEvidenceResult model works as expected."""
    result = ProofEvidenceResult(
        valid=True, job_id="abc123", vm_name="test-vm", checks=[EvidenceCheck(name="check1", passed=True, detail="ok")]
    )
    assert result.valid is True
    assert result.job_id == "abc123"
    assert result.vm_name == "test-vm"
    assert len(result.checks) == 1


# ---------------------------------------------------------------------------
# Test: render_staging validation (video_enabled parameter)
# ---------------------------------------------------------------------------


async def test_render_staging_missing_when_video_enabled(tmp_path: Path) -> None:
    """Missing render_staging with video_enabled=True → valid=False."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    # Note: NOT creating render_staging directory

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
        video_enabled=True,
    )

    assert result.valid is False

    # render_staging check should fail
    render_staging_check = next((c for c in result.checks if c.name == "render_staging"), None)
    assert render_staging_check is not None
    assert render_staging_check.passed is False
    assert render_staging_check.missing_reason == MissingReason.PATH_ABSENT


async def test_render_staging_present_when_video_enabled(tmp_path: Path) -> None:
    """render_staging present with video_enabled=True → valid=True."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    # Create render_staging directory at the expected location
    # screenshots_dir.parent is the job evidence dir (tmp_path in this test)
    render_staging_dir = tmp_path / "render_staging"
    render_staging_dir.mkdir(parents=True, exist_ok=True)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
        video_enabled=True,
    )

    assert result.valid is True

    # render_staging check should pass
    render_staging_check = next((c for c in result.checks if c.name == "render_staging"), None)
    assert render_staging_check is not None
    assert render_staging_check.passed is True
    assert render_staging_check.missing_reason is None


async def test_render_staging_skipped_when_video_disabled(tmp_path: Path) -> None:
    """No render_staging check when video_enabled=False (default)."""
    job_store = JobStore(root=tmp_path)
    vm_name = "test-vm"
    job_id = await _create_completed_job(job_store, vm_name)

    proof_file_path = tmp_path / "proof.txt"
    _create_valid_proof_file(proof_file_path)

    screenshots_dir = tmp_path / "screenshots"
    _create_screenshot(screenshots_dir, vm_name)

    # Note: NOT creating render_staging directory, and video_enabled=False (default)

    result = await validate_proof_evidence(
        job_id=job_id,
        vm_name=vm_name,
        jobs_root=tmp_path,
        screenshots_dir=screenshots_dir,
        proof_file_path=proof_file_path,
        video_enabled=False,  # explicit False
    )

    assert result.valid is True  # Should pass without render_staging

    # No render_staging check should be present
    render_staging_check = next((c for c in result.checks if c.name == "render_staging"), None)
    assert render_staging_check is None
