"""Payload builders and response parsing for FullEnrich."""

from __future__ import annotations

import logging
from typing import Any, Dict, Iterable, List, Optional, Tuple

from . import constants as fe_constants
from .constants import (
    CONTACT_ERROR_STATUSES,
    CONTACT_NOT_FOUND_STATUSES,
    DEFAULT_CONTACT_FIELDS,
    FullEnrichAPIVersion,
)
from .schema import ContactEnrichmentInput, ContactEnrichmentResult, EnrichmentResultStatus
from .validation import derive_contact_name, extract_domain_from_email, is_valid_contact_input

logger = logging.getLogger(__name__)

_LEGACY_NAME_KEYS = {
    "firstname",
    "lastname",
    "first name",
    "last name",
    "firstName",
    "lastName",
}


def _stringify_custom(custom: Dict[str, Any], skip_keys: Optional[set[str]] = None) -> Dict[str, str]:
    payload: Dict[str, str] = {}
    skip_keys = {str(k).strip().lower() for k in (skip_keys or set())}
    for key, value in (custom or {}).items():
        if value is None:
            continue
        key_text = str(key).strip()
        if not key_text:
            continue
        if key_text.lower() in skip_keys:
            continue
        text = str(value).strip()
        if text:
            payload[key_text] = text
    return payload


def build_custom_metadata(
    custom: Optional[Dict[str, Any]] = None,
    *,
    first_name: Optional[str] = None,
    last_name: Optional[str] = None,
    email: Optional[str] = None,
    linkedin_url: Optional[str] = None,
    row_index: Optional[int] = None,
    api_version: Optional[str] = None,
    job_id: Optional[str] = None,
    run_id: Optional[str] = None,
    workspace_id: Optional[str] = None,
) -> Dict[str, str]:
    payload = _stringify_custom(custom or {}, skip_keys=_LEGACY_NAME_KEYS)
    if row_index is not None:
        payload.setdefault("row_index", str(row_index))
    if email:
        payload.setdefault("email", str(email).strip())
    if linkedin_url:
        payload.setdefault("linkedin_url", str(linkedin_url).strip())
    if first_name:
        payload.setdefault("first_name", str(first_name).strip())
    if last_name:
        payload.setdefault("last_name", str(last_name).strip())
    if api_version:
        payload.setdefault("api_version", str(api_version))
    if job_id:
        payload.setdefault("job_id", str(job_id))
    if run_id:
        payload.setdefault("run_id", str(run_id))
    if workspace_id:
        payload.setdefault("workspace_id", str(workspace_id))
    return payload


def normalize_contact_list(value: Any) -> List[str]:
    if value is None:
        return []
    if isinstance(value, list):
        values: List[str] = []
        for item in value:
            if isinstance(item, dict):
                candidate = item.get("email") or item.get("number") or item.get("phone")
                if candidate and str(candidate).strip():
                    values.append(str(candidate).strip())
            else:
                if str(item).strip():
                    values.append(str(item).strip())
        return values
    if isinstance(value, str):
        return [value] if value.strip() else []
    return [str(value)]


def _derive_name_for_payload(input_row: ContactEnrichmentInput) -> Optional[Tuple[str, str]]:
    return derive_contact_name(
        first_name=input_row.first_name,
        last_name=input_row.last_name,
        email=input_row.email,
        linkedin_url=input_row.linkedin_url,
        custom=input_row.custom,
    )


