"""
Tests for base class concrete methods in the advanced_search_system.

Each test class creates a minimal concrete subclass that inherits from the ABC,
implements abstract methods as no-ops, then exercises the concrete helper methods.
"""

from unittest.mock import Mock

from local_deep_research.advanced_search_system.candidates.base_candidate import (
    Candidate,
)
from local_deep_research.advanced_search_system.constraint_checking.base_constraint_checker import (
    BaseConstraintChecker,
)
from local_deep_research.advanced_search_system.constraints.base_constraint import (
    Constraint,
    ConstraintType,
)
from local_deep_research.advanced_search_system.findings.base_findings import (
    BaseFindingsRepository,
)
from local_deep_research.advanced_search_system.knowledge.base_knowledge import (
    BaseKnowledgeGenerator,
)
from local_deep_research.advanced_search_system.questions.base_question import (
    BaseQuestionGenerator,
)
from local_deep_research.advanced_search_system.questions.followup.base_followup_question import (
    BaseFollowUpQuestionGenerator,
)


# ---------------------------------------------------------------------------
# Minimal concrete subclasses
# ---------------------------------------------------------------------------


class ConcreteKnowledgeGenerator(BaseKnowledgeGenerator):
    def generate(self, query, context):
        return ""

    def generate_knowledge(
        self, query, context="", current_knowledge="", questions=None
    ):
        return ""

    def generate_sub_knowledge(self, sub_query, context=""):
        return ""

    def compress_knowledge(
        self, current_knowledge, query, section_links, **kwargs
    ):
        return ""

    def format_citations(self, links):
        return ""


class ConcreteConstraintChecker(BaseConstraintChecker):
    def check_candidate(self, candidate, constraints):
        return None

    def should_reject_candidate(self, candidate, constraint, evidence_data):
        return (False, "")


class ConcreteQuestionGenerator(BaseQuestionGenerator):
    def generate_questions(
        self,
        current_knowledge,
        query,
        questions_per_iteration,
        questions_by_iteration,
    ):
        return []


class ConcreteFollowUpQuestionGenerator(BaseFollowUpQuestionGenerator):
    def generate_contextualized_query(
        self, follow_up_query, original_query, past_findings, **kwargs
    ):
        return ""


class ConcreteFindingsRepository(BaseFindingsRepository):
    def add_finding(self, query, finding):
        pass

    def get_findings(self, query):
        return []

    def clear_findings(self, query):
        pass

    def synthesize_findings(
        self, query, sub_queries, findings, accumulated_knowledge
    ):
        return ""


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


class TestBaseKnowledgeGenerator:
    def setup_method(self):
        self.gen = ConcreteKnowledgeGenerator(model=Mock())

    def test_validate_knowledge_valid(self):
        assert self.gen._validate_knowledge("some knowledge") is True

    def test_validate_knowledge_empty_string(self):
        assert self.gen._validate_knowledge("") is False

    def test_validate_knowledge_none(self):
        assert self.gen._validate_knowledge(None) is False

    def test_validate_knowledge_non_string(self):
        assert self.gen._validate_knowledge(123) is False

    def test_validate_links_valid(self):
        assert (
            self.gen._validate_links(["http://a.com", "http://b.com"]) is True
        )

    def test_validate_links_empty_list(self):
        assert self.gen._validate_links([]) is True

    def test_validate_links_not_list(self):
        assert self.gen._validate_links("not a list") is False

    def test_validate_links_non_string_element(self):
        assert self.gen._validate_links(["ok", 42]) is False

    def test_extract_key_points(self):
        result = self.gen._extract_key_points("line1\nline2\nline3")
        assert result == ["line1", "line2", "line3"]

    def test_extract_key_points_single_line(self):
        result = self.gen._extract_key_points("single")
        assert result == ["single"]


