"""Unit tests for QueryTermMatcher."""

import json
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from src.extraction_v2.domain_term_extractor import DomainTaxonomy
from src.knowledge.query_term_matcher import QueryTermMatcher
from src.knowledge.term_index import TermIndex


@pytest.fixture
def mock_term_index():
    """Create a mock TermIndex."""
    term_index = AsyncMock(spec=TermIndex)
    term_index.load_tenant_terms = AsyncMock(return_value=set())
    return term_index


@pytest.fixture
def matcher(mock_term_index):
    """Create QueryTermMatcher with mocked dependencies."""
    return QueryTermMatcher(term_index=mock_term_index)


class TestQueryTermMatcher:
    """Tests for QueryTermMatcher class."""

    @pytest.mark.asyncio
    async def test_extract_query_terms_finance_query(self, matcher):
        """Should extract finance terms from finance query."""
        query = "What is the Q4 EBITDA and revenue growth?"

        result = await matcher.extract_query_terms(query)

        assert "matched_terms" in result
        assert "domain_category" in result
        assert "matching_method" in result

        # Should find finance terms
        assert "ebitda" in result["matched_terms"]
        assert "revenue" in result["matched_terms"]
        assert result["domain_category"] == "finance"

    @pytest.mark.asyncio
    async def test_extract_query_terms_hr_query(self, matcher):
        """Should extract HR terms from HR query."""
        query = "How many employees were hired last quarter? What was the total payroll?"

        result = await matcher.extract_query_terms(query)

        assert "employee" in result["matched_terms"] or "hire" in result["matched_terms"]
        assert "payroll" in result["matched_terms"]
        assert result["domain_category"] == "hr"

    @pytest.mark.asyncio
    async def test_extract_query_terms_empty_query(self, matcher):
        """Should return empty results for empty query."""
        result = await matcher.extract_query_terms("")

        assert result["matched_terms"] == []
        assert result["domain_category"] == "general"
        assert result["matching_method"] == "none"

    @pytest.mark.asyncio
    async def test_extract_query_terms_whitespace_query(self, matcher):
        """Should return empty results for whitespace-only query."""
        result = await matcher.extract_query_terms("   \n\t  ")

        assert result["matched_terms"] == []
        assert result["domain_category"] == "general"

    @pytest.mark.asyncio
    async def test_extract_query_terms_respects_max_limit(self, matcher):
        """Should limit matched terms to MAX_QUERY_TERMS."""
        # Query with many matching terms
        query = """
        What is the revenue, EBITDA, profit, margin, income, expense,
        asset, liability, equity, cash flow, investment, dividend, earnings
        for the quarterly financial report?
        """

        result = await matcher.extract_query_terms(query)

        # Should respect MAX_QUERY_TERMS limit (default 10)
        assert len(result["matched_terms"]) <= 10

    @pytest.mark.asyncio
    async def test_extract_query_terms_deduplicates(self, matcher):
        """Should deduplicate repeated terms."""
        query = "Revenue revenue REVENUE analysis"

        result = await matcher.extract_query_terms(query)

        # Should only include "revenue" once
        revenue_count = result["matched_terms"].count("revenue")
        assert revenue_count == 1

    @pytest.mark.asyncio
    async def test_keyword_match_exact_matching(self, matcher):
        """Should match exact terms from taxonomy."""
        query = "Show me the quarterly revenue and profit margins"

        matched = await matcher._keyword_match(query, "test_tenant")

        # Should find exact matches
        assert "revenue" in matched
        assert "profit" in matched
        assert "margin" in matched or "quarterly" in matched

    @pytest.mark.asyncio
    async def test_keyword_match_uses_word_boundaries(self, matcher):
        """Should use word boundaries to avoid partial matches."""
        query = "The company's revenue stream is strong"

        matched = await matcher._keyword_match(query, "test_tenant")

        assert "revenue" in matched

    @pytest.mark.asyncio
    async def test_keyword_match_case_insensitive(self, matcher):
        """Should match case-insensitively."""
        query = "EBITDA and REVENUE increased significantly"

        matched = await matcher._keyword_match(query, "test_tenant")

        assert "ebitda" in matched
        assert "revenue" in matched

    @pytest.mark.asyncio
    async def test_keyword_match_with_indexed_terms(self, mock_term_index):
        """Should use indexed terms for fuzzy matching."""
        # Setup indexed terms
        indexed_terms = {"custom_metric", "proprietary_term", "domain_specific"}
        mock_term_index.load_tenant_terms = AsyncMock(return_value=indexed_terms)

        matcher = QueryTermMatcher(term_index=mock_term_index)
        query = "What is the custom metric for this quarter?"

        matched = await matcher._keyword_match(query, "test_tenant")

        # Should find indexed term
        assert "custom_metric" in matched or "custom" in matched

    @patch("src.knowledge.query_term_matcher.QueryTermMatcher._get_client")
    @pytest.mark.asyncio
    async def test_extract_query_terms_uses_llm_when_few_matches(
        self, mock_get_client, mock_term_index
    ):
        """Should use LLM expansion when keyword matching finds <2 terms."""
        # Mock LLM response
        mock_client = MagicMock()
        mock_response = MagicMock()
        mock_response.choices[0].message.content = json.dumps({
            "terms": ["strategic", "planning", "goals"],
            "category": "operations"
        })
        mock_client.chat.completions.create.return_value = mock_response
        mock_get_client.return_value = mock_client

        matcher = QueryTermMatcher(term_index=mock_term_index)

        with patch.dict("os.environ", {"QUERY_TERM_LLM_EXPANSION": "true"}):
            # Query with generic terms (few taxonomy matches)
            query = "Tell me about our strategic planning initiatives"

            result = await matcher.extract_query_terms(query)

        # Should have used LLM
        assert result["matching_method"] == "llm"
        assert len(result["matched_terms"]) > 0

    @patch("src.knowledge.query_term_matcher.QueryTermMatcher._get_client")
    def test_llm_expand_handles_json_parse_error(self, mock_get_client):
        """Should handle LLM returning invalid JSON gracefully."""
        # Mock LLM returning invalid JSON
        mock_client = MagicMock()
        mock_response = MagicMock()
        mock_response.choices[0].message.content = "invalid json"
        mock_client.chat.completions.create.return_value = mock_response
        mock_get_client.return_value = mock_client

        matcher = QueryTermMatcher()
        result = matcher._llm_expand("test query")

        # Should return empty results
        assert result["terms"] == []
        assert result["category"] == "general"

    @patch("src.knowledge.query_term_matcher.QueryTermMatcher._get_client")
    def test_llm_expand_handles_exception(self, mock_get_client):
        """Should handle LLM API errors gracefully."""
        # Mock LLM raising exception
        mock_client = MagicMock()
        mock_client.chat.completions.create.side_effect = Exception("API Error")
        mock_get_client.return_value = mock_client

        matcher = QueryTermMatcher()
        result = matcher._llm_expand("test query")

        # Should return empty results
        assert result["terms"] == []
        assert result["category"] == "general"

    @patch("src.knowledge.query_term_matcher.QueryTermMatcher._get_client")
    def test_llm_expand_success(self, mock_get_client):
        """Should successfully expand query terms using LLM."""
        # Mock successful LLM response
        mock_client = MagicMock()
        mock_response = MagicMock()
        mock_response.choices[0].message.content = json.dumps({
            "terms": ["budget", "forecast", "planning"],
            "category": "finance"
        })
        mock_client.chat.completions.create.return_value = mock_response
        mock_get_client.return_value = mock_client

        matcher = QueryTermMatcher()
        result = matcher._llm_expand("What are our financial projections?")

        assert result["terms"] == ["budget", "forecast", "planning"]
        assert result["category"] == "finance"

    def test_infer_category_from_terms_finance(self, matcher):
        """Should infer finance category from finance terms."""
        terms = ["revenue", "ebitda", "profit"]
        category = matcher._infer_category_from_terms(terms)

        assert category == "finance"

    def test_infer_category_from_terms_hr(self, matcher):
        """Should infer HR category from HR terms."""
        terms = ["employee", "salary", "payroll"]
        category = matcher._infer_category_from_terms(terms)

        assert category == "hr"

    def test_infer_category_from_terms_mixed(self, matcher):
        """Should choose category with most matches for mixed terms."""
        # More finance terms than HR
        terms = ["revenue", "ebitda", "profit", "employee"]
        category = matcher._infer_category_from_terms(terms)

        assert category == "finance"

    def test_infer_category_from_terms_no_match(self, matcher):
        """Should return 'general' when no terms match taxonomy."""
        terms = ["unknown", "random", "words"]
        category = matcher._infer_category_from_terms(terms)

        assert category == "general"

    def test_infer_category_from_terms_empty(self, matcher):
        """Should return 'general' for empty terms."""
        category = matcher._infer_category_from_terms([])

        assert category == "general"