def build_contact_payload_v1(
    input_row: ContactEnrichmentInput,
    default_fields: Optional[Iterable[str]] = None,
    *,
    api_version: Optional[str] = None,
    custom_context: Optional[Dict[str, Any]] = None,
) -> Optional[Dict[str, Any]]:
    if not is_valid_contact_input(
        input_row.email,
        input_row.linkedin_url,
        first_name=input_row.first_name,
        last_name=input_row.last_name,
        domain=input_row.domain,
        company_name=input_row.company_name,
    ):
        return None

    name = _derive_name_for_payload(input_row)
    first = name[0] if name else None
    last = name[1] if name else None
    if not name and not input_row.linkedin_url:
        return None

    payload: Dict[str, Any] = {
        "enrich_fields": list(input_row.enrich_fields or default_fields or DEFAULT_CONTACT_FIELDS),
    }

    if first and last:
        payload["firstname"] = first
        payload["lastname"] = last

    if input_row.domain:
        payload["domain"] = input_row.domain
    else:
        derived_domain = extract_domain_from_email(input_row.email)
        if derived_domain:
            payload["domain"] = derived_domain

    if input_row.company_name:
        payload["company_name"] = input_row.company_name
    if input_row.linkedin_url:
        payload["linkedin_url"] = input_row.linkedin_url

    merged_custom = {}
    merged_custom.update(input_row.custom or {})
    if custom_context:
        merged_custom.update(custom_context)
    custom_payload = build_custom_metadata(
        merged_custom,
        first_name=first,
        last_name=last,
        email=input_row.email,
        linkedin_url=input_row.linkedin_url,
        row_index=input_row.row_index,
        api_version=api_version,
    )
    if custom_payload:
        payload["custom"] = custom_payload

    return payload


def build_contact_payload_v2(
    input_row: ContactEnrichmentInput,
    default_fields: Optional[Iterable[str]] = None,
    *,
    api_version: Optional[str] = None,
    custom_context: Optional[Dict[str, Any]] = None,
) -> Optional[Dict[str, Any]]:
    if not is_valid_contact_input(
        input_row.email,
        input_row.linkedin_url,
        first_name=input_row.first_name,
        last_name=input_row.last_name,
        domain=input_row.domain,
        company_name=input_row.company_name,
    ):
        return None

    name = _derive_name_for_payload(input_row)
    first = name[0] if name else None
    last = name[1] if name else None
    if not name and not input_row.linkedin_url:
        return None

    payload: Dict[str, Any] = {
        "enrich_fields": list(input_row.enrich_fields or default_fields or DEFAULT_CONTACT_FIELDS),
    }

    if first and last:
        payload["first_name"] = first
        payload["last_name"] = last

    if input_row.domain:
        payload["domain"] = input_row.domain
    else:
        derived_domain = extract_domain_from_email(input_row.email)
        if derived_domain:
            payload["domain"] = derived_domain

    if input_row.company_name:
        payload["company_name"] = input_row.company_name
    if input_row.linkedin_url:
        payload["linkedin_url"] = input_row.linkedin_url

    merged_custom = {}
    merged_custom.update(input_row.custom or {})
    if custom_context:
        merged_custom.update(custom_context)
    custom_payload = build_custom_metadata(
        merged_custom,
        first_name=first,
        last_name=last,
        email=input_row.email,
        linkedin_url=input_row.linkedin_url,
        row_index=input_row.row_index,
        api_version=api_version,
    )
    if custom_payload:
        payload["custom"] = custom_payload

    return payload


