"""Tests for people tools."""

import pytest
import respx
from httpx import Response
from pydantic import ValidationError

from crustdata_mcp.client import CrustdataClient
from crustdata_mcp.tools.people import (
    LinkedInPostsByProfileInput,
    PeopleEnrichInput,
    PeopleSearchInput,
    PeopleSearchRealtimeInput,
)


class TestPeopleEnrichInput:
    """Tests for PeopleEnrichInput validation."""

    def test_valid_with_linkedin(self):
        """Test valid input with LinkedIn URL."""
        params = PeopleEnrichInput(
            linkedin_profile_url="https://linkedin.com/in/johndoe"
        )
        assert params.linkedin_profile_url is not None

    def test_valid_with_email(self):
        """Test valid input with business email."""
        params = PeopleEnrichInput(business_email="john@company.com")
        assert params.business_email == "john@company.com"

    def test_invalid_no_identifier(self):
        """Test that at least one identifier is required."""
        with pytest.raises(ValidationError, match="At least one identifier required"):
            PeopleEnrichInput()

    def test_invalid_linkedin_url(self):
        """Test validation of LinkedIn URL format."""
        with pytest.raises(ValidationError, match="Invalid LinkedIn URL"):
            PeopleEnrichInput(linkedin_profile_url="https://twitter.com/user")

    def test_invalid_email_format(self):
        """Test validation of email format."""
        with pytest.raises(ValidationError, match="Invalid email format"):
            PeopleEnrichInput(business_email="not-an-email")

    def test_multiple_linkedin_urls(self):
        """Test multiple comma-separated LinkedIn URLs."""
        params = PeopleEnrichInput(
            linkedin_profile_url="https://linkedin.com/in/user1,https://linkedin.com/in/user2"
        )
        assert params.linkedin_profile_url is not None

    def test_too_many_linkedin_urls(self):
        """Test that more than 25 URLs is rejected."""
        urls = ",".join([f"https://linkedin.com/in/user{i}" for i in range(26)])
        with pytest.raises(ValidationError, match="Maximum 25 LinkedIn profile URLs"):
            PeopleEnrichInput(linkedin_profile_url=urls)

    def test_multiple_emails(self):
        """Test multiple comma-separated emails."""
        params = PeopleEnrichInput(business_email="user1@company.com,user2@company.com")
        assert params.business_email is not None

    def test_too_many_emails(self):
        """Test that more than 25 emails is rejected."""
        emails = ",".join([f"user{i}@company.com" for i in range(26)])
        with pytest.raises(ValidationError, match="Maximum 25 email addresses"):
            PeopleEnrichInput(business_email=emails)

    def test_fields_as_string(self):
        """Test that fields parameter accepts comma-separated string."""
        params = PeopleEnrichInput(
            linkedin_profile_url="https://linkedin.com/in/user",
            fields="name,headline,current_employers",
        )
        assert params.fields == "name,headline,current_employers"


