"""Tests for rubric model, parser, scaffold, and validation."""

from __future__ import annotations

import json
import os

import pytest

from bandito.judge.rubric import (
    Rubric,
    RubricExample,
    RubricMeta,
    parse_rubric,
)


SAMPLE_RUBRIC = """\
---
description: Customer support chatbot
---

## What makes a good response

- Answers the user's question directly
- Professional tone

## What makes a bad response

- Factually incorrect
- Ignores the question

## Examples

### Good

**Input:** How do I reset my password?
**Output:** Go to Settings > Security > Reset Password.
**Critique:** Correct steps, direct answer.

### Good

**Input:** What are your hours?
**Output:** We're open Monday through Friday, 9am to 5pm.
**Critique:** Clear and specific.

### Bad

**Input:** What's your refund policy?
**Output:** I think refunds are usually within 30 days lol
**Critique:** Hedges, unprofessional tone.

### Bad

**Input:** My order is late
**Output:** idk maybe check the tracking?
**Critique:** Dismissive, no actual help.
"""


class TestParseFrontmatter:
    def test_parses_description(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, SAMPLE_RUBRIC)
        rubric = parse_rubric(path)
        assert rubric.meta.description == "Customer support chatbot"

    def test_missing_frontmatter(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, "## What makes a good response\n\n- Be helpful\n")
        rubric = parse_rubric(path)
        assert rubric.meta.description == ""


class TestParseCriteria:
    def test_extracts_pass_criteria(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, SAMPLE_RUBRIC)
        rubric = parse_rubric(path)
        assert "Answers the user's question directly" in rubric.pass_criteria
        assert "Professional tone" in rubric.pass_criteria

    def test_extracts_fail_criteria(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, SAMPLE_RUBRIC)
        rubric = parse_rubric(path)
        assert "Factually incorrect" in rubric.fail_criteria
        assert "Ignores the question" in rubric.fail_criteria

    def test_flexible_headings(self, tmp_path):
        content = "---\ndescription: test\n---\n\n## Good\n\n- Be helpful\n\n## Bad\n\n- Don't lie\n"
        path = str(tmp_path / "rubric.md")
        _write(path, content)
        rubric = parse_rubric(path)
        assert "Be helpful" in rubric.pass_criteria
        assert "Don't lie" in rubric.fail_criteria

    def test_missing_criteria(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, "---\ndescription: test\n---\n\nNo headings here.\n")
        rubric = parse_rubric(path)
        assert rubric.pass_criteria == ""
        assert rubric.fail_criteria == ""


class TestParseExamples:
    def test_parses_good_and_bad(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, SAMPLE_RUBRIC)
        rubric = parse_rubric(path)
        assert len(rubric.good_examples) == 2
        assert len(rubric.bad_examples) == 2

    def test_example_fields(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, SAMPLE_RUBRIC)
        rubric = parse_rubric(path)
        ex = rubric.good_examples[0]
        assert ex.input == "How do I reset my password?"
        assert "Settings > Security" in ex.output
        assert "Correct steps" in ex.critique

    def test_bad_example_fields(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, SAMPLE_RUBRIC)
        rubric = parse_rubric(path)
        ex = rubric.bad_examples[0]
        assert "refund" in ex.input.lower()
        assert "lol" in ex.output
        assert "Hedges" in ex.critique

    def test_no_examples_section(self, tmp_path):
        content = "---\ndescription: test\n---\n\n## Good\n\n- Be nice\n\n## Bad\n\n- Don't lie\n"
        path = str(tmp_path / "rubric.md")
        _write(path, content)
        rubric = parse_rubric(path)
        assert rubric.good_examples == []
        assert rubric.bad_examples == []


class TestValidation:
    def test_valid_rubric(self, tmp_path):
        path = str(tmp_path / "rubric.md")
        _write(path, SAMPLE_RUBRIC)
        rubric = parse_rubric(path)
        assert rubric.validate() == []

    def test_missing_everything(self):
        rubric = Rubric()
        warnings = rubric.validate()
        assert len(warnings) == 4  # pass, fail, good examples, bad examples

    def test_one_example_warns(self):
        rubric = Rubric(
            pass_criteria="Be good",
            fail_criteria="Don't be bad",
            good_examples=[RubricExample("a", "b", "c")],
            bad_examples=[RubricExample("d", "e", "f")],
        )
        warnings = rubric.validate()
        assert len(warnings) == 2  # only 1 good, only 1 bad
        assert any("1 good" in w for w in warnings)

    def test_two_examples_pass(self):
        rubric = Rubric(
            pass_criteria="Be good",
            fail_criteria="Don't be bad",
            good_examples=[RubricExample("a", "b", "c"), RubricExample("d", "e", "f")],
            bad_examples=[RubricExample("g", "h", "i"), RubricExample("j", "k", "l")],
        )
        assert rubric.validate() == []




def _write(path: str, content: str) -> None:
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w") as f:
        f.write(content)
