"""People tools for Crustdata MCP Server."""

import re
from typing import Optional

from mcp.server.fastmcp import Context
from pydantic import BaseModel, Field, field_validator, model_validator

from ..client import CrustdataAPIError, CrustdataAuthError, CrustdataNotFoundError
from ..client_factory import get_client_for_request
from ..models.common import PERSON_DB_VALID_COLUMNS, PostType, validate_linkedin_url
from ..server import AppContext, mcp


class PeopleEnrichInput(BaseModel):
    """Input parameters for people enrichment."""

    linkedin_profile_url: Optional[str] = Field(
        None,
        description="LinkedIn profile URL (e.g., 'https://linkedin.com/in/johndoe'). "
        "Can be comma-separated for up to 25 profiles.",
    )
    business_email: Optional[str] = Field(
        None,
        description="Business email address. Can be comma-separated for up to 25 emails.",
    )
    enrich_realtime: bool = Field(
        False,
        description="Force real-time enrichment (slower but more current data). "
        "Uses more credits. Default: false (uses cached data).",
    )
    fields: Optional[str] = Field(
        None,
        description="Comma-separated field names to return. If not specified, returns all available fields.",
    )

    @field_validator("linkedin_profile_url")
    @classmethod
    def validate_linkedin_urls(cls, v: Optional[str]) -> Optional[str]:
        """Validate LinkedIn profile URLs (supports comma-separated list)."""
        if v is None:
            return v
        urls = [url.strip() for url in v.split(",")]
        if len(urls) > 25:
            raise ValueError("Maximum 25 LinkedIn profile URLs allowed")
        for url in urls:
            validate_linkedin_url(url)
        return v

    @field_validator("business_email")
    @classmethod
    def validate_emails(cls, v: Optional[str]) -> Optional[str]:
        """Validate business email addresses (supports comma-separated list)."""
        if v is None:
            return v
        emails = [email.strip() for email in v.split(",")]
        if len(emails) > 25:
            raise ValueError("Maximum 25 email addresses allowed")
        email_pattern = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
        for email in emails:
            if not email_pattern.match(email):
                raise ValueError(f"Invalid email format: {email}")
        return v

    @model_validator(mode="after")
    def check_at_least_one_identifier(self) -> "PeopleEnrichInput":
        """Ensure at least one identifier is provided."""
        if not any([self.linkedin_profile_url, self.business_email]):
            raise ValueError(
                "At least one identifier required: linkedin_profile_url or business_email"
            )
        return self


@mcp.tool(
    name="crustdata_people_enrich",
    description="Get detailed person profile data by LinkedIn URL or email. "
    "Returns professional information including current role, company, experience history, "
    "education, and skills. Costs 2-5 credits depending on enrichment type.",
)
async def crustdata_people_enrich(
    params: PeopleEnrichInput,
    ctx: Context[None, AppContext, None],
) -> dict:
    """Get detailed person profile data.

    Returns comprehensive person information including:
    - Current position and company
    - Work history
    - Education background
    - Skills and certifications
    - Contact information

    Args:
        params: Person enrichment parameters (at least one identifier required).
        ctx: MCP context for logging and client access.

    Returns:
        Dictionary with person profile data or enrichment status.
    """
    client = get_client_for_request()

    # Build query params
    query_params: dict = {}
    if params.linkedin_profile_url:
        query_params["linkedin_profile_url"] = params.linkedin_profile_url
    if params.business_email:
        query_params["business_email"] = params.business_email
    if params.enrich_realtime:
        query_params["enrich_realtime"] = "true"
    if params.fields:
        query_params["fields"] = params.fields

    try:
        ctx.info(f"Enriching person with params: {query_params}")
        result = await client.get("/screener/person/enrich", params=query_params)

        if isinstance(result, list):
            ctx.info(f"Found {len(result)} person profiles")
            return {"profiles": result, "count": len(result)}
        else:
            ctx.info("Found 1 person profile")
            return result

    except CrustdataNotFoundError as e:
        ctx.info("Person not found - may be queued for enrichment")
        return {
            "found": False,
            "message": "Person not found in database. May be queued for enrichment.",
            "error": str(e),
        }
    except CrustdataAuthError as e:
        ctx.error(f"Authentication failed: {e}")
        raise
    except CrustdataAPIError as e:
        ctx.error(f"API error: {e}")
        raise


