"""Tests for benchmark task loading."""

import pytest
from pathlib import Path
from unittest.mock import patch

from backend.calibration.tasks import (
    BenchmarkTask,
    load_tasks_from_directory,
    get_builtin_tasks,
    get_all_tasks,
    get_tasks_by_category,
    get_tasks_by_language,
    get_tasks_by_difficulty,
    get_task_stats,
    get_categories,
    get_task_by_id,
)


class TestLoadTasksFromDirectory:
    def test_load_from_valid_directory(self, tmp_path):
        task_file = tmp_path / "test.yaml"
        task_file.write_text("""
- id: test_001
  category: code
  difficulty: simple
  language: en
  prompt: Write a function
  expected_behavior: Should work
""")
        tasks = load_tasks_from_directory(tmp_path)
        assert len(tasks) == 1
        assert tasks[0].id == "test_001"
        assert tasks[0].category == "code"
        assert tasks[0].difficulty == "simple"
        assert tasks[0].language == "en"

    def test_load_from_missing_directory(self):
        tasks = load_tasks_from_directory("/nonexistent/path")
        assert tasks == []

    def test_skip_invalid_tasks(self, tmp_path):
        task_file = tmp_path / "test.yaml"
        task_file.write_text("""
- id: valid_001
  category: code
  difficulty: simple
  language: en
  prompt: Valid task
- id: missing_fields
  category: code
  # missing difficulty, language, prompt
- category: no_id
  difficulty: simple
  language: en
  prompt: No ID
""")
        tasks = load_tasks_from_directory(tmp_path)
        assert len(tasks) == 1
        assert tasks[0].id == "valid_001"

    def test_skip_invalid_difficulty(self, tmp_path):
        task_file = tmp_path / "test.yaml"
        task_file.write_text("""
- id: bad_diff
  category: code
  difficulty: invalid
  language: en
  prompt: Test
""")
        tasks = load_tasks_from_directory(tmp_path)
        assert len(tasks) == 0

    def test_skip_invalid_language(self, tmp_path):
        task_file = tmp_path / "test.yaml"
        task_file.write_text("""
- id: bad_lang
  category: code
  difficulty: simple
  language: fr
  prompt: Test
""")
        tasks = load_tasks_from_directory(tmp_path)
        assert len(tasks) == 0

    def test_duplicate_ids_skipped(self, tmp_path):
        file1 = tmp_path / "a.yaml"
        file1.write_text("""
- id: dupe_001
  category: code
  difficulty: simple
  language: en
  prompt: First
""")
        file2 = tmp_path / "b.yaml"
        file2.write_text("""
- id: dupe_001
  category: code
  difficulty: simple
  language: en
  prompt: Second (duplicate)
""")
        tasks = load_tasks_from_directory(tmp_path)
        assert len(tasks) == 1
        assert tasks[0].prompt == "First"

    def test_loads_yml_extension(self, tmp_path):
        task_file = tmp_path / "test.yml"
        task_file.write_text("""
- id: yml_test
  category: math
  difficulty: moderate
  language: it
  prompt: Calcola
""")
        tasks = load_tasks_from_directory(tmp_path)
        assert len(tasks) == 1
        assert tasks[0].id == "yml_test"

    def test_handles_malformed_yaml(self, tmp_path):
        task_file = tmp_path / "bad.yaml"
        task_file.write_text("not: valid: yaml: [")
        tasks = load_tasks_from_directory(tmp_path)
        assert tasks == []

    def test_handles_non_list_yaml(self, tmp_path):
        task_file = tmp_path / "dict.yaml"
        task_file.write_text("key: value")
        tasks = load_tasks_from_directory(tmp_path)
        assert tasks == []