def build_bulk_payload_v1(
    inputs: List[ContactEnrichmentInput],
    *,
    name: Optional[str] = None,
    webhook_url: Optional[str] = None,
    default_fields: Optional[Iterable[str]] = None,
    api_version: Optional[str] = None,
    custom_context: Optional[Dict[str, Any]] = None,
) -> Tuple[Dict[str, Any], List[ContactEnrichmentInput], List[ContactEnrichmentResult]]:
    payload: Dict[str, Any] = {
        "name": name or f"Bulk enrichment ({len(inputs)} contacts)",
        "datas": [],
    }
    if webhook_url:
        payload["webhook_url"] = webhook_url

    accepted: List[ContactEnrichmentInput] = []
    skipped: List[ContactEnrichmentResult] = []

    for input_row in inputs:
        if not is_valid_contact_input(
            input_row.email,
            input_row.linkedin_url,
            first_name=input_row.first_name,
            last_name=input_row.last_name,
            domain=input_row.domain,
            company_name=input_row.company_name,
        ):
            skipped.append(
                ContactEnrichmentResult(
                    row_index=input_row.row_index,
                    status=EnrichmentResultStatus.SKIPPED.value,
                    first_name=input_row.first_name,
                    last_name=input_row.last_name,
                    email=str(input_row.email).strip() if input_row.email else None,
                    linkedin_url=input_row.linkedin_url,
                    error_message="Invalid input: email, LinkedIn URL, or name + company required",
                    custom=input_row.custom,
                )
            )
            continue

        contact_payload = build_contact_payload_v1(
            input_row,
            default_fields=default_fields,
            api_version=api_version,
            custom_context=custom_context,
        )
        if not contact_payload:
            skipped.append(
                ContactEnrichmentResult(
                    row_index=input_row.row_index,
                    status=EnrichmentResultStatus.SKIPPED.value,
                    first_name=input_row.first_name,
                    last_name=input_row.last_name,
                    email=str(input_row.email).strip() if input_row.email else None,
                    linkedin_url=input_row.linkedin_url,
                    error_message="Unable to derive contact name from input",
                    custom=input_row.custom,
                )
            )
            continue

        payload["datas"].append(contact_payload)
        accepted.append(input_row)

    return payload, accepted, skipped


def build_bulk_payload_v2(
    inputs: List[ContactEnrichmentInput],
    *,
    name: Optional[str] = None,
    webhook_url: Optional[str] = None,
    default_fields: Optional[Iterable[str]] = None,
    api_version: Optional[str] = None,
    custom_context: Optional[Dict[str, Any]] = None,
) -> Tuple[Dict[str, Any], List[ContactEnrichmentInput], List[ContactEnrichmentResult]]:
    payload: Dict[str, Any] = {
        "name": name or f"Bulk enrichment ({len(inputs)} contacts)",
        "data": [],
    }
    if webhook_url:
        payload["webhook_url"] = webhook_url
        payload["webhook_events"] = {"contact_finished": True}

    accepted: List[ContactEnrichmentInput] = []
    skipped: List[ContactEnrichmentResult] = []

    for input_row in inputs:
        if not is_valid_contact_input(
            input_row.email,
            input_row.linkedin_url,
            first_name=input_row.first_name,
            last_name=input_row.last_name,
            domain=input_row.domain,
            company_name=input_row.company_name,
        ):
            skipped.append(
                ContactEnrichmentResult(
                    row_index=input_row.row_index,
                    status=EnrichmentResultStatus.SKIPPED.value,
                    first_name=input_row.first_name,
                    last_name=input_row.last_name,
                    email=str(input_row.email).strip() if input_row.email else None,
                    linkedin_url=input_row.linkedin_url,
                    error_message="Invalid input: email, LinkedIn URL, or name + company required",
                    custom=input_row.custom,
                )
            )
            continue

        contact_payload = build_contact_payload_v2(
            input_row,
            default_fields=default_fields,
            api_version=api_version,
            custom_context=custom_context,
        )
        if not contact_payload:
            skipped.append(
                ContactEnrichmentResult(
                    row_index=input_row.row_index,
                    status=EnrichmentResultStatus.SKIPPED.value,
                    first_name=input_row.first_name,
                    last_name=input_row.last_name,
                    email=str(input_row.email).strip() if input_row.email else None,
                    linkedin_url=input_row.linkedin_url,
                    error_message="Unable to derive contact name from input",
                    custom=input_row.custom,
                )
            )
            continue

        payload["data"].append(contact_payload)
        accepted.append(input_row)

    return payload, accepted, skipped


