"""Tests for search endpoint."""

from unittest.mock import MagicMock, patch
from uuid import uuid4

import pytest
from fastapi import HTTPException
from preloop.models.models.user import User


class TestPerformSearch:
    """Test perform_search function."""

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    @patch("preloop.api.endpoints.search.crud_issue_embedding")
    @patch("preloop.api.endpoints.search.crud_embedding_model")
    @patch("preloop.api.endpoints.search.crud_project")
    @patch("preloop.api.endpoints.search.crud_organization")
    async def test_similarity_search_success_with_issue(
        self,
        mock_crud_org,
        mock_crud_proj,
        mock_crud_embed_model,
        mock_crud_issue_embed,
        mock_get_accessible,
    ):
        """Test similarity search with Issue result."""
        from preloop.api.endpoints.search import perform_search
        from preloop.models import models as sm_models

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects
        mock_project = MagicMock()
        mock_project.id = uuid4()
        mock_project.name = "Test Project"
        mock_project.identifier = "TP"
        mock_project.slug = "test-project"
        mock_project.organization_id = uuid4()
        mock_get_accessible.return_value = [mock_project]

        # Mock active embedding model
        mock_model = MagicMock()
        mock_model.id = uuid4()
        mock_crud_embed_model.get_active.return_value = [mock_model]

        # Mock embedding generation
        mock_crud_issue_embed._generate_embedding_vector.return_value = [0.1, 0.2, 0.3]

        # Mock issue result
        mock_issue = MagicMock(spec=sm_models.Issue)
        mock_issue.id = uuid4()
        mock_issue.project_id = mock_project.id
        mock_issue.external_id = "EXT-123"
        mock_issue.key = "TP-1"
        mock_issue.title = "Test Issue"
        mock_issue.description = "Test description"
        mock_issue.status = "open"
        mock_issue.priority = "high"
        mock_issue.external_url = "https://example.com/issue"
        mock_issue.created_at = "2024-01-01T00:00:00"
        mock_issue.updated_at = "2024-01-02T00:00:00"
        mock_issue.meta_data = {"labels": ["bug", "urgent"]}

        mock_crud_issue_embed.similarity_search.return_value = [(mock_issue, 0.95)]

        # Mock project for result construction
        mock_crud_proj.get.return_value = mock_project

        # Mock organization for result construction
        mock_org = MagicMock()
        mock_org.name = "Test Org"
        mock_crud_org.get.return_value = mock_org

        # Call function
        mock_db = MagicMock()
        result = await perform_search(
            query="test query",
            db=mock_db,
            current_user=mock_user,
            search_type="similarity",
            limit=10,
            skip=0,
        )

        # Verify results
        assert len(result.results) == 1
        assert result.results[0].item_type == "issue"
        assert result.results[0].similarity == 0.95
        assert result.results[0].item.title == "Test Issue"
        assert result.results[0].item.organization == "Test Org"
        assert result.results[0].item.project == "Test Project"

        # Verify calls
        mock_get_accessible.assert_called_once()
        mock_crud_embed_model.get_active.assert_called_once_with(mock_db)
        mock_crud_issue_embed._generate_embedding_vector.assert_called_once()
        mock_crud_issue_embed.similarity_search.assert_called_once()

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    @patch("preloop.api.endpoints.search.crud_issue_embedding")
    @patch("preloop.api.endpoints.search.crud_embedding_model")
    async def test_similarity_search_success_with_comment(
        self, mock_crud_embed_model, mock_crud_issue_embed, mock_get_accessible
    ):
        """Test similarity search with Comment result."""
        from preloop.api.endpoints.search import perform_search
        from preloop.models import models as sm_models

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects
        mock_project = MagicMock()
        mock_project.id = uuid4()
        mock_get_accessible.return_value = [mock_project]

        # Mock active embedding model
        mock_model = MagicMock()
        mock_model.id = uuid4()
        mock_crud_embed_model.get_active.return_value = [mock_model]

        # Mock embedding generation
        mock_crud_issue_embed._generate_embedding_vector.return_value = [0.1, 0.2, 0.3]

        # Mock comment result
        mock_comment = MagicMock(spec=sm_models.Comment)
        mock_comment.id = uuid4()
        mock_comment.body = "Test comment"
        mock_comment.author = "john_doe"
        mock_comment.created_at = "2024-01-01T00:00:00"
        mock_comment.updated_at = "2024-01-02T00:00:00"
        mock_comment.issue_id = uuid4()
        mock_comment.meta_data = {"source": "web"}

        mock_crud_issue_embed.similarity_search.return_value = [(mock_comment, 0.85)]

        # Call function
        mock_db = MagicMock()
        result = await perform_search(
            query="test query",
            db=mock_db,
            current_user=mock_user,
            search_type="similarity",
            limit=10,
            skip=0,
        )

        # Verify results
        assert len(result.results) == 1
        assert result.results[0].item_type == "comment"
        assert result.results[0].similarity == 0.85
        assert result.results[0].item.body == "Test comment"
        assert result.results[0].item.author == "john_doe"

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    @patch("preloop.api.endpoints.search.crud_embedding_model")
    async def test_similarity_search_no_active_model(
        self, mock_crud_embed_model, mock_get_accessible
    ):
        """Test similarity search with no active embedding model."""
        from preloop.api.endpoints.search import perform_search

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects
        mock_get_accessible.return_value = []

        # Mock no active embedding model
        mock_crud_embed_model.get_active.return_value = []

        # Call function - should raise HTTPException
        mock_db = MagicMock()
        with pytest.raises(HTTPException) as exc_info:
            await perform_search(
                query="test query",
                db=mock_db,
                current_user=mock_user,
                search_type="similarity",
                limit=10,
                skip=0,
            )

        assert exc_info.value.status_code == 500
        assert "No active embedding model" in exc_info.value.detail

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    @patch("preloop.api.endpoints.search.crud_issue_embedding")
    @patch("preloop.api.endpoints.search.crud_embedding_model")
    async def test_similarity_search_embedding_generation_error(
        self, mock_crud_embed_model, mock_crud_issue_embed, mock_get_accessible
    ):
        """Test similarity search with embedding generation error."""
        from preloop.api.endpoints.search import perform_search

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects
        mock_get_accessible.return_value = []

        # Mock active embedding model
        mock_model = MagicMock()
        mock_model.id = uuid4()
        mock_crud_embed_model.get_active.return_value = [mock_model]

        # Mock embedding generation error
        mock_crud_issue_embed._generate_embedding_vector.side_effect = Exception(
            "API Error"
        )

        # Call function - should raise HTTPException
        mock_db = MagicMock()
        with pytest.raises(HTTPException) as exc_info:
            await perform_search(
                query="test query",
                db=mock_db,
                current_user=mock_user,
                search_type="similarity",
                limit=10,
                skip=0,
            )

        assert exc_info.value.status_code == 500
        assert "Error generating query vector" in exc_info.value.detail

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    @patch("preloop.api.endpoints.search.crud_issue_embedding")
    @patch("preloop.api.endpoints.search.crud_embedding_model")
    async def test_similarity_search_with_organization_id_filter(
        self, mock_crud_embed_model, mock_crud_issue_embed, mock_get_accessible
    ):
        """Test similarity search with organization_id filter."""
        from preloop.api.endpoints.search import perform_search

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects with different organizations
        mock_project1 = MagicMock()
        mock_project1.id = uuid4()
        mock_project1.organization_id = "org-1"

        mock_project2 = MagicMock()
        mock_project2.id = uuid4()
        mock_project2.organization_id = "org-2"

        mock_get_accessible.return_value = [mock_project1, mock_project2]

        # Mock active embedding model
        mock_model = MagicMock()
        mock_model.id = uuid4()
        mock_crud_embed_model.get_active.return_value = [mock_model]

        # Mock embedding generation
        mock_crud_issue_embed._generate_embedding_vector.return_value = [0.1, 0.2, 0.3]
        mock_crud_issue_embed.similarity_search.return_value = []

        # Call function with organization_id filter
        mock_db = MagicMock()
        result = await perform_search(
            query="test query",
            db=mock_db,
            current_user=mock_user,
            search_type="similarity",
            organization_id="org-1",
            limit=10,
            skip=0,
        )

        # Verify similarity_search was called with only proj-1 (org-1)
        call_args = mock_crud_issue_embed.similarity_search.call_args
        assert call_args[1]["project_ids"] == [mock_project1.id]

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    @patch("preloop.api.endpoints.search.crud_issue_embedding")
    @patch("preloop.api.endpoints.search.crud_embedding_model")
    async def test_similarity_search_with_organization_name_filter(
        self, mock_crud_embed_model, mock_crud_issue_embed, mock_get_accessible
    ):
        """Test similarity search with organization name filter."""
        from preloop.api.endpoints.search import perform_search

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects with different organizations
        mock_org1 = MagicMock()
        mock_org1.name = "Org Alpha"

        mock_org2 = MagicMock()
        mock_org2.name = "Org Beta"

        mock_project1 = MagicMock()
        mock_project1.id = uuid4()
        mock_project1.organization = mock_org1

        mock_project2 = MagicMock()
        mock_project2.id = uuid4()
        mock_project2.organization = mock_org2

        mock_get_accessible.return_value = [mock_project1, mock_project2]

        # Mock active embedding model
        mock_model = MagicMock()
        mock_model.id = uuid4()
        mock_crud_embed_model.get_active.return_value = [mock_model]

        # Mock embedding generation
        mock_crud_issue_embed._generate_embedding_vector.return_value = [0.1, 0.2, 0.3]
        mock_crud_issue_embed.similarity_search.return_value = []

        # Call function with organization name filter
        mock_db = MagicMock()
        result = await perform_search(
            query="test query",
            db=mock_db,
            current_user=mock_user,
            search_type="similarity",
            organization="Org Alpha",
            limit=10,
            skip=0,
        )

        # Verify similarity_search was called with only proj-1 (Org Alpha)
        call_args = mock_crud_issue_embed.similarity_search.call_args
        assert call_args[1]["project_ids"] == [mock_project1.id]

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    @patch("preloop.api.endpoints.search.crud_issue_embedding")
    @patch("preloop.api.endpoints.search.crud_embedding_model")
    async def test_similarity_search_with_project_name_filter(
        self, mock_crud_embed_model, mock_crud_issue_embed, mock_get_accessible
    ):
        """Test similarity search with project name filter."""
        from preloop.api.endpoints.search import perform_search

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects
        mock_project1 = MagicMock()
        mock_project1.id = uuid4()
        mock_project1.name = "Project Alpha"

        mock_project2 = MagicMock()
        mock_project2.id = uuid4()
        mock_project2.name = "Project Beta"

        mock_get_accessible.return_value = [mock_project1, mock_project2]

        # Mock active embedding model
        mock_model = MagicMock()
        mock_model.id = uuid4()
        mock_crud_embed_model.get_active.return_value = [mock_model]

        # Mock embedding generation
        mock_crud_issue_embed._generate_embedding_vector.return_value = [0.1, 0.2, 0.3]
        mock_crud_issue_embed.similarity_search.return_value = []

        # Call function with project name filter
        mock_db = MagicMock()
        result = await perform_search(
            query="test query",
            db=mock_db,
            current_user=mock_user,
            search_type="similarity",
            project="Project Alpha",
            limit=10,
            skip=0,
        )

        # Verify similarity_search was called with only proj-1 (Project Alpha)
        call_args = mock_crud_issue_embed.similarity_search.call_args
        assert call_args[1]["project_ids"] == [mock_project1.id]

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    async def test_fulltext_search_not_implemented(self, mock_get_accessible):
        """Test that fulltext search raises 501 Not Implemented."""
        from preloop.api.endpoints.search import perform_search

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects
        mock_get_accessible.return_value = []

        # Call function with fulltext search type
        mock_db = MagicMock()
        with pytest.raises(HTTPException) as exc_info:
            await perform_search(
                query="test query",
                db=mock_db,
                current_user=mock_user,
                search_type="fulltext",
                limit=10,
                skip=0,
            )

        assert exc_info.value.status_code == 501
        assert "not implemented" in exc_info.value.detail.lower()

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    async def test_invalid_search_type(self, mock_get_accessible):
        """Test that invalid search_type raises 400 Bad Request."""
        from preloop.api.endpoints.search import perform_search

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects
        mock_get_accessible.return_value = []

        # Call function with invalid search type
        mock_db = MagicMock()
        with pytest.raises(HTTPException) as exc_info:
            await perform_search(
                query="test query",
                db=mock_db,
                current_user=mock_user,
                search_type="invalid",
                limit=10,
                skip=0,
            )

        assert exc_info.value.status_code == 400
        assert "Invalid search_type" in exc_info.value.detail

    @pytest.mark.asyncio
    @patch("preloop.api.endpoints.search.get_accessible_projects")
    @patch("preloop.api.endpoints.search.crud_issue_embedding")
    @patch("preloop.api.endpoints.search.crud_embedding_model")
    @patch("preloop.api.endpoints.search.crud_project")
    async def test_similarity_search_with_status_filter(
        self,
        mock_crud_proj,
        mock_crud_embed_model,
        mock_crud_issue_embed,
        mock_get_accessible,
    ):
        """Test similarity search with status filter."""
        from preloop.api.endpoints.search import perform_search

        # Mock user
        mock_user = MagicMock(spec=User)
        mock_user.account_id = uuid4()

        # Mock accessible projects
        mock_project = MagicMock()
        mock_project.id = uuid4()
        mock_get_accessible.return_value = [mock_project]

        # Mock active embedding model
        mock_model = MagicMock()
        mock_model.id = uuid4()
        mock_crud_embed_model.get_active.return_value = [mock_model]

        # Mock embedding generation
        mock_crud_issue_embed._generate_embedding_vector.return_value = [0.1, 0.2, 0.3]
        mock_crud_issue_embed.similarity_search.return_value = []

        # Call function with status filter
        mock_db = MagicMock()
        result = await perform_search(
            query="test query",
            db=mock_db,
            current_user=mock_user,
            search_type="similarity",
            status="open",
            limit=10,
            skip=0,
        )

        # Verify similarity_search was called with status parameter
        call_args = mock_crud_issue_embed.similarity_search.call_args
        assert call_args[1]["status"] == "open"