class PeopleSearchInput(BaseModel):
    """Input parameters for people database search."""

    filters: dict = Field(
        ...,
        description="""Search filters for people. Can be a simple filter or complex nested conditions.

IMPORTANT: PersonDB uses "column" as the field key (NOT "filter_type" like CompanyDB).

Simple filter example:
{"column": "name", "type": "(.)", "value": "John Smith"}

Complex AND filter example:
{
    "op": "and",
    "conditions": [
        {"column": "current_employers.name", "type": "(.)", "value": "Google"},
        {"column": "current_employers.title", "type": "(.)", "value": "Engineer"}
    ]
}

Available filter fields (use EXACTLY these names):
- name, first_name, last_name (person name)
- region (location string, e.g., "Bengaluru, Karnataka, India")
- headline, summary, skills (profile info)
- current_employers.name, current_employers.title, current_employers.seniority_level (current job)
- current_employers.company_id, current_employers.company_industries, current_employers.company_headcount_range
- past_employers.name, past_employers.title (previous jobs)
- education_background.institute_name, education_background.degree_name, education_background.field_of_study
- years_of_experience_raw (numeric), num_of_connections (numeric)

Operators: =, !=, in, not_in, (.) (fuzzy), [.] (match phrase)
Note: Use lowercase "and"/"or" for the "op" field.
""",
    )

    @model_validator(mode="after")
    def normalize_and_validate_filters(self) -> "PeopleSearchInput":
        """Normalize and validate filter structure for PersonDB API compatibility.

        - Converts 'filter_type' to 'column' (PersonDB uses 'column')
        - Normalizes 'op' to lowercase ('and'/'or')
        - Validates column names against known valid fields
        """
        self.filters, invalid_columns = self._normalize_filter(self.filters)
        if invalid_columns:
            raise ValueError(
                f"Unsupported columns in conditions: {invalid_columns}. "
                f"Use valid PersonDB column names like: name, headline, region, "
                f"current_employers.name, current_employers.title, skills, etc."
            )
        return self

    def _normalize_filter(self, filter_obj: dict) -> tuple[dict, list[str]]:
        """Recursively normalize a filter object and collect invalid columns.

        Returns:
            Tuple of (normalized_filter, list_of_invalid_columns)
        """
        if not isinstance(filter_obj, dict):
            return filter_obj, []

        result = {}
        invalid_columns: list[str] = []

        for key, value in filter_obj.items():
            if key == "filter_type":
                # Convert filter_type to column for PersonDB
                column_name = value
                result["column"] = column_name
                # Validate column name
                if column_name not in PERSON_DB_VALID_COLUMNS:
                    invalid_columns.append(column_name)
            elif key == "column":
                # Validate column name
                if value not in PERSON_DB_VALID_COLUMNS:
                    invalid_columns.append(value)
                result["column"] = value
            elif key == "op" and isinstance(value, str):
                # Normalize operator to lowercase
                result["op"] = value.lower()
            elif key == "conditions" and isinstance(value, list):
                # Recursively normalize conditions
                normalized_conditions = []
                for cond in value:
                    normalized_cond, cond_invalid = self._normalize_filter(cond)
                    normalized_conditions.append(normalized_cond)
                    invalid_columns.extend(cond_invalid)
                result["conditions"] = normalized_conditions
            else:
                result[key] = value

        return result, invalid_columns

    sorts: Optional[list[dict]] = Field(
        None,
        description='Sort options. Example: [{"column": "name", "order": "asc"}]',
    )
    limit: int = Field(
        20,
        ge=1,
        le=1000,
        description="Number of results to return (default: 20, max: 1000).",
    )
    cursor: Optional[str] = Field(
        None,
        description="Pagination cursor from previous response for fetching next page.",
    )


