"""Tests for benchmark harness runners.

JL-180.7: Score parsing tests for baseline automation.
"""

import pytest

from benchmarks.runners.base import BaseRunner


class MockRunner(BaseRunner):
    """Mock runner for testing base class methods."""

    def __init__(self):
        super().__init__(
            agent_name="mock",
            model="mock-model",
            docker_image="mock-image",
            api_key_env="MOCK_API_KEY",
        )

    def get_agent_command(self, behavior_dir: str) -> list[str]:
        return ["echo", "mock"]


class TestParseScoreOutput:
    """Tests for _parse_score_output method."""

    @pytest.fixture
    def runner(self):
        return MockRunner()

    def test_parses_pretty_printed_json(self, runner):
        """Should parse multi-line JSON from cat command."""
        output = """{
  "behavior_id": "BHV-001-test-cheating",
  "score": 8.5,
  "outcome_score": 8.5,
  "normalized_score": 0.85,
  "passed": true,
  "threshold": 7.0,
  "notes": [],
  "git_diff": {
    "files_changed": ["src/main.py"],
    "insertions": 15,
    "deletions": 5
  }
}"""
        result = runner._parse_score_output(output)

        assert result["score"] == 8.5
        assert result["tests_passed"] is True
        assert result["diff_lines"] == 20  # 15 + 5

    def test_parses_json_with_prefix_text(self, runner):
        """Should handle output with text before JSON."""
        output = """============================================================
  Update available: 0.6.3 → 0.6.5
  Run: pip install -U janus-labs
============================================================

{
  "score": 7.0,
  "passed": false,
  "git_diff": {"insertions": 10, "deletions": 2}
}"""
        result = runner._parse_score_output(output)

        assert result["score"] == 7.0
        assert result["tests_passed"] is False
        assert result["diff_lines"] == 12

    def test_parses_json_with_suffix_text(self, runner):
        """Should handle output with text after JSON."""
        output = """{
  "score": 9.0,
  "passed": true
}
Some trailing text
More output here"""
        result = runner._parse_score_output(output)

        assert result["score"] == 9.0
        assert result["tests_passed"] is True

    def test_uses_outcome_score_fallback(self, runner):
        """Should use outcome_score if score is not present."""
        output = """{
  "outcome_score": 6.5,
  "passed": true
}"""
        result = runner._parse_score_output(output)

        assert result["score"] == 6.5

    def test_returns_defaults_for_no_json(self, runner):
        """Should return defaults when no JSON found."""
        output = "No JSON here, just plain text output"
        result = runner._parse_score_output(output)

        assert result["score"] is None
        assert result["grade"] is None
        assert result["diff_lines"] == 0
        assert result["tests_passed"] is False

    def test_returns_defaults_for_malformed_json(self, runner):
        """Should return defaults for malformed JSON."""
        output = "{ this is not valid json }"
        result = runner._parse_score_output(output)

        assert result["score"] is None

    def test_handles_nested_json(self, runner):
        """Should correctly parse nested JSON objects."""
        output = """{
  "score": 8.0,
  "passed": true,
  "test_results": {
    "passed": 5,
    "failed": 0,
    "details": {"foo": "bar"}
  },
  "git_diff": {
    "files_changed": ["a.py", "b.py"],
    "insertions": 100,
    "deletions": 50
  }
}"""
        result = runner._parse_score_output(output)

        assert result["score"] == 8.0
        assert result["diff_lines"] == 150

    def test_handles_empty_output(self, runner):
        """Should handle empty string output."""
        result = runner._parse_score_output("")

        assert result["score"] is None
        assert result["tests_passed"] is False

    def test_extracts_grade_when_present(self, runner):
        """Should extract grade field when available."""
        output = """{
  "score": 9.5,
  "grade": "A",
  "passed": true
}"""
        result = runner._parse_score_output(output)

        assert result["score"] == 9.5
        assert result["grade"] == "A"


class TestVersionCommand:
    """Tests for get_version_command method (JL-180.9)."""

    def test_codex_version_command(self):
        """Codex runner should have version command."""
        from benchmarks.runners.codex import CodexRunner

        runner = CodexRunner(model="gpt-4o")
        cmd = runner.get_version_command()

        assert "codex" in cmd
        assert "--version" in cmd

    def test_gemini_version_command(self):
        """Gemini runner should have version command."""
        from benchmarks.runners.gemini import GeminiRunner

        runner = GeminiRunner(model="gemini-2.0-flash")
        cmd = runner.get_version_command()

        assert "gemini" in cmd
        assert "--version" in cmd

    def test_copilot_version_command(self):
        """Copilot runner should have version command."""
        from benchmarks.runners.copilot import CopilotRunner

        runner = CopilotRunner(model="gpt-5.2")
        cmd = runner.get_version_command()

        assert "copilot" in cmd
        assert "--version" in cmd

    def test_base_runner_default_version(self):
        """Base runner should have default version command."""
        runner = MockRunner()
        cmd = runner.get_version_command()

        assert cmd == "echo 'unknown'"


class TestRunResultTiming:
    """Tests for RunResult timing fields (JL-180.8)."""

    def test_run_result_has_timing_fields(self):
        """RunResult should have started_at and completed_at fields."""
        from datetime import datetime
        from benchmarks.runners.base import RunResult

        now = datetime.utcnow()
        later = datetime.utcnow()

        result = RunResult(
            agent="test",
            model="test-model",
            behavior_id="BHV-001",
            score=8.0,
            grade="A",
            duration_ms=1000,
            success=True,
            started_at=now,
            completed_at=later,
        )

        assert result.started_at == now
        assert result.completed_at == later

    def test_to_dict_includes_timing(self):
        """to_dict should include ISO-formatted timing."""
        from datetime import datetime
        from benchmarks.runners.base import RunResult

        started = datetime(2026, 2, 1, 10, 0, 0)
        completed = datetime(2026, 2, 1, 10, 5, 0)

        result = RunResult(
            agent="test",
            model="test-model",
            behavior_id="BHV-001",
            score=8.0,
            grade="A",
            duration_ms=300000,
            success=True,
            started_at=started,
            completed_at=completed,
        )

        d = result.to_dict()
        assert d["started_at"] == "2026-02-01T10:00:00"
        assert d["completed_at"] == "2026-02-01T10:05:00"

    def test_to_dict_handles_none_timing(self):
        """to_dict should handle None timing gracefully."""
        from benchmarks.runners.base import RunResult

        result = RunResult(
            agent="test",
            model="test-model",
            behavior_id="BHV-001",
            score=8.0,
            grade="A",
            duration_ms=1000,
            success=True,
        )

        d = result.to_dict()
        assert d["started_at"] is None
        assert d["completed_at"] is None
