"""Tests for gherbal.evaluation module."""

import numpy as np
import pandas as pd
import pytest
from gherbal.evaluation import (
    evaluate,
    report,
    get_confused_pairs,
    _format_report,
)


class TestEvaluate:
    """Tests for evaluate function."""

    def test_basic_evaluate(self, trained_model, eval_df):
        model, _ = trained_model
        acc, report_str, cm, labels, df_filtered = evaluate(model, eval_df)

        assert isinstance(acc, float)
        assert 0.0 <= acc <= 1.0
        assert isinstance(report_str, str)
        assert isinstance(cm, np.ndarray)
        assert isinstance(labels, list)
        assert isinstance(df_filtered, pd.DataFrame)

    def test_evaluate_returns_sorted_labels(self, trained_model, eval_df):
        model, _ = trained_model
        _, _, _, labels, _ = evaluate(model, eval_df)
        assert labels == sorted(labels)

    def test_evaluate_confusion_matrix_shape(self, trained_model, eval_df):
        model, _ = trained_model
        _, _, cm, labels, _ = evaluate(model, eval_df)
        assert cm.shape == (len(labels), len(labels))

    def test_evaluate_report_contains_labels(self, trained_model, eval_df):
        model, _ = trained_model
        _, report_str, _, labels, _ = evaluate(model, eval_df)
        for label in labels:
            assert label in report_str

    def test_evaluate_with_dict_input(self, trained_model):
        model, _ = trained_model
        data = [
            {"lang": "eng_Latn", "text": "hello world test"},
            {"lang": "arb_Arab", "text": "مرحبا بالعالم"},
        ]
        # Should work with list of dicts too
        acc, _, _, _, _ = evaluate(model, data)
        assert isinstance(acc, float)

    def test_evaluate_no_matching_classes_raises(self, trained_model):
        model, _ = trained_model
        # Use a label that doesn't exist in the model
        bad_df = pd.DataFrame({
            "lang": ["xyz_Unkw", "abc_Unkw"],
            "text": ["hello", "world"],
        })
        with pytest.raises(ValueError, match="No matching classes"):
            evaluate(model, bad_df)


class TestReport:
    """Tests for report function."""

    def test_report_silent(self, trained_model, eval_df):
        model, _ = trained_model
        acc, df = report(model, eval_df, silent=True)
        assert isinstance(acc, float)
        assert isinstance(df, pd.DataFrame)

    def test_report_verbose(self, trained_model, eval_df, capsys):
        model, _ = trained_model
        acc, df = report(model, eval_df, silent=False)
        captured = capsys.readouterr()
        assert "Accuracy" in captured.out
        assert "Classification Report" in captured.out


class TestGetConfusedPairs:
    """Tests for get_confused_pairs function."""

    def test_basic(self):
        cm = np.array([
            [10, 2, 1],
            [3, 8, 0],
            [0, 1, 9],
        ])
        labels = ["a", "b", "c"]
        pairs = get_confused_pairs(cm, labels, top_k=3)
        assert len(pairs) == 3
        # Most confused should be a-b (2+3=5)
        assert pairs[0][0] == "a"
        assert pairs[0][1] == "b"
        assert pairs[0][2] == 5

    def test_top_k_limits(self):
        cm = np.array([[5, 1, 2], [3, 4, 1], [0, 2, 6]])
        labels = ["x", "y", "z"]
        pairs = get_confused_pairs(cm, labels, top_k=1)
        assert len(pairs) == 1

    def test_empty_confusion(self):
        cm = np.array([[10, 0], [0, 10]])
        labels = ["a", "b"]
        pairs = get_confused_pairs(cm, labels)
        assert len(pairs) == 1
        assert pairs[0][2] == 0

    def test_single_class(self):
        cm = np.array([[10]])
        labels = ["a"]
        pairs = get_confused_pairs(cm, labels)
        assert len(pairs) == 0

    def test_description_string(self):
        cm = np.array([[10, 3], [2, 8]])
        labels = ["eng", "fra"]
        pairs = get_confused_pairs(cm, labels)
        assert "eng->fra: 3" in pairs[0][3]
        assert "fra->eng: 2" in pairs[0][3]


class TestFormatReport:
    """Tests for _format_report helper."""

    def test_basic_format(self):
        report_dict = {
            "eng": {"precision": 0.9, "recall": 0.85, "f1-score": 0.87, "support": 20},
            "fra": {"precision": 0.8, "recall": 0.9, "f1-score": 0.85, "support": 15},
            "macro avg": {"precision": 0.85, "recall": 0.875, "f1-score": 0.86, "support": 35},
            "weighted avg": {"precision": 0.86, "recall": 0.87, "f1-score": 0.865, "support": 35},
        }
        labels = ["eng", "fra"]
        result = _format_report(report_dict, labels, accuracy=0.86)
        assert "eng" in result
        assert "fra" in result
        assert "accuracy" in result
        assert "macro avg" in result
        assert "weighted avg" in result