@mcp.tool(
    name="crustdata_people_search_db",
    description="Search the Crustdata people database with flexible filters. "
    "Find professionals by name, company, title, location, skills, and more. "
    "Uses database field names (different from real-time LinkedIn Sales Navigator filters). "
    "Costs 3 credits per 100 results.",
)
async def crustdata_people_search_db(
    params: PeopleSearchInput,
    ctx: Context[None, AppContext, None],
) -> dict:
    """Search people in the Crustdata database.

    Returns matching person profiles based on the provided filters.

    IMPORTANT: PersonDB uses "column" as the field key (NOT "filter_type" like CompanyDB).
    The validator will auto-convert filter_type to column for backward compatibility.

    NOTE: This database search uses different field names than the real-time
    LinkedIn Sales Navigator search (crustdata_people_search). Use database field
    names like 'headline', 'region', 'current_employers.name' instead of Sales
    Navigator filter types like 'CURRENT_COMPANY', 'REGION'.

    Common filter fields:
    - headline: LinkedIn headline (best for keyword search like "golang developer bangalore")
    - region: Location (e.g., "Bengaluru, Karnataka, India")
    - skills: Array of skills
    - name, first_name, last_name: Name fields
    - current_employers.name: Current company name
    - current_employers.title: Current job title
    - current_employers.company_industries: Company industries
    - years_of_experience_raw: Years of experience (numeric)

    Example filters:
    - Simple fuzzy search: {"column": "headline", "type": "(.)", "value": "golang developer bangalore"}
    - Filter by region: {"column": "region", "type": "(.)", "value": "Bengaluru, Karnataka, India"}
    - Complex AND: {"op": "and", "conditions": [{"column": "current_employers.name", "type": "(.)", "value": "Google"}, {"column": "years_of_experience_raw", "type": ">", "value": 5}]}

    Args:
        params: Search parameters including filters, sorts, limit, and cursor.
        ctx: MCP context for logging and client access.

    Returns:
        Dictionary with:
        - profiles: List of matching person profiles
        - total_count: Total number of matches
        - next_cursor: Cursor for fetching next page (if more results exist)
    """
    client = get_client_for_request()

    data: dict = {"filters": params.filters, "limit": params.limit}
    if params.sorts:
        data["sorts"] = params.sorts
    if params.cursor:
        data["cursor"] = params.cursor

    try:
        ctx.info(f"Searching people with filters: {params.filters}")
        result = await client.post("/screener/persondb/search", data=data)

        profiles = result.get("profiles", [])
        ctx.info(f"Found {len(profiles)} people")
        return {
            "profiles": profiles,
            "total_count": result.get("total_count"),
            "next_cursor": result.get("next_cursor"),
        }

    except CrustdataAuthError as e:
        ctx.error(f"Authentication failed: {e}")
        raise
    except CrustdataAPIError as e:
        ctx.error(f"API error: {e}")
        raise


class PeopleAutocompleteInput(BaseModel):
    """Input parameters for people autocomplete."""

    field: str = Field(
        ...,
        description="Field to get autocomplete suggestions for "
        "(e.g., 'name', 'current_employers.company_name', 'skills').",
    )
    query: str = Field(
        ...,
        description="Prefix/query to get suggestions for.",
    )
    limit: int = Field(
        20,
        ge=1,
        le=100,
        description="Maximum number of suggestions (default: 20, max: 100).",
    )


@mcp.tool(
    name="crustdata_autocomplete_person",
    description="Get autocomplete suggestions for people database fields. "
    "Useful for building filters or discovering valid field values.",
)
async def crustdata_autocomplete_person(
    params: PeopleAutocompleteInput,
    ctx: Context[None, AppContext, None],
) -> list[str]:
    """Get autocomplete suggestions for a people field.

    Args:
        params: Autocomplete parameters including field, query, and limit.
        ctx: MCP context for logging and client access.

    Returns:
        List of suggested values for the specified field.
    """
    client = get_client_for_request()

    data = {"field": params.field, "query": params.query, "limit": params.limit}

    try:
        ctx.info(f"Getting autocomplete for {params.field}: '{params.query}'")
        result = await client.post("/screener/persondb/autocomplete", data=data)

        suggestions = result.get("suggestions", [])
        ctx.info(f"Found {len(suggestions)} suggestions")
        return suggestions

    except CrustdataAuthError as e:
        ctx.error(f"Authentication failed: {e}")
        raise
    except CrustdataAPIError as e:
        ctx.error(f"API error: {e}")
        raise


# =============================================================================
# Real-time People Search - POST /screener/person/search
# =============================================================================