class TestPeopleSearchInput:
    """Tests for PeopleSearchInput validation."""

    def test_valid_filter_with_column(self):
        """Test valid search input with column (correct PersonDB format)."""
        params = PeopleSearchInput(
            filters={"column": "name", "type": "(.)", "value": "John"},
            limit=10,
        )
        assert params.filters["column"] == "name"
        assert params.limit == 10

    def test_filter_type_converted_to_column(self):
        """Test that filter_type is automatically converted to column for PersonDB."""
        params = PeopleSearchInput(
            filters={"filter_type": "name", "type": "(.)", "value": "John"},
            limit=10,
        )
        # filter_type should be converted to column
        assert "column" in params.filters
        assert "filter_type" not in params.filters
        assert params.filters["column"] == "name"

    def test_operator_normalized_to_lowercase(self):
        """Test that op is normalized to lowercase."""
        params = PeopleSearchInput(
            filters={
                "op": "AND",
                "conditions": [
                    {"column": "name", "type": "(.)", "value": "John"},
                ],
            },
        )
        assert params.filters["op"] == "and"

    def test_complex_filter_normalization(self):
        """Test normalization of complex nested filters."""
        params = PeopleSearchInput(
            filters={
                "op": "AND",
                "conditions": [
                    {"filter_type": "name", "type": "(.)", "value": "John"},
                    {
                        "op": "OR",
                        "conditions": [
                            {"filter_type": "current_employers.name", "type": "(.)", "value": "Google"},
                            {"filter_type": "current_employers.name", "type": "(.)", "value": "Meta"},
                        ],
                    },
                ],
            },
        )
        # Check top level
        assert params.filters["op"] == "and"
        # Check first condition - filter_type converted to column
        assert params.filters["conditions"][0]["column"] == "name"
        assert "filter_type" not in params.filters["conditions"][0]
        # Check nested OR
        assert params.filters["conditions"][1]["op"] == "or"
        assert params.filters["conditions"][1]["conditions"][0]["column"] == "current_employers.name"

    def test_invalid_column_name_rejected(self):
        """Test that invalid column names are rejected with helpful error."""
        with pytest.raises(ValidationError) as exc_info:
            PeopleSearchInput(
                filters={"column": "current_employers.company_name", "type": "(.)", "value": "Google"},
            )
        # Check error message mentions the invalid column
        assert "current_employers.company_name" in str(exc_info.value)
        assert "Unsupported columns" in str(exc_info.value)

    def test_invalid_column_in_nested_filter_rejected(self):
        """Test that invalid columns in nested filters are caught."""
        with pytest.raises(ValidationError) as exc_info:
            PeopleSearchInput(
                filters={
                    "op": "and",
                    "conditions": [
                        {"column": "name", "type": "(.)", "value": "John"},
                        {"column": "invalid_field_name", "type": "(.)", "value": "test"},
                    ],
                },
            )
        assert "invalid_field_name" in str(exc_info.value)

    def test_limit_range(self):
        """Test limit validation."""
        # Too high
        with pytest.raises(ValidationError):
            PeopleSearchInput(filters={}, limit=2000)

        # Too low
        with pytest.raises(ValidationError):
            PeopleSearchInput(filters={}, limit=0)


class TestPeopleEnrichTool:
    """Tests for crustdata_people_enrich tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_enrich_by_linkedin_success(self):
        """Test successful person enrichment by LinkedIn URL."""
        expected_response = {
            "person_id": "12345",
            "name": "John Doe",
            "headline": "Software Engineer at Tech Corp",
            "current_employers": [{"company_name": "Tech Corp", "title": "Software Engineer"}],
        }
        respx.get("https://api.crustdata.com/screener/person/enrich").mock(
            return_value=Response(200, json=expected_response)
        )

        async with CrustdataClient("test-token") as client:
            result = await client.get(
                "/screener/person/enrich",
                params={"linkedin_profile_url": "https://linkedin.com/in/johndoe"},
            )

        assert result["name"] == "John Doe"
        assert result["person_id"] == "12345"

    @pytest.mark.asyncio
    @respx.mock
    async def test_enrich_not_found(self):
        """Test enrichment when person not found."""
        from crustdata_mcp.client import CrustdataNotFoundError

        respx.get("https://api.crustdata.com/screener/person/enrich").mock(
            return_value=Response(404, json={"error": "Person not found", "error_code": "PE03"})
        )

        async with CrustdataClient("test-token") as client:
            with pytest.raises(CrustdataNotFoundError):
                await client.get(
                    "/screener/person/enrich",
                    params={"linkedin_profile_url": "https://linkedin.com/in/unknown"},
                )

    def test_tool_is_registered(self):
        """Test that people enrich tool is registered."""
        from crustdata_mcp.server import mcp

        tools = list(mcp._tool_manager._tools.keys())
        assert "crustdata_people_enrich" in tools


class TestPeopleSearchDbTool:
    """Tests for crustdata_people_search_db tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_search_by_name_success(self):
        """Test successful people search by name."""
        expected_response = {
            "profiles": [
                {"person_id": "1", "name": "John Doe", "headline": "Engineer"},
                {"person_id": "2", "name": "John Smith", "headline": "Manager"},
            ],
            "total_count": 2,
        }
        respx.post("https://api.crustdata.com/screener/persondb/search").mock(
            return_value=Response(200, json=expected_response)
        )

        async with CrustdataClient("test-token") as client:
            result = await client.post(
                "/screener/persondb/search",
                data={
                    "filters": {"filter_type": "name", "type": "(.)", "value": "John"},
                    "limit": 10,
                },
            )

        assert len(result["profiles"]) == 2
        assert result["total_count"] == 2

    @pytest.mark.asyncio
    @respx.mock
    async def test_search_with_complex_filter(self):
        """Test people search with complex AND filter."""
        expected_response = {
            "profiles": [{"person_id": "1", "name": "John Doe"}],
            "total_count": 1,
        }
        respx.post("https://api.crustdata.com/screener/persondb/search").mock(
            return_value=Response(200, json=expected_response)
        )

        filters = {
            "op": "AND",
            "conditions": [
                {"filter_type": "current_employers.company_name", "type": "(.)", "value": "Google"},
                {"filter_type": "current_employers.title", "type": "(.)", "value": "Engineer"},
            ],
        }

        async with CrustdataClient("test-token") as client:
            result = await client.post(
                "/screener/persondb/search",
                data={"filters": filters, "limit": 10},
            )

        assert len(result["profiles"]) == 1

    def test_tool_is_registered(self):
        """Test that people search tool is registered."""
        from crustdata_mcp.server import mcp

        tools = list(mcp._tool_manager._tools.keys())
        assert "crustdata_people_search_db" in tools


