"""Tests for the MockAIService."""

import pandas as pd
import pytest

from crystallise.screening.mock import MockAIService


class TestCalculateMockScore:
    def test_returns_float_in_range(self):
        score = MockAIService.calculate_mock_score("A study on drug efficacy in adults")
        assert isinstance(score, float)
        assert 1.0 <= score <= 5.0

    def test_deterministic_same_input(self):
        abstract = "Randomized controlled trial of drug X in adult patients"
        score1 = MockAIService.calculate_mock_score(abstract, "Study Title")
        score2 = MockAIService.calculate_mock_score(abstract, "Study Title")
        assert score1 == score2

    def test_empty_abstract_returns_low_score(self):
        score = MockAIService.calculate_mock_score("")
        assert score == 1.5

    def test_none_abstract_returns_low_score(self):
        score = MockAIService.calculate_mock_score(None)
        assert score == 1.5

    def test_different_inputs_different_scores(self):
        score1 = MockAIService.calculate_mock_score("Randomized clinical trial of cancer treatment")
        score2 = MockAIService.calculate_mock_score("Editorial commentary on healthcare policy")
        # Not guaranteed to differ, but highly likely with these inputs
        # Just verify both are valid
        assert 1.0 <= score1 <= 5.0
        assert 1.0 <= score2 <= 5.0


class TestGenerateMockReasoning:
    def test_returns_nonempty_string(self):
        reasoning = MockAIService.generate_mock_reasoning(
            "Test Title", "Test abstract about a clinical trial", 3.5
        )
        assert isinstance(reasoning, str)
        assert len(reasoning) > 0

    def test_high_score_reasoning(self):
        reasoning = MockAIService.generate_mock_reasoning("Title", "Abstract", 4.5)
        assert "inclusion" in reasoning.lower() or "candidate" in reasoning.lower()

    def test_low_score_reasoning(self):
        reasoning = MockAIService.generate_mock_reasoning("Title", "Abstract", 1.5)
        assert "exclusion" in reasoning.lower() or "weak" in reasoning.lower()


class TestScoreToDecision:
    def test_above_threshold_include(self):
        assert MockAIService.score_to_decision(3.0, threshold=2.5) == "include"

    def test_at_threshold_exclude(self):
        assert MockAIService.score_to_decision(2.5, threshold=2.5) == "exclude"

    def test_below_threshold_exclude(self):
        assert MockAIService.score_to_decision(1.0, threshold=2.5) == "exclude"

    def test_default_threshold(self):
        assert MockAIService.score_to_decision(1.5) == "include"
        assert MockAIService.score_to_decision(0.5) == "exclude"


class TestGenerateMockClusters:
    def test_returns_list_of_dicts_with_expected_keys(self):
        mock = MockAIService()
        clusters = mock.generate_mock_clusters(100)
        assert isinstance(clusters, list)
        assert len(clusters) > 0
        for cluster in clusters:
            assert "cluster_name" in cluster
            assert "cluster_type" in cluster
            assert "cluster_description" in cluster
            assert "related_criteria" in cluster

    def test_filters_by_include_type(self):
        mock = MockAIService()
        clusters = mock.generate_mock_clusters(100, clusters_type="include")
        assert all(c["cluster_type"] == "include" for c in clusters)

    def test_filters_by_exclude_type(self):
        mock = MockAIService()
        clusters = mock.generate_mock_clusters(100, clusters_type="exclude")
        assert all(c["cluster_type"] == "exclude" for c in clusters)

    def test_no_filter_returns_both_types(self):
        mock = MockAIService()
        clusters = mock.generate_mock_clusters(100, clusters_type=None)
        types = {c["cluster_type"] for c in clusters}
        assert "include" in types
        assert "exclude" in types


class TestAssignCluster:
    def test_returns_list_of_dicts(self):
        mock = MockAIService()
        clusters = mock.generate_mock_clusters(100)
        result = mock.assign_cluster(3.0, "A study about patient safety outcomes", clusters)
        assert isinstance(result, list)
        assert len(result) > 0
        for item in result:
            assert "cluster_name" in item
            assert "cluster_type" in item

    def test_high_score_assignment(self):
        mock = MockAIService()
        clusters = mock.generate_mock_clusters(100)
        result = mock.assign_cluster(4.5, "Treatment efficacy study", clusters)
        assert len(result) > 0

    def test_low_score_assignment(self):
        mock = MockAIService()
        clusters = mock.generate_mock_clusters(100)
        result = mock.assign_cluster(1.0, "A review article about guidelines", clusters)
        assert len(result) > 0


class TestScreenPapersPipeline:
    @pytest.mark.asyncio
    async def test_end_to_end_with_sample_dataframe(self):
        mock = MockAIService()
        papers_df = pd.DataFrame([
            {"title": "RCT of Drug X", "abstract": "A randomized controlled trial examining efficacy"},
            {"title": "Review of Methods", "abstract": "A systematic review of research methodology"},
            {"title": "Case Report", "abstract": "A case report of rare adverse events"},
        ])

        results_df, clusters = await mock.screen_papers(
            papers_df=papers_df,
            criteria=[{"name": "Population", "type": "include", "value": "Adults"}],
            questions=["Is Drug X effective?"],
            threshold=2.5,
        )

        assert len(results_df) == 3
        assert "ai_score" in results_df.columns
        assert "ai_decision" in results_df.columns
        assert "ai_reasoning" in results_df.columns
        assert "assigned_clusters" in results_df.columns
        assert isinstance(clusters, list)

        # Verify all scores are valid
        for score in results_df["ai_score"]:
            assert 1.0 <= score <= 5.0

        # Verify decisions are valid
        for decision in results_df["ai_decision"]:
            assert decision in ("include", "exclude")

    @pytest.mark.asyncio
    async def test_empty_dataframe(self):
        mock = MockAIService()
        papers_df = pd.DataFrame(columns=["title", "abstract"])
        results_df, clusters = await mock.screen_papers(papers_df=papers_df)
        assert len(results_df) == 0
        assert clusters == []

    @pytest.mark.asyncio
    async def test_cancel_stops_processing(self):
        mock = MockAIService()
        mock.cancel()
        papers_df = pd.DataFrame([
            {"title": "Title", "abstract": "Abstract"},
        ])
        results_df, clusters = await mock.screen_papers(papers_df=papers_df)
        # After cancel, some columns may not be populated
        assert len(results_df) == 1