class PeopleSearchRealtimeInput(BaseModel):
    """Input parameters for real-time people search via LinkedIn Sales Navigator filters."""

    filters: list[dict] = Field(
        ...,
        description="""Array of filter objects for LinkedIn Sales Navigator style filtering.

Each filter object should contain:
- filter_type: The type of filter (e.g., CURRENT_COMPANY, CURRENT_TITLE, REGION)
- type: The operation type - "in", "not in", or "between"
- value: The filter value(s) - array of strings for most filters

Example filters:
[
  {"filter_type": "CURRENT_COMPANY", "type": "in", "value": ["Google", "Microsoft"]},
  {"filter_type": "SENIORITY_LEVEL", "type": "in", "value": ["CXO", "Vice President", "Director"]},
  {"filter_type": "REGION", "type": "in", "value": ["United States"]}
]

Common filter types (see PeopleSearchFilterType enum in models/common.py):
- CURRENT_COMPANY, PAST_COMPANY: Company names
- CURRENT_TITLE, PAST_TITLE: Job titles
- SENIORITY_LEVEL: CXO, Vice President, Director, Manager, Senior, Entry
- COMPANY_HEADCOUNT: Self-employed, 1-10, 11-50, 51-200, 201-500, 501-1,000, etc.
- INDUSTRY: Industry names
- REGION: Geographic regions
- KEYWORD: Keywords in profile
- SCHOOL: Education institution names

Note: For background_job processing of large result sets, use the 'limit' parameter instead.
""",
    )
    page: int = Field(
        1,
        ge=1,
        le=100,
        description="Page number for pagination (1-100 for sync requests). Each page returns up to 25 results.",
    )


@mcp.tool(
    name="crustdata_people_search",
    description="Real-time search for professionals using LinkedIn Sales Navigator style filters. "
    "Find decision makers by company, title, seniority, location, and more. "
    "Returns up to 25 results per page. Costs 2 credits per search.",
)
async def crustdata_people_search(
    params: PeopleSearchRealtimeInput,
    ctx: Context[None, AppContext, None],
) -> dict:
    """Search for professionals in real-time using advanced filters.

    Uses LinkedIn Sales Navigator style filtering for discovering professionals.

    Args:
        params: Search parameters including filters array and page number.
        ctx: MCP context for logging and client access.

    Returns:
        Dictionary with:
        - profiles: List of matching professional profiles (up to 25 per page)
        - total_display_count: Total number of profiles available (e.g., "1M+", "1.5K")
    """
    client = get_client_for_request()

    data: dict = {
        "filters": params.filters,
        "page": params.page,
    }

    try:
        ctx.info(f"Searching for people with {len(params.filters)} filters on page {params.page}")
        result = await client.post("/screener/person/search", data=data)

        profiles = result.get("profiles", [])
        total_display_count = result.get("total_display_count", "unknown")
        ctx.info(f"Found {len(profiles)} people (total: {total_display_count})")

        return {
            "profiles": profiles,
            "total_display_count": total_display_count,
        }

    except CrustdataAuthError as e:
        ctx.error(f"Authentication failed: {e}")
        raise
    except CrustdataAPIError as e:
        ctx.error(f"API error: {e}")
        raise


# =============================================================================
# LinkedIn Posts by Profile - GET /screener/linkedin_posts
# =============================================================================