class TestPeopleAutocompleteTool:
    """Tests for crustdata_autocomplete_person tool."""

    @pytest.mark.asyncio
    @respx.mock
    async def test_autocomplete_success(self):
        """Test successful autocomplete."""
        expected_response = {"suggestions": ["John Doe", "John Smith", "John Adams"]}
        respx.post("https://api.crustdata.com/screener/persondb/autocomplete").mock(
            return_value=Response(200, json=expected_response)
        )

        async with CrustdataClient("test-token") as client:
            result = await client.post(
                "/screener/persondb/autocomplete",
                data={"field": "name", "query": "John"},
            )

        assert "John Doe" in result["suggestions"]

    def test_tool_is_registered(self):
        """Test that autocomplete tool is registered."""
        from crustdata_mcp.server import mcp

        tools = list(mcp._tool_manager._tools.keys())
        assert "crustdata_autocomplete_person" in tools


class TestPeopleSearchRealtimeInput:
    """Tests for PeopleSearchRealtimeInput validation."""

    def test_valid_input(self):
        """Test valid input with filters."""
        params = PeopleSearchRealtimeInput(
            filters=[
                {"filter_type": "CURRENT_COMPANY", "type": "in", "value": ["Google"]},
                {"filter_type": "REGION", "type": "in", "value": ["United States"]},
            ]
        )
        assert len(params.filters) == 2
        assert params.page == 1

    def test_valid_page_range(self):
        """Test valid page range."""
        params = PeopleSearchRealtimeInput(
            filters=[{"filter_type": "KEYWORD", "type": "in", "value": ["engineer"]}],
            page=50,
        )
        assert params.page == 50

    def test_page_upper_bound(self):
        """Test page upper bound of 100."""
        params = PeopleSearchRealtimeInput(
            filters=[{"filter_type": "KEYWORD", "type": "in", "value": ["engineer"]}],
            page=100,
        )
        assert params.page == 100

    def test_page_exceeds_upper_bound(self):
        """Test that page > 100 is rejected."""
        with pytest.raises(ValidationError, match="less than or equal to 100"):
            PeopleSearchRealtimeInput(
                filters=[{"filter_type": "KEYWORD", "type": "in", "value": ["engineer"]}],
                page=101,
            )

    def test_page_below_lower_bound(self):
        """Test that page < 1 is rejected."""
        with pytest.raises(ValidationError, match="greater than or equal to 1"):
            PeopleSearchRealtimeInput(
                filters=[{"filter_type": "KEYWORD", "type": "in", "value": ["engineer"]}],
                page=0,
            )


