"""Tests for Harbor task directory validator."""

import textwrap
from pathlib import Path

import pytest

from clawbench.task_contract import (
    ValidationError,
    validate_evaluate_callable,
    validate_task_dir,
    validate_tasks,
)


@pytest.fixture
def valid_task(tmp_path):
    """Create a minimal valid Harbor task directory."""
    task_dir = tmp_path / "my-task"
    task_dir.mkdir()
    (task_dir / "task.toml").write_text('name = "my-task"\n')
    (task_dir / "instruction.md").write_text("Do the thing.\n")
    tests_dir = task_dir / "tests"
    tests_dir.mkdir()
    (tests_dir / "evaluate.py").write_text(
        textwrap.dedent("""\
        def evaluate(submission):
            return {"rewards": {"score": 1.0}}
        """)
    )
    return task_dir


class TestValidateTaskDir:
    def test_valid(self, valid_task):
        assert validate_task_dir(valid_task) == []

    def test_missing_task_toml(self, valid_task):
        (valid_task / "task.toml").unlink()
        errors = validate_task_dir(valid_task)
        assert any("missing task.toml" in e for e in errors)

    def test_missing_instruction_md(self, valid_task):
        (valid_task / "instruction.md").unlink()
        errors = validate_task_dir(valid_task)
        assert any("missing instruction.md" in e for e in errors)

    def test_missing_evaluate_py(self, valid_task):
        (valid_task / "tests" / "evaluate.py").unlink()
        errors = validate_task_dir(valid_task)
        assert any("missing tests/evaluate.py" in e for e in errors)

    def test_not_a_directory(self, tmp_path):
        f = tmp_path / "not-a-dir"
        f.write_text("nope")
        errors = validate_task_dir(f)
        assert any("not a directory" in e for e in errors)


class TestValidateEvaluateCallable:
    def test_valid(self, valid_task):
        assert validate_evaluate_callable(valid_task) == []

    def test_no_evaluate_function(self, valid_task):
        (valid_task / "tests" / "evaluate.py").write_text("x = 1\n")
        errors = validate_evaluate_callable(valid_task)
        assert any("no 'evaluate' callable" in e for e in errors)

    def test_evaluate_not_callable(self, valid_task):
        (valid_task / "tests" / "evaluate.py").write_text("evaluate = 42\n")
        errors = validate_evaluate_callable(valid_task)
        assert any("not callable" in e for e in errors)

    def test_evaluate_no_args(self, valid_task):
        (valid_task / "tests" / "evaluate.py").write_text(
            "def evaluate():\n    pass\n"
        )
        errors = validate_evaluate_callable(valid_task)
        assert any("takes no arguments" in e for e in errors)

    def test_import_error(self, valid_task):
        (valid_task / "tests" / "evaluate.py").write_text(
            "import nonexistent_module_xyz\n"
        )
        errors = validate_evaluate_callable(valid_task)
        assert any("import failed" in e for e in errors)


class TestValidateTasks:
    def test_valid_tasks(self, valid_task):
        tasks = [{"task_name": "my-task", "task_path": str(valid_task)}]
        validate_tasks(tasks, check_callable=True)  # should not raise

    def test_empty_tasks(self):
        with pytest.raises(ValidationError, match="No tasks provided"):
            validate_tasks([])

    def test_invalid_task_raises(self, tmp_path):
        bad_dir = tmp_path / "bad-task"
        bad_dir.mkdir()
        # Missing all required files
        tasks = [{"task_name": "bad-task", "task_path": str(bad_dir)}]
        with pytest.raises(ValidationError) as exc_info:
            validate_tasks(tasks)
        assert len(exc_info.value.errors) >= 1

    def test_duplicate_names_warns(self, valid_task, caplog):
        tasks = [
            {"task_name": "dup", "task_path": str(valid_task)},
            {"task_name": "dup", "task_path": str(valid_task)},
        ]
        import logging
        with caplog.at_level(logging.WARNING):
            validate_tasks(tasks, check_callable=False)
        assert any("Duplicate task name" in r.message for r in caplog.records)


class TestValidationBlocksOptimization:
    """Verify that run_optimization calls validate_tasks and stops on failure."""

    def test_invalid_task_blocks_custom_engine(self, tmp_path):
        from clawbench.optimize import run_optimization

        bad_dir = tmp_path / "bad-task"
        bad_dir.mkdir()
        # Missing all required files — validator should reject
        tasks = [{"task_name": "bad-task", "task_path": str(bad_dir)}]

        skill = tmp_path / "SKILL.md"
        skill.write_text("# Skill\n")

        with pytest.raises(ValidationError):
            run_optimization(
                skill_path=skill,
                train_tasks=tasks,
                engine="custom",
            )

    def test_invalid_task_blocks_gepa_engine(self, tmp_path):
        from clawbench.optimize import run_optimization

        bad_dir = tmp_path / "bad-task"
        bad_dir.mkdir()
        tasks = [{"task_name": "bad-task", "task_path": str(bad_dir)}]

        skill = tmp_path / "SKILL.md"
        skill.write_text("# Skill\n")

        with pytest.raises(ValidationError):
            run_optimization(
                skill_path=skill,
                train_tasks=tasks,
                engine="gepa",
            )