def build_bulk_payload(
    inputs: List[ContactEnrichmentInput],
    *,
    name: Optional[str] = None,
    webhook_url: Optional[str] = None,
    default_fields: Optional[Iterable[str]] = None,
    api_version: Optional[str] = None,
    custom_context: Optional[Dict[str, Any]] = None,
) -> Tuple[Dict[str, Any], List[ContactEnrichmentInput], List[ContactEnrichmentResult]]:
    version = fe_constants.normalize_api_version(api_version) if api_version else None
    if version is None:
        version = fe_constants.DEFAULT_API_VERSION
        logger.warning("FullEnrich api_version missing; defaulting to %s payload.", version.value)

    if version == FullEnrichAPIVersion.V2:
        return build_bulk_payload_v2(
            inputs,
            name=name,
            webhook_url=webhook_url,
            default_fields=default_fields,
            api_version=version.value,
            custom_context=custom_context,
        )

    return build_bulk_payload_v1(
        inputs,
        name=name,
        webhook_url=webhook_url,
        default_fields=default_fields,
        api_version=version.value,
        custom_context=custom_context,
    )


def _extract_probable_value(value: Any, key: str) -> Optional[str]:
    if isinstance(value, dict):
        candidate = value.get(key)
        if candidate and str(candidate).strip():
            return str(candidate).strip()
    if isinstance(value, str) and value.strip():
        return value.strip()
    return None


def extract_enrichment_id(payload: Dict[str, Any]) -> Optional[str]:
    if not isinstance(payload, dict):
        return None

    for key in ("enrichment_id", "id"):
        value = payload.get(key)
        if value is not None and str(value).strip():
            return str(value).strip()

    meta = payload.get("meta") if isinstance(payload.get("meta"), dict) else {}
    for key in ("enrichment_id", "id"):
        value = meta.get(key)
        if value is not None and str(value).strip():
            return str(value).strip()

    return None


def parse_contact_response_v1(
    raw: Dict[str, Any],
    fallback_input: Optional[ContactEnrichmentInput] = None,
) -> ContactEnrichmentResult:
    fallback = fallback_input or ContactEnrichmentInput()
    custom = raw.get("custom") or {}
    payload = raw.get("contact") if isinstance(raw.get("contact"), dict) else raw
    profile = payload.get("profile") if isinstance(payload.get("profile"), dict) else {}

    # Work emails (primary)
    emails = normalize_contact_list(payload.get("emails") or payload.get("email"))
    # Personal emails
    personal_emails = normalize_contact_list(payload.get("personal_emails") or payload.get("personal_email"))
    phones = normalize_contact_list(payload.get("phones") or payload.get("phone"))
    title = payload.get("title") or payload.get("job_title") or payload.get("position")
    location = payload.get("location")
    if not location:
        city = payload.get("city")
        state = payload.get("state")
        country = payload.get("country")
        location_parts = [p for p in [city, state, country] if p]
        if location_parts:
            location = ", ".join(location_parts)

    company_name = payload.get("company_name") or payload.get("company") or payload.get("organization")
    company_domain = payload.get("domain") or payload.get("company_domain") or payload.get("company_website")
    linkedin_url = payload.get("linkedin_url") or payload.get("linkedin") or fallback.linkedin_url
    first_name = (
        payload.get("firstname")
        or profile.get("firstname")
        or custom.get("first_name")
        or custom.get("firstname")
        or fallback.first_name
    )
    last_name = (
        payload.get("lastname")
        or profile.get("lastname")
        or custom.get("last_name")
        or custom.get("lastname")
        or fallback.last_name
    )

    email = emails[0] if emails else (str(custom.get("email")).strip() if custom.get("email") else None)
    if not email and fallback.email:
        email = str(fallback.email).strip()
    if not company_domain and email:
        company_domain = extract_domain_from_email(email)
    if not company_domain and fallback.email:
        company_domain = extract_domain_from_email(fallback.email)

    error_message = payload.get("error") or payload.get("error_message") or raw.get("error")
    status_raw = str(payload.get("status") or raw.get("status") or raw.get("state") or "").lower()
    has_data = bool(emails or personal_emails or phones or title or location or company_name or company_domain or linkedin_url)
    if error_message or status_raw in CONTACT_ERROR_STATUSES:
        status = EnrichmentResultStatus.ERROR.value
    elif status_raw in CONTACT_NOT_FOUND_STATUSES:
        status = EnrichmentResultStatus.NOT_FOUND.value
    elif has_data:
        status = EnrichmentResultStatus.ENRICHED.value
    else:
        status = EnrichmentResultStatus.NOT_FOUND.value

    return ContactEnrichmentResult(
        row_index=fallback.row_index,
        status=status,
        first_name=str(first_name).strip() if first_name else None,
        last_name=str(last_name).strip() if last_name else None,
        email=email,
        emails=emails,
        personal_email=personal_emails[0] if personal_emails else None,
        personal_emails=personal_emails,
        phone=phones[0] if phones else None,
        phones=phones,
        linkedin_url=linkedin_url,
        title=title,
        location=location,
        company_name=company_name,
        company_domain=company_domain,
        error_message=error_message,
        custom=custom or {},
        raw=raw,
    )