class TestLinkedInPostsByProfileInput:
    """Tests for LinkedInPostsByProfileInput validation."""

    def test_valid_person_linkedin_url(self):
        """Test valid input with person LinkedIn URL."""
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe"
        )
        assert params.person_linkedin_url is not None

    def test_valid_company_domain(self):
        """Test valid input with company domain."""
        params = LinkedInPostsByProfileInput(company_domain="anthropic.com")
        assert params.company_domain == "anthropic.com"

    def test_invalid_person_linkedin_url(self):
        """Test validation of person LinkedIn URL format."""
        with pytest.raises(ValidationError, match="Invalid LinkedIn URL"):
            LinkedInPostsByProfileInput(person_linkedin_url="https://twitter.com/user")

    def test_invalid_company_linkedin_url(self):
        """Test validation of company LinkedIn URL format."""
        with pytest.raises(ValidationError, match="Invalid LinkedIn URL"):
            LinkedInPostsByProfileInput(company_linkedin_url="https://twitter.com/company")

    def test_no_identifier(self):
        """Test that at least one identifier is required."""
        with pytest.raises(ValidationError, match="Exactly one identifier required"):
            LinkedInPostsByProfileInput()

    def test_multiple_identifiers(self):
        """Test that multiple identifiers are rejected."""
        with pytest.raises(ValidationError, match="Exactly one identifier required"):
            LinkedInPostsByProfileInput(
                person_linkedin_url="https://linkedin.com/in/johndoe",
                company_domain="anthropic.com",
            )

    def test_page_and_limit_mutual_exclusivity(self):
        """Test that page and limit cannot both be provided."""
        with pytest.raises(ValidationError, match="Use either 'page' OR 'limit', not both"):
            LinkedInPostsByProfileInput(
                person_linkedin_url="https://linkedin.com/in/johndoe",
                page=1,
                limit=10,
            )

    def test_valid_with_page_only(self):
        """Test valid input with page parameter."""
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            page=5,
        )
        assert params.page == 5
        assert params.limit is None

    def test_valid_with_limit_only(self):
        """Test valid input with limit parameter."""
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            limit=50,
        )
        assert params.limit == 50
        assert params.page is None

    def test_page_range_validation(self):
        """Test page range 1-20."""
        # Valid
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            page=20,
        )
        assert params.page == 20

        # Invalid - too high
        with pytest.raises(ValidationError, match="less than or equal to 20"):
            LinkedInPostsByProfileInput(
                person_linkedin_url="https://linkedin.com/in/johndoe",
                page=21,
            )

    def test_limit_range_validation(self):
        """Test limit range 1-100."""
        # Valid
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            limit=100,
        )
        assert params.limit == 100

        # Invalid - too high
        with pytest.raises(ValidationError, match="less than or equal to 100"):
            LinkedInPostsByProfileInput(
                person_linkedin_url="https://linkedin.com/in/johndoe",
                limit=101,
            )

    def test_max_reactors_range(self):
        """Test max_reactors range 0-5000."""
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            max_reactors=5000,
        )
        assert params.max_reactors == 5000

        with pytest.raises(ValidationError, match="less than or equal to 5000"):
            LinkedInPostsByProfileInput(
                person_linkedin_url="https://linkedin.com/in/johndoe",
                max_reactors=5001,
            )

    def test_max_comments_range(self):
        """Test max_comments range 0-5000."""
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            max_comments=5000,
        )
        assert params.max_comments == 5000

        with pytest.raises(ValidationError, match="less than or equal to 5000"):
            LinkedInPostsByProfileInput(
                person_linkedin_url="https://linkedin.com/in/johndoe",
                max_comments=5001,
            )

    def test_post_types_valid_single(self):
        """Test valid single post_types value."""
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            post_types="original",
        )
        assert params.post_types == "original"

        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            post_types="repost",
        )
        assert params.post_types == "repost"

    def test_post_types_valid_comma_separated(self):
        """Test valid comma-separated post_types values."""
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            post_types="original,repost",
        )
        assert params.post_types == "original,repost"

        # With spaces
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
            post_types="original, repost",
        )
        assert params.post_types == "original, repost"

    def test_post_types_invalid(self):
        """Test invalid post_types value is rejected."""
        with pytest.raises(ValidationError, match="Invalid post types"):
            LinkedInPostsByProfileInput(
                person_linkedin_url="https://linkedin.com/in/johndoe",
                post_types="invalid_type",
            )

    def test_post_types_invalid_mixed(self):
        """Test invalid post_types in comma-separated list is rejected."""
        with pytest.raises(ValidationError, match="Invalid post types"):
            LinkedInPostsByProfileInput(
                person_linkedin_url="https://linkedin.com/in/johndoe",
                post_types="original,invalid",
            )

    def test_post_types_none_is_valid(self):
        """Test that None post_types is accepted (optional field)."""
        params = LinkedInPostsByProfileInput(
            person_linkedin_url="https://linkedin.com/in/johndoe",
        )
        assert params.post_types is None