class TestIntegration:
    """Integration tests for QueryTermMatcher."""

    @pytest.mark.asyncio
    async def test_full_query_matching_pipeline_finance(self):
        """Test complete query matching flow with finance query."""
        matcher = QueryTermMatcher()

        query = """
        What was our Q4 2023 financial performance?
        I need to see EBITDA, revenue growth, and profit margins.
        """

        result = await matcher.extract_query_terms(query)

        # Should identify as finance
        assert result["domain_category"] == "finance"

        # Should extract key finance terms
        matched = set(result["matched_terms"])
        finance_terms = {"ebitda", "revenue", "profit", "margin", "quarterly", "quarter", "financial"}

        # At least 50% of finance terms should be matched
        overlap = len(finance_terms & matched)
        assert overlap >= 3

    @pytest.mark.asyncio
    async def test_full_query_matching_pipeline_hr(self):
        """Test complete query matching flow with HR query."""
        matcher = QueryTermMatcher()

        query = """
        How many employees were hired in Q3?
        What was the total compensation and benefits cost?
        """

        result = await matcher.extract_query_terms(query)

        # Should identify as HR
        assert result["domain_category"] == "hr"

        # Should have HR-related terms
        matched = set(result["matched_terms"])
        assert len(matched & {"employee", "hire", "compensation", "benefit"}) >= 2