def parse_contact_response_v2(
    raw: Dict[str, Any],
    fallback_input: Optional[ContactEnrichmentInput] = None,
) -> ContactEnrichmentResult:
    fallback = fallback_input or ContactEnrichmentInput()
    custom = raw.get("custom") or {}
    input_payload = raw.get("input") if isinstance(raw.get("input"), dict) else {}
    contact_info = raw.get("contact_info") if isinstance(raw.get("contact_info"), dict) else {}
    profile = raw.get("profile") if isinstance(raw.get("profile"), dict) else {}

    work_emails = normalize_contact_list(contact_info.get("work_emails"))
    personal_emails = normalize_contact_list(contact_info.get("personal_emails"))
    phones = normalize_contact_list(contact_info.get("phones"))

    if not work_emails:
        probable = _extract_probable_value(contact_info.get("most_probable_work_email"), "email")
        if probable:
            work_emails = [probable]
    if not personal_emails:
        probable = _extract_probable_value(contact_info.get("most_probable_personal_email"), "email")
        if probable:
            personal_emails = [probable]
    if not phones:
        probable = _extract_probable_value(contact_info.get("most_probable_phone"), "number")
        if probable:
            phones = [probable]

    email = work_emails[0] if work_emails else None
    personal_email = personal_emails[0] if personal_emails else None
    phone = phones[0] if phones else None

    first_name = (
        input_payload.get("first_name")
        or custom.get("first_name")
        or custom.get("firstname")
        or fallback.first_name
    )
    last_name = (
        input_payload.get("last_name")
        or custom.get("last_name")
        or custom.get("lastname")
        or fallback.last_name
    )
    linkedin_url = (
        input_payload.get("linkedin_url")
        or custom.get("linkedin_url")
        or fallback.linkedin_url
    )
    company_name = input_payload.get("company_name") or custom.get("company_name") or fallback.company_name
    company_domain = input_payload.get("company_domain") or input_payload.get("domain") or fallback.domain

    title = profile.get("job_title") or profile.get("title") or None
    location = profile.get("location")

    error_message = raw.get("error") or raw.get("error_message")
    has_data = bool(work_emails or personal_emails or phones or title or location or company_name or company_domain or linkedin_url)
    if error_message:
        status = EnrichmentResultStatus.ERROR.value
    elif has_data:
        status = EnrichmentResultStatus.ENRICHED.value
    else:
        status = EnrichmentResultStatus.NOT_FOUND.value

    return ContactEnrichmentResult(
        row_index=fallback.row_index,
        status=status,
        first_name=str(first_name).strip() if first_name else None,
        last_name=str(last_name).strip() if last_name else None,
        email=email,
        emails=work_emails,
        personal_email=personal_email,
        personal_emails=personal_emails,
        phone=phone,
        phones=phones,
        linkedin_url=linkedin_url,
        title=title,
        location=location,
        company_name=company_name,
        company_domain=company_domain,
        error_message=error_message,
        custom=custom or {},
        raw=raw,
    )