class TestBuiltinTasks:
    def test_builtin_tasks_exist(self):
        tasks = get_builtin_tasks()
        assert len(tasks) > 0

    def test_builtin_tasks_cached(self):
        tasks1 = get_builtin_tasks()
        tasks2 = get_builtin_tasks()
        assert tasks1 is tasks2  # Same object (cached)

    def test_builtin_tasks_immutable(self):
        tasks = get_builtin_tasks()
        assert isinstance(tasks, tuple)

    def test_all_builtin_tasks_valid(self):
        for task in get_builtin_tasks():
            assert task.id
            assert task.category
            assert task.difficulty in ("simple", "moderate", "complex", "expert")
            assert task.language in ("en", "it")
            assert task.prompt


class TestGetAllTasks:
    def test_returns_builtin_without_custom(self):
        tasks = get_all_tasks(custom_dir=None)
        assert len(tasks) == len(get_builtin_tasks())

    def test_includes_custom_tasks(self, tmp_path):
        custom_file = tmp_path / "custom.yaml"
        custom_file.write_text("""
- id: custom_001
  category: custom
  difficulty: simple
  language: en
  prompt: Custom task
""")
        tasks = get_all_tasks(custom_dir=str(tmp_path))
        builtin_count = len(get_builtin_tasks())
        assert len(tasks) == builtin_count + 1

    def test_custom_conflicts_skipped(self, tmp_path):
        # Get a real builtin task ID
        builtin = get_builtin_tasks()[0]
        
        custom_file = tmp_path / "conflict.yaml"
        custom_file.write_text(f"""
- id: {builtin.id}
  category: custom
  difficulty: simple
  language: en
  prompt: Conflicting task
""")
        tasks = get_all_tasks(custom_dir=str(tmp_path))
        # Should not add the conflicting task
        assert len(tasks) == len(get_builtin_tasks())


class TestTaskFilters:
    def test_get_tasks_by_category(self):
        tasks = get_tasks_by_category("code")
        assert all(t.category == "code" for t in tasks)
        assert len(tasks) > 0

    def test_get_tasks_by_language_en(self):
        tasks = get_tasks_by_language("en")
        assert all(t.language == "en" for t in tasks)
        assert len(tasks) > 0

    def test_get_tasks_by_language_it(self):
        tasks = get_tasks_by_language("it")
        assert all(t.language == "it" for t in tasks)
        assert len(tasks) > 0

    def test_get_tasks_by_difficulty(self):
        tasks = get_tasks_by_difficulty("simple")
        assert all(t.difficulty == "simple" for t in tasks)


class TestTaskStats:
    def test_get_task_stats(self):
        stats = get_task_stats()
        assert "total" in stats
        assert "by_category" in stats
        assert "by_language" in stats
        assert "by_difficulty" in stats
        assert stats["total"] > 0

    def test_stats_match_task_count(self):
        stats = get_task_stats()
        tasks = get_builtin_tasks()
        assert stats["total"] == len(tasks)
        
        # Sum of categories should equal total
        category_sum = sum(stats["by_category"].values())
        assert category_sum == stats["total"]


class TestGetCategories:
    def test_get_categories(self):
        categories = get_categories()
        assert isinstance(categories, list)
        assert len(categories) > 0
        assert "code" in categories


class TestGetTaskById:
    def test_find_existing_task(self):
        tasks = get_builtin_tasks()
        task = get_task_by_id(tasks[0].id)
        assert task is not None
        assert task.id == tasks[0].id

    def test_find_nonexistent_task(self):
        task = get_task_by_id("nonexistent_task_id")
        assert task is None


class TestBenchmarkTask:
    def test_task_dataclass(self):
        task = BenchmarkTask(
            id="test",
            category="code",
            difficulty="simple",
            prompt="Test prompt",
            language="en",
            expected_behavior="Expected",
            deterministic_checks=[{"type": "python_exec", "test_code": "assert True"}],
        )
        assert task.id == "test"
        assert task.deterministic_checks is not None

    def test_task_default_values(self):
        task = BenchmarkTask(
            id="test",
            category="code",
            difficulty="simple",
            prompt="Test",
            language="en",
        )
        assert task.expected_behavior == ""
        assert task.deterministic_checks is None