class LinkedInPostsByProfileInput(BaseModel):
    """Input parameters for fetching LinkedIn posts from a person or company profile."""

    person_linkedin_url: Optional[str] = Field(
        None,
        description="LinkedIn profile URL of a person (e.g., 'https://linkedin.com/in/johndoe').",
    )
    company_domain: Optional[str] = Field(
        None,
        description="Company domain to fetch posts for (e.g., 'anthropic.com').",
    )
    company_linkedin_url: Optional[str] = Field(
        None,
        description="LinkedIn company page URL (e.g., 'https://linkedin.com/company/anthropic').",
    )
    company_name: Optional[str] = Field(
        None,
        description="Company name to fetch posts for.",
    )
    company_id: Optional[str] = Field(
        None,
        description="Crustdata company ID to fetch posts for.",
    )
    page: Optional[int] = Field(
        None,
        ge=1,
        le=20,
        description="Page number (1-20). Use either page OR limit, not both.",
    )
    limit: Optional[int] = Field(
        None,
        ge=1,
        le=100,
        description="Number of posts to return (1-100). Use either limit OR page, not both.",
    )
    post_types: Optional[str] = Field(
        None,
        description="Comma-separated post types: 'original', 'repost', or 'repost,original' (default).",
    )
    fields: Optional[str] = Field(
        None,
        description="Comma-separated fields. Add 'reactors' for users who reacted, 'comments' for comments.",
    )
    max_reactors: Optional[int] = Field(
        None,
        ge=0,
        le=5000,
        description="Max reactors per post when 'reactors' in fields (0-5000). Default: 100.",
    )
    max_comments: Optional[int] = Field(
        None,
        ge=0,
        le=5000,
        description="Max comments per post when 'comments' in fields (0-5000). Default: 100.",
    )

    @field_validator("person_linkedin_url")
    @classmethod
    def validate_person_linkedin_url(cls, v: Optional[str]) -> Optional[str]:
        """Validate person LinkedIn profile URL."""
        if v is not None:
            validate_linkedin_url(v)
        return v

    @field_validator("company_linkedin_url")
    @classmethod
    def validate_company_linkedin_url(cls, v: Optional[str]) -> Optional[str]:
        """Validate company LinkedIn page URL."""
        if v is not None:
            validate_linkedin_url(v)
        return v

    @field_validator("post_types")
    @classmethod
    def validate_post_types(cls, v: Optional[str]) -> Optional[str]:
        """Validate post_types against allowed PostType enum values.

        Supports comma-separated values of 'original' and/or 'repost'.
        """
        if v is None:
            return v

        # Split comma-separated values and strip whitespace
        types = [t.strip() for t in v.split(",")]

        # Valid values from PostType enum
        valid_types = {t.value for t in PostType}

        # Check each type
        invalid_types = [t for t in types if t not in valid_types]
        if invalid_types:
            raise ValueError(
                f"Invalid post types: {invalid_types}. "
                f"Valid types are: {', '.join(sorted(valid_types))}"
            )

        return v

    @model_validator(mode="after")
    def check_exactly_one_identifier(self) -> "LinkedInPostsByProfileInput":
        """Ensure exactly one identifier is provided."""
        identifiers = [
            self.person_linkedin_url,
            self.company_domain,
            self.company_linkedin_url,
            self.company_name,
            self.company_id,
        ]
        provided = [x for x in identifiers if x is not None]
        if len(provided) != 1:
            raise ValueError(
                "Exactly one identifier required: person_linkedin_url, company_domain, "
                "company_linkedin_url, company_name, or company_id"
            )
        return self

    @model_validator(mode="after")
    def check_page_limit_mutual_exclusivity(self) -> "LinkedInPostsByProfileInput":
        """Ensure page and limit are not both provided."""
        if self.page is not None and self.limit is not None:
            raise ValueError("Use either 'page' OR 'limit', not both")
        return self


@mcp.tool(
    name="crustdata_linkedin_posts",
    description="Get LinkedIn posts from a specific person or company profile. "
    "Returns post content, engagement metrics (likes, comments, shares), and optionally "
    "reactor/commenter details. Use for social media analysis and engagement tracking.",
)
async def crustdata_linkedin_posts(
    params: LinkedInPostsByProfileInput,
    ctx: Context[None, AppContext, None],
) -> dict:
    """Fetch LinkedIn posts for a person or company.

    Args:
        params: Parameters including profile identifier and pagination options.
        ctx: MCP context for logging and client access.

    Returns:
        Dictionary with:
        - posts: List of LinkedIn posts with content and engagement metrics
        - count: Number of posts returned
    """
    client = get_client_for_request()

    query_params: dict = {}
    if params.person_linkedin_url:
        query_params["person_linkedin_url"] = params.person_linkedin_url
    if params.company_domain:
        query_params["company_domain"] = params.company_domain
    if params.company_linkedin_url:
        query_params["company_linkedin_url"] = params.company_linkedin_url
    if params.company_name:
        query_params["company_name"] = params.company_name
    if params.company_id:
        query_params["company_id"] = params.company_id
    if params.page is not None:
        query_params["page"] = params.page
    if params.limit is not None:
        query_params["limit"] = params.limit
    if params.post_types:
        query_params["post_types"] = params.post_types
    if params.fields:
        query_params["fields"] = params.fields
    if params.max_reactors is not None:
        query_params["max_reactors"] = params.max_reactors
    if params.max_comments is not None:
        query_params["max_comments"] = params.max_comments

    try:
        profile_id = (
            params.person_linkedin_url
            or params.company_domain
            or params.company_linkedin_url
            or params.company_name
            or params.company_id
        )
        ctx.info(f"Fetching LinkedIn posts for: {profile_id}")
        result = await client.get("/screener/linkedin_posts", params=query_params)

        # API returns {"posts": [...]} or just a list
        if isinstance(result, list):
            posts = result
        else:
            posts = result.get("posts", [])

        ctx.info(f"Found {len(posts)} LinkedIn posts")
        return {
            "posts": posts,
            "count": len(posts),
        }

    except CrustdataNotFoundError:
        ctx.info("No posts found for the specified profile")
        return {"posts": [], "count": 0}
    except CrustdataAuthError as e:
        ctx.error(f"Authentication failed: {e}")
        raise
    except CrustdataAPIError as e:
        ctx.error(f"API error: {e}")
        raise
