from __future__ import annotations

import json
import logging
import os
import time
from typing import Any, Dict, Iterable, List, Optional, TYPE_CHECKING, Callable

from ..core.psl_root import registrable_root
from .cache import make_cache_key

if TYPE_CHECKING:
    from .cache import HybridCache
    from .salesforce_gateway import SalesforceGateway
    from .gremlin_intent_router import ResolutionTarget

logger = logging.getLogger(__name__)

_bq_client = None

_SFDC_IN_MAX = 200
_SFDC_TIMEOUT_SEC = 30
_SFDC_FIELDS = [
    "Id",
    "Name",
    "Website",
    "BillingCity",
    "BillingState",
    "AnnualRevenue",
]


def _get_bq_client():
    global _bq_client
    if _bq_client is None:
        from google.cloud import bigquery

        project_id = os.getenv("FM_FOUNDRYGRAPH_PROJECT_ID") or os.getenv(
            "GOOGLE_CLOUD_PROJECT"
        )
        _bq_client = bigquery.Client(project=project_id) if project_id else bigquery.Client()
    return _bq_client


def _table_ref(dataset: str, table: str) -> str:
    project_id = os.getenv("FM_FOUNDRYGRAPH_PROJECT_ID") or os.getenv(
        "GOOGLE_CLOUD_PROJECT"
    )
    if project_id:
        return f"{project_id}.{dataset}.{table}"
    return f"{dataset}.{table}"


def _extract_exchange(qualifier: Any) -> str:
    if not qualifier:
        return ""
    if isinstance(qualifier, str):
        try:
            qualifier = json.loads(qualifier)
        except Exception:
            return ""
    if not isinstance(qualifier, dict):
        return ""
    for key in ("exchange", "stock_exchange", "market", "primary_exchange"):
        value = qualifier.get(key)
        if value:
            return str(value)
    if len(qualifier) == 1:
        return str(next(iter(qualifier.values())))
    return ""


def _chunk_list(values: List[str], size: int) -> Iterable[List[str]]:
    for start in range(0, len(values), size):
        yield values[start : start + size]


def _escape_soql_literal(value: str) -> str:
    return str(value).replace("\\", "\\\\").replace("'", "\\'")


def _normalize_domain_value(value: str) -> str:
    if not value:
        return ""
    s = value.strip().lower()
    if "://" in s:
        s = s.split("://", 1)[1]
    s = s.split("/")[0]
    if s.startswith("www."):
        s = s[4:]
    root = registrable_root(s)
    return root or s


def _looks_like_domain_value(value: str) -> bool:
    if not value:
        return False
    lowered = value.strip().lower()
    if "://" in lowered or "/" in lowered:
        return True
    if "@" in lowered:
        return True
    return "." in lowered


def _website_variants(domain: str) -> List[str]:
    if not domain:
        return []
    base = domain.strip().lower()
    if base.startswith("http://") or base.startswith("https://"):
        base = _normalize_domain_value(base)
    variants = {base}
    if not base.startswith("www."):
        variants.add(f"www.{base}")
    for val in list(variants):
        variants.add(f"http://{val}")
        variants.add(f"https://{val}")
    return sorted(variants)


def _build_hq(city: Optional[str], state: Optional[str]) -> str:
    city = (city or "").strip()
    state = (state or "").strip()
    if city and state:
        return f"{city}, {state}"
    return city or state or ""


def _safe_json_list(value: Any) -> List[Any]:
    if not value or (isinstance(value, str) and not value.strip()):
        return []
    if isinstance(value, list):
        return value
    if isinstance(value, str):
        try:
            return json.loads(value)
        except (json.JSONDecodeError, TypeError):
            return []
    return []


def _safe_json_dict(value: Any) -> Optional[Dict[str, Any]]:
    if not value or (isinstance(value, str) and not value.strip()):
        return None
    if isinstance(value, dict):
        return value
    if isinstance(value, str):
        try:
            parsed = json.loads(value)
        except (json.JSONDecodeError, TypeError):
            return None
        return parsed if isinstance(parsed, dict) else None
    return None