def parse_bulk_response_v1(
    data: Dict[str, Any],
    inputs: Optional[List[ContactEnrichmentInput]] = None,
) -> List[ContactEnrichmentResult]:
    raw_contacts = data.get("datas") or []
    inputs = inputs or []
    by_index: Dict[int, ContactEnrichmentInput] = {
        inp.row_index: inp for inp in inputs if inp.row_index is not None
    }

    results: Dict[int, ContactEnrichmentResult] = {}

    for idx, raw in enumerate(raw_contacts):
        custom = raw.get("custom") or {}
        row_index = custom.get("row_index")
        try:
            row_index = int(row_index) if row_index is not None else None
        except (TypeError, ValueError):
            row_index = None
        fallback = None
        if row_index is not None:
            fallback = by_index.get(row_index)
        if fallback is None and idx < len(inputs):
            fallback = inputs[idx]

        parsed = parse_contact_response_v1(raw, fallback)
        if parsed.row_index is None and row_index is not None:
            parsed.row_index = row_index
        if parsed.row_index is None:
            parsed.row_index = idx
        results[parsed.row_index] = parsed

    if inputs:
        for inp in inputs:
            if inp.row_index is None:
                continue
            if inp.row_index not in results:
                results[inp.row_index] = ContactEnrichmentResult(
                    row_index=inp.row_index,
                    status=EnrichmentResultStatus.NOT_FOUND.value,
                    first_name=inp.first_name,
                    last_name=inp.last_name,
                    email=str(inp.email).strip() if inp.email else None,
                    linkedin_url=inp.linkedin_url,
                    custom=inp.custom,
                )

        ordered = [results[inp.row_index] for inp in inputs if inp.row_index in results]
        return ordered

    return list(results.values())


def parse_bulk_response_v2(
    data: Dict[str, Any],
    inputs: Optional[List[ContactEnrichmentInput]] = None,
) -> List[ContactEnrichmentResult]:
    raw_contacts = data.get("data") or []
    inputs = inputs or []
    by_index: Dict[int, ContactEnrichmentInput] = {
        inp.row_index: inp for inp in inputs if inp.row_index is not None
    }

    results: Dict[int, ContactEnrichmentResult] = {}

    for idx, raw in enumerate(raw_contacts):
        custom = raw.get("custom") or {}
        row_index = custom.get("row_index")
        try:
            row_index = int(row_index) if row_index is not None else None
        except (TypeError, ValueError):
            row_index = None
        fallback = None
        if row_index is not None:
            fallback = by_index.get(row_index)
        if fallback is None and idx < len(inputs):
            fallback = inputs[idx]

        parsed = parse_contact_response_v2(raw, fallback)
        if parsed.row_index is None and row_index is not None:
            parsed.row_index = row_index
        if parsed.row_index is None:
            parsed.row_index = idx
        results[parsed.row_index] = parsed

    if inputs:
        for inp in inputs:
            if inp.row_index is None:
                continue
            if inp.row_index not in results:
                results[inp.row_index] = ContactEnrichmentResult(
                    row_index=inp.row_index,
                    status=EnrichmentResultStatus.NOT_FOUND.value,
                    first_name=inp.first_name,
                    last_name=inp.last_name,
                    email=str(inp.email).strip() if inp.email else None,
                    linkedin_url=inp.linkedin_url,
                    custom=inp.custom,
                )

        ordered = [results[inp.row_index] for inp in inputs if inp.row_index in results]
        return ordered

    return list(results.values())


def parse_bulk_response(
    data: Dict[str, Any],
    inputs: Optional[List[ContactEnrichmentInput]] = None,
    api_version: Optional[str] = None,
) -> List[ContactEnrichmentResult]:
    version = fe_constants.normalize_api_version(api_version) if api_version else None
    if version is None:
        version = fe_constants.DEFAULT_API_VERSION
        logger.warning("FullEnrich api_version missing; defaulting to %s parser.", version.value)

    if version == FullEnrichAPIVersion.V2:
        return parse_bulk_response_v2(data, inputs=inputs)

    return parse_bulk_response_v1(data, inputs=inputs)


# Backward-compatible names
build_contact_payload = build_contact_payload_v1
parse_contact_response = parse_contact_response_v1