class TestBaseConstraintChecker:
    def _make_candidate(self, name="TestCandidate"):
        return Candidate(name=name)

    def _make_constraint(self, value="must be blue", weight=1.0):
        return Constraint(
            id="c1",
            type=ConstraintType.PROPERTY,
            description="test",
            value=value,
            weight=weight,
        )

    def test_gather_evidence_with_gatherer(self):
        gatherer = Mock(return_value=[{"text": "evidence"}])
        checker = ConcreteConstraintChecker(
            model=Mock(), evidence_gatherer=gatherer
        )
        candidate = self._make_candidate()
        constraint = self._make_constraint()

        result = checker._gather_evidence_for_constraint(candidate, constraint)

        gatherer.assert_called_once_with(candidate, constraint)
        assert result == [{"text": "evidence"}]

    def test_gather_evidence_without_gatherer(self):
        checker = ConcreteConstraintChecker(
            model=Mock(), evidence_gatherer=None
        )
        result = checker._gather_evidence_for_constraint(
            self._make_candidate(), self._make_constraint()
        )
        assert result == []

    def test_log_constraint_result_high_score(self):
        checker = ConcreteConstraintChecker(model=Mock())
        checker._log_constraint_result(
            self._make_candidate(), self._make_constraint(), 0.9, {}
        )

    def test_log_constraint_result_medium_score(self):
        checker = ConcreteConstraintChecker(model=Mock())
        checker._log_constraint_result(
            self._make_candidate(), self._make_constraint(), 0.6, {}
        )

    def test_log_constraint_result_low_score(self):
        checker = ConcreteConstraintChecker(model=Mock())
        checker._log_constraint_result(
            self._make_candidate(), self._make_constraint(), 0.3, {}
        )

    def test_calculate_weighted_score(self):
        checker = ConcreteConstraintChecker(model=Mock())
        scores = [0.8, 0.6]
        weights = [2.0, 1.0]
        result = checker._calculate_weighted_score(scores, weights)
        expected = (0.8 * 2.0 + 0.6 * 1.0) / (2.0 + 1.0)
        assert abs(result - expected) < 1e-9

    def test_calculate_weighted_score_empty(self):
        checker = ConcreteConstraintChecker(model=Mock())
        assert checker._calculate_weighted_score([], []) == 0.0

    def test_calculate_weighted_score_empty_scores(self):
        checker = ConcreteConstraintChecker(model=Mock())
        assert checker._calculate_weighted_score([], [1.0]) == 0.0

    def test_calculate_weighted_score_empty_weights(self):
        checker = ConcreteConstraintChecker(model=Mock())
        assert checker._calculate_weighted_score([0.5], []) == 0.0


class TestBaseQuestionGenerator:
    def setup_method(self):
        self.gen = ConcreteQuestionGenerator(model=Mock())

    def test_format_previous_questions(self):
        questions_by_iteration = {
            1: ["What is X?", "What is Y?"],
            2: ["How does Z work?"],
        }
        result = self.gen._format_previous_questions(questions_by_iteration)
        assert "Iteration 1:" in result
        assert "- What is X?" in result
        assert "- What is Y?" in result
        assert "Iteration 2:" in result
        assert "- How does Z work?" in result

    def test_format_previous_questions_empty(self):
        result = self.gen._format_previous_questions({})
        assert result == ""


class TestBaseFollowUpQuestionGenerator:
    def setup_method(self):
        self.gen = ConcreteFollowUpQuestionGenerator(model=Mock())

    def test_init_sets_empty_context(self):
        assert self.gen.follow_up_context == {}

    def test_set_follow_up_context(self):
        ctx = {"past_findings": "some findings", "original_query": "test query"}
        self.gen.set_follow_up_context(ctx)
        assert self.gen.follow_up_context is ctx

    def test_generate_questions_returns_query(self):
        result = self.gen.generate_questions(
            current_knowledge="knowledge",
            query="my query",
            questions_per_iteration=3,
            questions_by_iteration={},
        )
        assert result == ["my query"]


class TestBaseFindingsRepository:
    def test_init_sets_model_and_findings(self):
        model = Mock()
        repo = ConcreteFindingsRepository(model=model)
        assert repo.model is model
        assert repo.findings == {}