def _extract_labels(items: List[Any]) -> List[str]:
    labels: List[str] = []
    for item in items:
        if isinstance(item, dict):
            label = item.get("label")
            if label:
                labels.append(str(label))
        elif isinstance(item, str):
            labels.append(item)
    return labels


def _coerce_number(value: Any) -> Optional[float]:
    if value is None:
        return None
    if isinstance(value, (int, float)):
        return float(value)
    if isinstance(value, str):
        cleaned = value.replace(",", "").replace("$", "").strip()
        if not cleaned:
            return None
        try:
            return float(cleaned)
        except ValueError:
            return None
    return None


def _flatten_enrich_result(obj: Dict[str, Any]) -> List[Any]:
    return [
        obj.get("canonical_name", ""),
        obj.get("domain", ""),
        obj.get("wikidata_id", ""),
        obj.get("industries", ""),
        obj.get("headquarters", ""),
        obj.get("country", ""),
        obj.get("revenue_amount", ""),
        obj.get("revenue_currency", ""),
        obj.get("revenue_year", ""),
        obj.get("employee_count", ""),
        obj.get("stock_ticker", ""),
        obj.get("stock_exchange", ""),
        obj.get("parent_company", ""),
        obj.get("parent_domain", ""),
        obj.get("twitter_handle", ""),
        obj.get("youtube_channel", ""),
    ]


async def _sfdc_bulk_query(
    gateway: "SalesforceGateway",
    field: str,
    values: List[str],
    *,
    timeout_at: Optional[float] = None,
) -> List[Dict[str, Any]]:
    if not gateway or not values:
        return []

    field = field.strip()
    records: List[Dict[str, Any]] = []
    for chunk in _chunk_list(values, _SFDC_IN_MAX):
        if timeout_at and time.monotonic() > timeout_at:
            break
        escaped = [_escape_soql_literal(val) for val in chunk if val]
        if not escaped:
            continue
        in_clause = ", ".join(f"'{val}'" for val in escaped)
        limit = len(escaped)
        fields_sql = ", ".join(_SFDC_FIELDS)
        soql = f"SELECT {fields_sql} FROM Account WHERE {field} IN ({in_clause}) LIMIT {limit}"
        try:
            result = await gateway.soql(soql)
            records.extend(result.get("records", []) if isinstance(result, dict) else [])
            continue
        except Exception as exc:
            try:
                from .salesforce_gateway import SalesforceFieldAccessError
            except Exception:
                SalesforceFieldAccessError = None  # type: ignore
            if SalesforceFieldAccessError and isinstance(exc, SalesforceFieldAccessError):
                invalid = {f.lower() for f in exc.invalid_fields}
                safe_fields = [f for f in _SFDC_FIELDS if f.lower() not in invalid]
                if safe_fields and safe_fields != _SFDC_FIELDS:
                    fields_sql = ", ".join(safe_fields)
                    soql = f"SELECT {fields_sql} FROM Account WHERE {field} IN ({in_clause}) LIMIT {limit}"
                    result = await gateway.soql(soql, fls_guard=False)
                    records.extend(result.get("records", []) if isinstance(result, dict) else [])
                    continue
            raise
    return records


async def _fetch_sfdc_accounts_by_name(
    gateway: "SalesforceGateway",
    names: List[str],
    *,
    timeout_at: Optional[float] = None,
) -> Dict[str, Dict[str, Any]]:
    unique = []
    seen = set()
    for name in names:
        key = (name or "").strip()
        if not key:
            continue
        lowered = key.casefold()
        if lowered in seen:
            continue
        seen.add(lowered)
        unique.append(key)

    if not unique:
        return {}

    records = await _sfdc_bulk_query(gateway, "Name", unique, timeout_at=timeout_at)
    results: Dict[str, Dict[str, Any]] = {}
    for record in records:
        name = (record.get("Name") or "").strip()
        if not name:
            continue
        results[name.casefold()] = record
    return results


async def _fetch_sfdc_accounts_by_website(
    gateway: "SalesforceGateway",
    domains: List[str],
    *,
    timeout_at: Optional[float] = None,
) -> Dict[str, Dict[str, Any]]:
    unique_domains = []
    seen_domains = set()
    for domain in domains:
        normalized = _normalize_domain_value(domain)
        if not normalized:
            continue
        if normalized in seen_domains:
            continue
        seen_domains.add(normalized)
        unique_domains.append(normalized)

    if not unique_domains:
        return {}

    variants: List[str] = []
    for domain in unique_domains:
        variants.extend(_website_variants(domain))

    if not variants:
        return {}

    records = await _sfdc_bulk_query(gateway, "Website", variants, timeout_at=timeout_at)
    results: Dict[str, Dict[str, Any]] = {}
    for record in records:
        website = record.get("Website") or ""
        normalized = _normalize_domain_value(website)
        if normalized and normalized not in results:
            results[normalized] = record
    return results


async def _batch_enrich_from_foundrygraph(
    values: List[Any],
    params: Dict[str, Any],
    *,
    sfdc_gateway: Optional["SalesforceGateway"] = None,
    resolution_target: Optional["ResolutionTarget"] = None,
    sfdc_unavailable: bool = False,
    sfdc_error_reason: Optional[str] = None,
    cache: Optional["HybridCache"] = None,
    canon_func: Optional[Callable[[str, str, Dict[str, Any]], str]] = None,
) -> Dict[str, Any]:
    op_key = "enrich_from_foundrygraph"
    data: List[Any] = [None] * len(values)
    cache_hits = 0
    target_value = getattr(resolution_target, "value", resolution_target) or "smart"
    target_value = str(target_value).lower()

    timeout_at = time.monotonic() + _SFDC_TIMEOUT_SEC
    timed_out = False
    sfdc_matches = 0
    fg_matches = 0
    no_matches = 0

    input_type_param = params.get("input_type") or params.get("inputType") or "domain"

    def _effective_input_type(raw_type: Any, raw_value: str) -> str:
        candidate = (raw_type or input_type_param or "").strip().lower()
        if candidate in ("domain", "company_name"):
            return candidate
        return "domain" if _looks_like_domain_value(raw_value) else "company_name"

    def _blank_fg_row(domain: str = "") -> Dict[str, Any]:
        return {
            "canonical_name": "",
            "domain": domain or "",
            "wikidata_id": "",
            "industries": "",
            "headquarters": "",
            "country": "",
            "revenue_amount": "",
            "revenue_currency": "",
            "revenue_year": "",
            "employee_count": "",
            "stock_ticker": "",
            "stock_exchange": "",
            "parent_company": "",
            "parent_domain": "",
            "twitter_handle": "",
            "youtube_channel": "",
        }

    def _build_sfdc_result(record: Dict[str, Any], fallback_domain: str) -> Dict[str, Any]:
        website = record.get("Website") or fallback_domain
        domain = _normalize_domain_value(website)
        result = _blank_fg_row(domain or fallback_domain)
        result["canonical_name"] = record.get("Name") or ""
        result["domain"] = domain or fallback_domain or ""
        hq = _build_hq(record.get("BillingCity"), record.get("BillingState"))
        if hq:
            result["headquarters"] = hq
        revenue = _coerce_number(record.get("AnnualRevenue"))
        if revenue is not None:
            result["revenue_amount"] = revenue
        result["sfdc_account_id"] = record.get("Id")
        result["source"] = "salesforce"
        return result

    missing_entries: List[Dict[str, Any]] = []
    cache_keys: Dict[int, str] = {}

    for idx, raw in enumerate(values):
        raw_value = ""
        raw_input_type = None
        if isinstance(raw, dict):
            raw_value = (
                raw.get("value")
                or raw.get("domain")
                or raw.get("company")
                or raw.get("website")
                or ""
            )
            raw_input_type = raw.get("input_type") or raw.get("inputType")
        else:
            raw_value = "" if raw is None else str(raw)

        raw_value = raw_value.strip()
        if not raw_value:
            data[idx] = _flatten_enrich_result(_blank_fg_row())
            continue

        effective_type = _effective_input_type(raw_input_type, raw_value)
        canon = canon_func(op_key, raw_value, params) if canon_func else raw_value
        cache_key = make_cache_key(
            "transform",
            {
                "op": op_key,
                "value": canon,
                "params": params,
                "input_type": effective_type,
                "resolution_target": target_value,
            },
        )
        cached = await cache.get(cache_key) if cache else None
        if cached is not None:
            data[idx] = cached
            cache_hits += 1
        else:
            missing_entries.append(
                {"idx": idx, "value": raw_value, "input_type": effective_type}
            )
            cache_keys[idx] = cache_key

    if not missing_entries:
        meta_out = {
            "provider": "foundrygraph",
            "model": "",
            "cache_hits": cache_hits,
            "total_rows": len(values),
            "op_name": op_key,
            "resolution_target": target_value,
        }
        if len(values):
            meta_out["cache_hit_rate"] = round(cache_hits / len(values), 3)
        return {"data": data, "meta": meta_out}

    row_domains: Dict[int, str] = {}
    row_outputs: Dict[int, Dict[str, Any]] = {}
    name_entries: List[Dict[str, Any]] = []

    for entry in missing_entries:
        idx = entry["idx"]
        if entry["input_type"] == "domain":
            domain = _normalize_domain_value(entry["value"])
            if domain:
                row_domains[idx] = domain
            else:
                row_outputs[idx] = _blank_fg_row()
        else:
            name_entries.append(entry)

    if name_entries and target_value != "salesforce" and time.monotonic() <= timeout_at:
        try:
            from ..operators.company_domain_resolver import resolve_company_domains
        except Exception as exc:
            logger.debug("enrich_from_foundrygraph resolver unavailable: %s", exc)
            resolve_company_domains = None  # type: ignore

        if resolve_company_domains:
            use_registry = (
                os.getenv("FM_COMPANY_TO_DOMAIN_REGISTRY", "true").lower() == "true"
            )
            names = [entry["value"] for entry in name_entries]
            resolved_offset = 0
            for chunk in _chunk_list(names, 500):
                if time.monotonic() > timeout_at:
                    timed_out = True
                    break
                try:
                    resolutions, _ = resolve_company_domains(
                        chunk, mode="balanced", use_llm=False, use_registry=use_registry
                    )
                except Exception as exc:
                    logger.debug("enrich_from_foundrygraph resolver batch failed: %s", exc)
                    resolutions = []

                for local_idx, resolution in enumerate(resolutions):
                    entry_idx = resolved_offset + local_idx
                    if entry_idx >= len(name_entries):
                        break
                    global_idx = name_entries[entry_idx]["idx"]
                    value = resolution.get("value") if isinstance(resolution, dict) else {}
                    value = value if isinstance(value, dict) else {}
                    domain = value.get("domain") or ""
                    confidence = (value.get("confidence") or "").lower()
                    if domain and confidence != "low":
                        row_domains[global_idx] = _normalize_domain_value(domain)
                    else:
                        row_outputs[global_idx] = _blank_fg_row()
                resolved_offset += len(chunk)

    if (
        not timed_out
        and target_value in ("smart", "salesforce")
        and sfdc_gateway
        and time.monotonic() <= timeout_at
    ):
        name_lookup = [
            entry
            for entry in name_entries
            if entry["idx"] not in row_outputs
        ]
        domain_lookup = [
            entry
            for entry in missing_entries
            if entry["input_type"] == "domain" and entry["idx"] not in row_outputs
        ]

        if name_lookup and time.monotonic() <= timeout_at:
            try:
                sfdc_by_name = await _fetch_sfdc_accounts_by_name(
                    sfdc_gateway,
                    [entry["value"] for entry in name_lookup],
                    timeout_at=timeout_at,
                )
            except Exception as exc:
                sfdc_unavailable = True
                sfdc_error_reason = sfdc_error_reason or str(exc)[:200]
                sfdc_by_name = {}
        else:
            sfdc_by_name = {}

        if domain_lookup and time.monotonic() <= timeout_at:
            try:
                sfdc_by_domain = await _fetch_sfdc_accounts_by_website(
                    sfdc_gateway,
                    [row_domains.get(entry["idx"], entry["value"]) for entry in domain_lookup],
                    timeout_at=timeout_at,
                )
            except Exception as exc:
                sfdc_unavailable = True
                sfdc_error_reason = sfdc_error_reason or str(exc)[:200]
                sfdc_by_domain = {}
        else:
            sfdc_by_domain = {}

        for entry in name_lookup:
            record = sfdc_by_name.get(entry["value"].casefold())
            if record:
                idx = entry["idx"]
                row_outputs[idx] = _build_sfdc_result(record, row_domains.get(idx, ""))
                sfdc_matches += 1

        for entry in domain_lookup:
            idx = entry["idx"]
            domain = row_domains.get(idx) or _normalize_domain_value(entry["value"])
            if not domain:
                continue
            record = sfdc_by_domain.get(domain)
            if record:
                row_outputs[idx] = _build_sfdc_result(record, domain)
                sfdc_matches += 1

    if time.monotonic() > timeout_at:
        timed_out = True

    if target_value == "salesforce":
        for entry in missing_entries:
            idx = entry["idx"]
            if idx not in row_outputs:
                row_outputs[idx] = _blank_fg_row()
                no_matches += 1
        timed_out = timed_out or time.monotonic() > timeout_at
    else:
        if not timed_out:
            domains_to_fetch: Dict[str, List[int]] = {}
            for entry in missing_entries:
                idx = entry["idx"]
                if idx in row_outputs:
                    continue
                domain = row_domains.get(idx)
                if not domain:
                    row_outputs[idx] = _blank_fg_row()
                    no_matches += 1
                    continue
                domains_to_fetch.setdefault(domain, []).append(idx)

            if domains_to_fetch:
                try:
                    client = _get_bq_client()
                except Exception as exc:
                    logger.debug("FoundryGraph BigQuery unavailable: %s", exc)
                    client = None

                if client is None:
                    for domain, indices in domains_to_fetch.items():
                        for idx in indices:
                            row_outputs[idx] = _blank_fg_row(domain)
                            no_matches += 1
                else:
                    profile_map: Dict[str, Dict[str, Any]] = {}
                    domains_list = list(domains_to_fetch.keys())
                    companies_table = _table_ref("foundrygraph_gold", "companies_gold")
                    domain_lookup_table = _table_ref("foundrygraph_gold", "domain_lookup")
                    external_ids_table = _table_ref("foundrygraph_silver", "external_ids")

                    from google.cloud import bigquery

                    for chunk in _chunk_list(domains_list, 500):
                        if time.monotonic() > timeout_at:
                            timed_out = True
                            break
                        query = f"""
                            SELECT c.wikidata_id, c.label, c.domain, c.industries, c.countries,
                                   c.headquarters, c.revenue, c.employees,
                                   p.label AS parent_label, p.domain AS parent_domain
                            FROM `{companies_table}` c
                            LEFT JOIN `{companies_table}` p
                              ON c.parent_org = p.wikidata_id
                            WHERE c.domain IN UNNEST(@domains)
                        """
                        job_config = bigquery.QueryJobConfig(
                            query_parameters=[
                                bigquery.ArrayQueryParameter("domains", "STRING", chunk)
                            ]
                        )
                        try:
                            rows = list(client.query(query, job_config=job_config).result())
                        except Exception as exc:
                            logger.debug("FoundryGraph batch query failed: %s", exc)
                            try:
                                fallback_query = f"""
                                    SELECT wikidata_id, label, domain, industries, countries,
                                           headquarters, revenue, employees
                                    FROM `{companies_table}`
                                    WHERE domain IN UNNEST(@domains)
                                """
                                rows = list(
                                    client.query(fallback_query, job_config=job_config).result()
                                )
                            except Exception as retry_exc:
                                logger.debug(
                                    "FoundryGraph batch query fallback failed: %s", retry_exc
                                )
                                rows = []

                        for row in rows:
                            profile_domain = getattr(row, "domain", "") or ""
                            domain = _normalize_domain_value(profile_domain)
                            if not domain or domain in profile_map:
                                continue
                            result = _blank_fg_row(domain)
                            result["canonical_name"] = getattr(row, "label", "") or ""
                            result["wikidata_id"] = getattr(row, "wikidata_id", "") or ""
                            if domain:
                                result["domain"] = domain

                            industry_labels = _extract_labels(
                                _safe_json_list(getattr(row, "industries", None))
                            )
                            if industry_labels:
                                result["industries"] = ", ".join(industry_labels)

                            hq_labels = _extract_labels(
                                _safe_json_list(getattr(row, "headquarters", None))
                            )
                            if hq_labels:
                                result["headquarters"] = hq_labels[0]

                            country_labels = _extract_labels(
                                _safe_json_list(getattr(row, "countries", None))
                            )
                            if country_labels:
                                result["country"] = country_labels[0]

                            revenue_payload = _safe_json_dict(getattr(row, "revenue", None)) or {}
                            amount = _coerce_number(
                                revenue_payload.get("amount") or revenue_payload.get("value")
                            )
                            if amount is not None:
                                result["revenue_amount"] = amount
                            currency = str(
                                revenue_payload.get("currency")
                                or revenue_payload.get("currency_code")
                                or ""
                            ).strip()
                            if currency:
                                result["revenue_currency"] = currency
                            year = str(
                                revenue_payload.get("year")
                                or revenue_payload.get("as_of")
                                or ""
                            ).strip()
                            if year:
                                result["revenue_year"] = year

                            employees_payload = _safe_json_dict(getattr(row, "employees", None)) or {}
                            employee_count = _coerce_number(
                                employees_payload.get("count") or employees_payload.get("value")
                            )
                            if employee_count is not None:
                                result["employee_count"] = int(employee_count)

                            parent_label = getattr(row, "parent_label", None)
                            parent_domain = getattr(row, "parent_domain", None)
                            if parent_label:
                                result["parent_company"] = parent_label
                            if parent_domain:
                                result["parent_domain"] = _normalize_domain_value(parent_domain)

                            profile_map[domain] = result

                    missing_domains = [
                        domain for domain in domains_list if domain not in profile_map
                    ]
                    if missing_domains and not timed_out:
                        for chunk in _chunk_list(missing_domains, 500):
                            if time.monotonic() > timeout_at:
                                timed_out = True
                                break
                            query = f"""
                                SELECT domain, wikidata_id, label, primary_domain
                                FROM `{domain_lookup_table}`
                                WHERE domain IN UNNEST(@domains)
                            """
                            job_config = bigquery.QueryJobConfig(
                                query_parameters=[
                                    bigquery.ArrayQueryParameter("domains", "STRING", chunk)
                                ]
                            )
                            try:
                                rows = list(client.query(query, job_config=job_config).result())
                            except Exception as exc:
                                logger.debug("FoundryGraph domain lookup batch failed: %s", exc)
                                rows = []

                            for row in rows:
                                domain = _normalize_domain_value(getattr(row, "domain", "") or "")
                                if not domain or domain in profile_map:
                                    continue
                                result = _blank_fg_row(domain)
                                result["wikidata_id"] = getattr(row, "wikidata_id", "") or ""
                                result["canonical_name"] = getattr(row, "label", "") or ""
                                primary_domain = getattr(row, "primary_domain", "") or ""
                                if primary_domain:
                                    result["domain"] = _normalize_domain_value(primary_domain)
                                profile_map[domain] = result

                    if not timed_out and profile_map:
                        wikidata_ids = sorted(
                            {
                                result.get("wikidata_id")
                                for result in profile_map.values()
                                if result.get("wikidata_id")
                            }
                        )

                        external_map: Dict[str, Dict[str, str]] = {}
                        if wikidata_ids:
                            for chunk in _chunk_list(wikidata_ids, 500):
                                if time.monotonic() > timeout_at:
                                    timed_out = True
                                    break
                                query = f"""
                                    SELECT wikidata_id, id_type, id_value, qualifier
                                    FROM `{external_ids_table}`
                                    WHERE wikidata_id IN UNNEST(@ids)
                                      AND id_type IN ('ticker', 'stock_exchange', 'twitter_handle', 'youtube_channel_id')
                                """
                                job_config = bigquery.QueryJobConfig(
                                    query_parameters=[
                                        bigquery.ArrayQueryParameter("ids", "STRING", chunk)
                                    ]
                                )
                                try:
                                    rows = list(client.query(query, job_config=job_config).result())
                                except Exception as exc:
                                    logger.debug("FoundryGraph external IDs batch failed: %s", exc)
                                    rows = []

                                for row in rows:
                                    wikidata_id = getattr(row, "wikidata_id", "") or ""
                                    id_type = getattr(row, "id_type", "") or ""
                                    id_value = getattr(row, "id_value", "") or ""
                                    qualifier = getattr(row, "qualifier", None)
                                    if not wikidata_id:
                                        continue
                                    payload = external_map.setdefault(wikidata_id, {})
                                    if id_type == "ticker":
                                        if id_value and not payload.get("stock_ticker"):
                                            payload["stock_ticker"] = id_value
                                        exchange = _extract_exchange(qualifier)
                                        if exchange and not payload.get("stock_exchange"):
                                            payload["stock_exchange"] = exchange
                                    elif id_type == "stock_exchange":
                                        if id_value and not payload.get("stock_exchange"):
                                            payload["stock_exchange"] = id_value
                                    elif id_type == "twitter_handle":
                                        handle = id_value or ""
                                        if handle and not handle.startswith("@"):
                                            handle = "@" + handle
                                        payload["twitter_handle"] = handle
                                    elif id_type == "youtube_channel_id":
                                        if id_value:
                                            payload["youtube_channel"] = id_value

                        for domain, result in profile_map.items():
                            if timed_out:
                                break
                            wikidata_id = result.get("wikidata_id") or ""
                            if wikidata_id and wikidata_id in external_map:
                                result.update(external_map[wikidata_id])

                    for domain, indices in domains_to_fetch.items():
                        result_obj = profile_map.get(domain) or _blank_fg_row(domain)
                        for idx in indices:
                            row_outputs[idx] = result_obj
                            if result_obj.get("canonical_name") or result_obj.get("wikidata_id"):
                                fg_matches += 1
                            else:
                                no_matches += 1

    processed_rows = 0
    for entry in missing_entries:
        idx = entry["idx"]
        result_obj = row_outputs.get(idx)
        if result_obj is None:
            domain = row_domains.get(idx, "")
            result_obj = _blank_fg_row(domain)
            row_outputs[idx] = result_obj
            no_matches += 1
        flat = _flatten_enrich_result(result_obj)
        data[idx] = flat
        if result_obj.get("canonical_name") or result_obj.get("wikidata_id"):
            processed_rows += 1
        cache_key = cache_keys.get(idx)
        if cache_key and cache:
            await cache.set(cache_key, flat)

    meta_out = {
        "provider": "foundrygraph" if target_value != "salesforce" else "salesforce",
        "model": "",
        "cache_hits": cache_hits,
        "total_rows": len(values),
        "op_name": op_key,
        "resolution_target": target_value,
        "sfdc_matches": sfdc_matches,
        "foundrygraph_matches": fg_matches,
        "no_matches": no_matches,
        "timed_out": timed_out,
        "processed_rows": processed_rows,
    }

    if len(values) > 0:
        meta_out["cache_hit_rate"] = round(cache_hits / len(values), 3)

    if sfdc_unavailable:
        meta_out["sfdc_unavailable"] = True
        meta_out["sfdc_error_reason"] = sfdc_error_reason

    return {"data": data, "meta": meta_out}
