"""FoundryGraph Lookup API."""

from __future__ import annotations

import json
import logging
import os
from collections import defaultdict
from datetime import date
from typing import Any, Dict, List, Optional, Tuple, Literal

from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from typing import TYPE_CHECKING

# Lazy import google.cloud.bigquery to avoid GCP credential discovery hang at startup
if TYPE_CHECKING:
    pass

_bq_client = None

def _get_bq_client():
    """Lazily import and create BigQuery client to avoid startup hang."""
    global _bq_client
    if _bq_client is None:
        from google.cloud import bigquery
        _bq_client = bigquery.Client(project=PROJECT_ID)
    return _bq_client

from ...auth_security import require_account

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v2/foundrygraph", tags=["foundrygraph"])


# ---------------------------------------------------------------------------
# Thin Rate Limiting (v1 - in-memory, resets daily)
# TODO: Move to Redis/DB for persistence across restarts
# ---------------------------------------------------------------------------
RATE_LIMITS = {
    "trial": {"daily": 500, "per_request": 200},
    "paid": {"daily": 10000, "per_request": 1000},
    "default": {"daily": 100, "per_request": 50},  # Fallback for unrecognized tiers
}

# In-memory counters: {(account_id, date_str): count}
_daily_counters: Dict[Tuple[str, str], int] = defaultdict(int)


def _get_tier_for_account(account_id: str) -> str:
    """
    Determine tier for rate limiting.
    TODO: Look up actual tier from agentLicenses table or JWT claims.
    For now, defaults to 'trial' for all accounts.
    """
    # Placeholder - in production, check agentLicenses table or decode JWT
    return "trial"


def _check_rate_limit(account_id: str) -> None:
    """
    Check and increment rate limit for account.
    Raises HTTPException 429 if limit exceeded.
    """
    tier = _get_tier_for_account(account_id)
    limits = RATE_LIMITS.get(tier, RATE_LIMITS["default"])
    daily_limit = limits["daily"]

    today = date.today().isoformat()
    key = (account_id, today)

    current_count = _daily_counters[key]
    if current_count >= daily_limit:
        logger.warning(f"Rate limit exceeded for {account_id}: {current_count}/{daily_limit}")
        raise HTTPException(
            status_code=429,
            detail={
                "error": "RATE_LIMIT_EXCEEDED",
                "message": f"Daily limit of {daily_limit} lookups reached. Resets at midnight UTC.",
                "tier": tier,
                "limit": daily_limit,
                "used": current_count,
            },
        )

    _daily_counters[key] += 1
    logger.info(f"FoundryGraph lookup by {account_id}: {current_count + 1}/{daily_limit} today")

PROJECT_ID = os.getenv("FM_FOUNDRYGRAPH_PROJECT_ID") or os.getenv("GOOGLE_CLOUD_PROJECT")


def _table_ref(dataset: str, table: str) -> str:
    if PROJECT_ID:
        return f"{PROJECT_ID}.{dataset}.{table}"
    return f"{dataset}.{table}"


def _get_serving_connection():
    try:
        from ...services.foundrygraph_serving import get_foundrygraph_connection
        return get_foundrygraph_connection()
    except Exception as exc:
        logger.debug("FoundryGraph serving unavailable: %s", exc)
        return None


def _lookup_domain_bq(domain: str) -> Optional[dict]:
    try:
        client = _get_bq_client()
    except Exception as exc:
        logger.debug("FoundryGraph BigQuery client unavailable: %s", exc)
        return None

    domain_lookup = _table_ref("foundrygraph_gold", "domain_lookup")
    companies_gold = _table_ref("foundrygraph_gold", "companies_gold")

    try:
        from google.cloud import bigquery

        query = f"""
            SELECT dl.domain, dl.wikidata_id, dl.label, dl.family_id, dl.is_primary,
                   dl.primary_domain, dl.industries, dl.headquarters, dl.quality_score,
                   cg.fg_id AS fg_id, cg.status AS status, cg.redirect_to_fg_id AS redirect_to_fg_id
            FROM `{domain_lookup}` dl
            LEFT JOIN `{companies_gold}` cg
              ON dl.wikidata_id = cg.wikidata_id
            WHERE dl.domain = @domain
            LIMIT 1
        """
        job_config = bigquery.QueryJobConfig(
            query_parameters=[
                bigquery.ScalarQueryParameter("domain", "STRING", domain)
            ]
        )
        rows = list(client.query(query, job_config=job_config).result())
        if rows:
            row = rows[0]
            return {
                "domain": getattr(row, "domain", None),
                "wikidata_id": getattr(row, "wikidata_id", None),
                "label": getattr(row, "label", None),
                "family_id": getattr(row, "family_id", None),
                "is_primary": getattr(row, "is_primary", False),
                "primary_domain": getattr(row, "primary_domain", None),
                "industries": getattr(row, "industries", None),
                "headquarters": getattr(row, "headquarters", None),
                "quality_score": getattr(row, "quality_score", None),
                "fg_id": getattr(row, "fg_id", None),
                "status": getattr(row, "status", None),
                "redirect_to_fg_id": getattr(row, "redirect_to_fg_id", None),
            }
    except Exception as exc:
        logger.debug("FoundryGraph BigQuery domain lookup failed: %s", exc)

    try:
        from google.cloud import bigquery

        query = f"""
            SELECT domain, wikidata_id, label, industries, headquarters, quality_score,
                   fg_id, status, redirect_to_fg_id
            FROM `{companies_gold}`
            WHERE domain = @domain
            LIMIT 1
        """
        job_config = bigquery.QueryJobConfig(
            query_parameters=[
                bigquery.ScalarQueryParameter("domain", "STRING", domain)
            ]
        )
        rows = list(client.query(query, job_config=job_config).result())
    except Exception as exc:
        logger.debug("FoundryGraph BigQuery company lookup failed: %s", exc)
        return None

    if not rows:
        return None

    row = rows[0]
    return {
        "domain": getattr(row, "domain", None),
        "wikidata_id": getattr(row, "wikidata_id", None),
        "label": getattr(row, "label", None),
        "family_id": None,
        "is_primary": False,
        "primary_domain": getattr(row, "domain", None),
        "industries": getattr(row, "industries", None),
        "headquarters": getattr(row, "headquarters", None),
        "quality_score": getattr(row, "quality_score", None),
        "fg_id": getattr(row, "fg_id", None),
        "status": getattr(row, "status", None),
        "redirect_to_fg_id": getattr(row, "redirect_to_fg_id", None),
    }


def _lookup_family_domains_bq(family_id: str, domain: str) -> List[str]:
    if not family_id:
        return []
    try:
        client = _get_bq_client()
    except Exception as exc:
        logger.debug("FoundryGraph BigQuery client unavailable: %s", exc)
        return []

    domain_lookup = _table_ref("foundrygraph_gold", "domain_lookup")
    try:
        from google.cloud import bigquery

        query = f"""
            SELECT domain
            FROM `{domain_lookup}`
            WHERE family_id = @family_id AND domain <> @domain
            LIMIT 20
        """
        job_config = bigquery.QueryJobConfig(
            query_parameters=[
                bigquery.ScalarQueryParameter("family_id", "STRING", family_id),
                bigquery.ScalarQueryParameter("domain", "STRING", domain),
            ]
        )
        rows = list(client.query(query, job_config=job_config).result())
    except Exception as exc:
        logger.debug("FoundryGraph BigQuery family lookup failed: %s", exc)
        return []

    return [getattr(row, "domain", "") for row in rows if getattr(row, "domain", "")]


def _lookup_fg_statuses(fg_ids: List[str]) -> Dict[str, Dict[str, Optional[str]]]:
    if not fg_ids:
        return {}
    try:
        client = _get_bq_client()
    except Exception as exc:
        logger.debug("FoundryGraph BigQuery client unavailable: %s", exc)
        return {}

    companies_gold = _table_ref("foundrygraph_gold", "companies_gold")
    try:
        from google.cloud import bigquery

        query = f"""
            SELECT fg_id, status, redirect_to_fg_id
            FROM `{companies_gold}`
            WHERE fg_id IN UNNEST(@fg_ids)
        """
        job_config = bigquery.QueryJobConfig(
            query_parameters=[
                bigquery.ArrayQueryParameter("fg_ids", "STRING", fg_ids)
            ]
        )
        rows = list(client.query(query, job_config=job_config).result())
    except Exception as exc:
        logger.debug("FoundryGraph BigQuery fg status lookup failed: %s", exc)
        return {}

    return {
        getattr(row, "fg_id", None): {
            "status": getattr(row, "status", None),
            "redirect_to_fg_id": getattr(row, "redirect_to_fg_id", None),
        }
        for row in rows
        if getattr(row, "fg_id", None)
    }


def _row_from_serving_query(conn: Any, row: Any) -> Optional[dict]:
    if not row:
        return None
    columns = [column[0] for column in conn.description]
    return dict(zip(columns, row))


def _lookup_registry_company_serving_by_lei(lei: str) -> Optional[dict]:
    conn = _get_serving_connection()
    if conn is None:
        return None

    normalized_lei = (lei or "").strip().upper()
    if not normalized_lei:
        return None

    try:
        row = conn.execute(
            """
            SELECT c.*
            FROM lei_lookup_registry l
            JOIN company_profiles_registry c USING (wikidata_id)
            WHERE l.primary_lei = ?
            LIMIT 1
            """,
            [normalized_lei],
        ).fetchone()
    except Exception as exc:
        logger.debug("FoundryGraph serving registry LEI lookup failed: %s", exc)
        return None

    record = _row_from_serving_query(conn, row)
    if record:
        record["source"] = "foundrygraph_serving_registry"
    return record


def _lookup_registry_company_serving_by_wikidata_id(wikidata_id: str) -> Optional[dict]:
    conn = _get_serving_connection()
    if conn is None:
        return None

    normalized_id = (wikidata_id or "").strip()
    if not normalized_id:
        return None

    try:
        row = conn.execute(
            """
            SELECT *
            FROM company_profiles_registry
            WHERE wikidata_id = ?
            LIMIT 1
            """,
            [normalized_id],
        ).fetchone()
    except Exception as exc:
        logger.debug("FoundryGraph serving registry company lookup failed: %s", exc)
        return None

    record = _row_from_serving_query(conn, row)
    if record:
        record["source"] = "foundrygraph_serving_registry"
    return record


def _lookup_registry_company_by_lei(lei: str) -> Optional[dict]:
    serving_record = _lookup_registry_company_serving_by_lei(lei)
    if serving_record:
        return serving_record
    try:
        from ...services import foundrygraph_bq

        return foundrygraph_bq.lookup_registry_company_by_lei(lei)
    except Exception as exc:
        logger.debug("FoundryGraph registry LEI lookup failed: %s", exc)
        return None


def _lookup_registry_company_by_wikidata_id(wikidata_id: str) -> Optional[dict]:
    serving_record = _lookup_registry_company_serving_by_wikidata_id(wikidata_id)
    if serving_record:
        return serving_record
    try:
        from ...services import foundrygraph_bq

        return foundrygraph_bq.lookup_registry_company_by_wikidata_id(wikidata_id)
    except Exception as exc:
        logger.debug("FoundryGraph registry company lookup failed: %s", exc)
        return None


def _coerce_json(value: Any) -> Any:
    if value is None:
        return None
    if isinstance(value, (dict, list)):
        return value
    if isinstance(value, str):
        try:
            return json.loads(value)
        except Exception:
            return value
    return value


class CompanyProfile(BaseModel):
    """Full company profile from FoundryGraph."""

    fg_id: Optional[str] = None
    wikidata_id: str
    label: str
    domain: Optional[str] = None
    website: Optional[str] = None
    industries: Optional[List[dict]] = None
    headquarters: Optional[List[dict]] = None
    countries: Optional[List[dict]] = None
    revenue: Optional[dict] = None
    employees: Optional[dict] = None
    parent: Optional[dict] = None
    subsidiaries: Optional[List[dict]] = None
    quality_score: Optional[int] = None
    aliases: Optional[List[str]] = None
    ticker: Optional[str] = None
    stock_exchange: Optional[str] = None
    sources: Optional[List[Any]] = None
    last_updated: Optional[str] = None
    status: Literal["active", "deprecated", "deleted"] = "active"
    redirect_to: Optional[str] = None
    deprecated_fg_id: Optional[str] = None


class RegistryCompanyProfile(BaseModel):
    """Registry-grade legal-entity profile from FoundryGraph."""

    wikidata_id: str
    label: str
    primary_lei: str
    entity_origin: Optional[str] = None
    legal_jurisdiction: Optional[str] = None
    registration_status: Optional[str] = None
    entity_status: Optional[str] = None
    managing_lou: Optional[str] = None
    validation_sources: Optional[str] = None
    gleif_last_verified_at: Optional[str] = None
    domain: Optional[str] = None
    website: Optional[str] = None
    industries: Optional[List[dict]] = None
    headquarters: Optional[List[dict]] = None
    countries: Optional[List[dict]] = None
    primary_industry_id: Optional[str] = None
    industry_ids: Optional[List[str]] = None
    hq_city: Optional[str] = None
    hq_country: Optional[str] = None
    primary_domain: Optional[str] = None
    sources: Optional[List[Any]] = None
    source_count: Optional[int] = None
    quality_score: Optional[int] = None
    promotion_status: Optional[str] = None
    authoritative_id_count: Optional[int] = None
    in_canonical_gold: bool = False
    has_primary_domain: bool = False
    parent_org: Optional[str] = None
    parent_source: Optional[str] = None
    subsidiaries: Optional[List[dict]] = None
    ultimate_parent_id: Optional[str] = None
    ultimate_parent_name: Optional[str] = None
    ultimate_parent_domain: Optional[str] = None
    sic_code: Optional[str] = None
    sic_description: Optional[str] = None
    source: Optional[str] = None


class DomainLookupResult(BaseModel):
    """Result of domain → company lookup."""

    domain: str
    company: Optional[CompanyProfile] = None
    family_id: Optional[str] = None
    is_primary: bool = False
    confidence: float = 0.0
    related_domains: Optional[List[str]] = None


class NameSearchResult(BaseModel):
    """Result of name search."""

    query: str
    results: List[CompanyProfile]
    total_count: int


class ResolveIdentifier(BaseModel):
    system: str  # 'wikidata', 'domain', 'lei', 'ticker'
    value: str


class ResolveRequest(BaseModel):
    identifiers: List[ResolveIdentifier]


class ResolvedIdentifier(BaseModel):
    system: str
    value: str
    fg_id: Optional[str] = None
    status: Optional[str] = None
    redirect_to: Optional[str] = None


class ResolveResponse(BaseModel):
    resolved: List[ResolvedIdentifier]
    unresolved: List[ResolveIdentifier]


def _to_registry_company_profile(record: Dict[str, Any], *, include_relationships: bool = True) -> RegistryCompanyProfile:
    subsidiaries = _coerce_json(record.get("subsidiaries"))
    if subsidiaries is not None:
        subsidiaries = [
            dict(item.items()) if hasattr(item, "items") else item
            for item in subsidiaries
        ]
    if not include_relationships:
        subsidiaries = None

    return RegistryCompanyProfile(
        wikidata_id=record.get("wikidata_id"),
        label=record.get("label"),
        primary_lei=record.get("primary_lei"),
        entity_origin=record.get("entity_origin"),
        legal_jurisdiction=record.get("legal_jurisdiction"),
        registration_status=record.get("registration_status"),
        entity_status=record.get("entity_status"),
        managing_lou=record.get("managing_lou"),
        validation_sources=record.get("validation_sources"),
        gleif_last_verified_at=record.get("gleif_last_verified_at"),
        domain=record.get("domain"),
        website=record.get("website"),
        industries=_coerce_json(record.get("industries")),
        headquarters=_coerce_json(record.get("headquarters")),
        countries=_coerce_json(record.get("countries")),
        primary_industry_id=record.get("primary_industry_id"),
        industry_ids=_coerce_json(record.get("industry_ids")),
        hq_city=record.get("hq_city"),
        hq_country=record.get("hq_country"),
        primary_domain=record.get("primary_domain"),
        sources=_coerce_json(record.get("sources")),
        source_count=record.get("source_count"),
        quality_score=record.get("quality_score"),
        promotion_status=record.get("promotion_status"),
        authoritative_id_count=record.get("authoritative_id_count"),
        in_canonical_gold=bool(record.get("in_canonical_gold")),
        has_primary_domain=bool(record.get("has_primary_domain")),
        parent_org=record.get("parent_org") if include_relationships else None,
        parent_source=record.get("parent_source") if include_relationships else None,
        subsidiaries=subsidiaries,
        ultimate_parent_id=record.get("ultimate_parent_id") if include_relationships else None,
        ultimate_parent_name=record.get("ultimate_parent_name") if include_relationships else None,
        ultimate_parent_domain=record.get("ultimate_parent_domain") if include_relationships else None,
        sic_code=record.get("sic_code"),
        sic_description=record.get("sic_description"),
        source=record.get("source"),
    )


@router.get("/lookup/domain", response_model=DomainLookupResult)
async def lookup_by_domain(
    domain: str = Query(..., description="Domain to lookup (e.g., 'acme.com')"),
    include_family: bool = Query(True, description="Include related domains in family"),
    account_id: str = Depends(require_account),
) -> DomainLookupResult:
    """
    Lookup company by domain against the Gold tier.
    """
    # Rate limit check
    _check_rate_limit(account_id)

    domain = domain.lower().strip()
    if domain.startswith("www."):
        domain = domain[4:]

    row = None
    fg_id = None
    status = None
    redirect_to = None
    has_fg_id = False
    conn = _get_serving_connection()
    if conn is not None:
        try:
            row = conn.execute(
                """
                SELECT domain, wikidata_id, fg_id, label, label_normalized, family_id, is_primary,
                       primary_domain, industries, headquarters, quality_score
                FROM domain_lookup
                WHERE domain = ?
                LIMIT 1
                """,
                [domain],
            ).fetchone()
            has_fg_id = True
        except Exception as exc:
            try:
                row = conn.execute(
                    """
                    SELECT domain, wikidata_id, label, label_normalized, family_id, is_primary,
                           primary_domain, industries, headquarters, quality_score
                    FROM domain_lookup
                    WHERE domain = ?
                    LIMIT 1
                    """,
                    [domain],
                ).fetchone()
                has_fg_id = False
            except Exception as exc2:
                logger.debug("FoundryGraph serving domain lookup failed: %s", exc2)
                row = None

    if row is None:
        bq_row = _lookup_domain_bq(domain)
        if not bq_row:
            return DomainLookupResult(domain=domain, company=None, confidence=0.0)
        fg_id = bq_row.get("fg_id")
        status = bq_row.get("status")
        redirect_to = bq_row.get("redirect_to_fg_id")
        row = (
            bq_row.get("domain"),
            bq_row.get("wikidata_id"),
            bq_row.get("label"),
            None,
            bq_row.get("family_id"),
            bq_row.get("is_primary"),
            bq_row.get("primary_domain"),
            bq_row.get("industries"),
            bq_row.get("headquarters"),
            bq_row.get("quality_score"),
        )

    if not row:
        return DomainLookupResult(domain=domain, company=None, confidence=0.0)

    if has_fg_id:
        (
            d_domain,
            wikidata_id,
            fg_id,
            label,
            _label_normalized,
            family_id,
            is_primary,
            primary_domain,
            industries,
            headquarters,
            quality_score,
        ) = row
    else:
        (
            d_domain,
            wikidata_id,
            label,
            _label_normalized,
            family_id,
            is_primary,
            primary_domain,
            industries,
            headquarters,
            quality_score,
        ) = row

    def _maybe_parse(value):
        if value is None:
            return None
        if isinstance(value, str):
            try:
                return json.loads(value)
            except Exception:
                return value
        return value

    related_domains: List[str] = []
    if include_family and family_id:
        if conn is not None:
            try:
                related_domains = [
                    r[0]
                    for r in conn.execute(
                        "SELECT domain FROM domain_lookup WHERE family_id = ? AND domain <> ? LIMIT 20",
                        [family_id, domain],
                    ).fetchall()
                ][:10]
            except Exception as exc:
                logger.debug("FoundryGraph serving family lookup failed: %s", exc)
                related_domains = []
        else:
            related_domains = _lookup_family_domains_bq(family_id, domain)[:10]

    return DomainLookupResult(
        domain=domain,
        company=CompanyProfile(
            fg_id=fg_id,
            wikidata_id=wikidata_id,
            label=label,
            domain=primary_domain or d_domain,
            industries=_maybe_parse(industries),
            headquarters=_maybe_parse(headquarters),
            quality_score=quality_score,
            status=status or "active",
            redirect_to=redirect_to,
        ),
        family_id=family_id,
        is_primary=bool(is_primary),
        confidence=0.95,
        related_domains=related_domains,
    )


@router.get("/lookup/company/{wikidata_id}", response_model=CompanyProfile)
async def get_company_profile(
    wikidata_id: str,
    include_relationships: bool = Query(True),
    include_aliases: bool = Query(True),
    account_id: str = Depends(require_account),
) -> CompanyProfile:
    """
    Get full company profile by Wikidata ID.
    """
    # Rate limit check
    _check_rate_limit(account_id)

    from ...operators.detect_parent_company import detect_parent_company
    from ...operators.wikidata_profiles import get_aliases, get_profile_by_wikidata_id, get_subsidiaries

    profile = get_profile_by_wikidata_id(wikidata_id)
    if not profile:
        raise HTTPException(status_code=404, detail=f"Company not found: {wikidata_id}")

    fg_meta = None
    try:
        from ...services import foundrygraph_bq

        fg_meta = foundrygraph_bq.lookup_company_by_wikidata_id(wikidata_id)
    except Exception as exc:
        logger.debug("FoundryGraph BigQuery fg_id lookup failed: %s", exc)

    result = CompanyProfile(
        fg_id=fg_meta.get("fg_id") if fg_meta else None,
        wikidata_id=profile.get("wikidata_id"),
        label=profile.get("label"),
        domain=profile.get("domain"),
        website=profile.get("website"),
        industries=profile.get("industries"),
        headquarters=profile.get("headquarters"),
        countries=profile.get("countries"),
        revenue=profile.get("revenue"),
        employees=profile.get("employees"),
        quality_score=profile.get("quality_score"),
        last_updated=profile.get("last_refreshed"),
        status=(fg_meta.get("status") if fg_meta else None) or "active",
        redirect_to=fg_meta.get("redirect_to_fg_id") if fg_meta else None,
    )

    if include_relationships:
        parent_info = detect_parent_company(profile.get("label"), profile.get("website"), profile.get("domain"))
        if parent_info and parent_info.get("confidence", 0) > 0.7:
            result.parent = {"label": parent_info.get("parent"), "confidence": parent_info.get("confidence")}
        subsidiaries = get_subsidiaries(wikidata_id)
        if subsidiaries:
            result.subsidiaries = subsidiaries[:20]

    if include_aliases:
        aliases = get_aliases(wikidata_id)
        if aliases:
            result.aliases = [alias["alias"] for alias in aliases][:10]

    return result


@router.get("/lookup/company/fg/{fg_id}", response_model=CompanyProfile)
async def get_company_by_fg_id(
    fg_id: str,
    follow_redirects: bool = Query(True, description="Follow deprecated entity redirects"),
    account_id: str = Depends(require_account),
) -> CompanyProfile:
    """
    Get company by FoundryGraph ID.

    Redirect behavior:
    - follow_redirects=True (default): If deprecated, return survivor profile
      with deprecated_fg_id set to the original requested ID
    - follow_redirects=False: Return the deprecated record as-is with
      status='deprecated' and redirect_to=<survivor_fg_id>
    """
    _check_rate_limit(account_id)

    from ...services.fg_entity_id import validate_fg_id
    from ...services.fg_entity_registry import EntityRegistry
    from ...operators.wikidata_profiles import get_profile_by_wikidata_id

    if not validate_fg_id(fg_id):
        raise HTTPException(status_code=400, detail=f"Invalid fg_id format: {fg_id}")

    registry = EntityRegistry()
    entity = await registry.get_entity(fg_id, follow_redirects=follow_redirects)
    if not entity:
        raise HTTPException(status_code=404, detail=f"Company not found: {fg_id}")

    deprecated_fg_id = entity.get("deprecated_fg_id")
    resolved_fg_id = entity.get("fg_id") or fg_id
    status = entity.get("status") or "active"
    redirect_to = entity.get("redirect_to_fg_id")
    wikidata_id = entity.get("wikidata_id")

    profile = get_profile_by_wikidata_id(wikidata_id) if wikidata_id else None
    if profile:
        return CompanyProfile(
            fg_id=resolved_fg_id,
            wikidata_id=profile.get("wikidata_id"),
            label=profile.get("label"),
            domain=profile.get("domain"),
            website=profile.get("website"),
            industries=profile.get("industries"),
            headquarters=profile.get("headquarters"),
            countries=profile.get("countries"),
            revenue=profile.get("revenue"),
            employees=profile.get("employees"),
            quality_score=profile.get("quality_score"),
            last_updated=profile.get("last_refreshed"),
            status=status,
            redirect_to=redirect_to,
            deprecated_fg_id=deprecated_fg_id,
        )

    if not wikidata_id:
        raise HTTPException(status_code=404, detail=f"Company profile unavailable: {fg_id}")

    return CompanyProfile(
        fg_id=resolved_fg_id,
        wikidata_id=wikidata_id,
        label=entity.get("label") or wikidata_id,
        domain=entity.get("domain"),
        status=status,
        redirect_to=redirect_to,
        deprecated_fg_id=deprecated_fg_id,
    )


@router.get("/lookup/lei/{lei}", response_model=RegistryCompanyProfile)
async def lookup_registry_by_lei(
    lei: str,
    include_relationships: bool = Query(True, description="Include parent and subsidiary structure"),
    account_id: str = Depends(require_account),
) -> RegistryCompanyProfile:
    """Lookup a registry-grade company profile by LEI."""
    _check_rate_limit(account_id)

    record = _lookup_registry_company_by_lei(lei)
    if not record:
        raise HTTPException(status_code=404, detail=f"Registry company not found for LEI: {lei}")

    return _to_registry_company_profile(record, include_relationships=include_relationships)


@router.get("/lookup/registry/{wikidata_id}", response_model=RegistryCompanyProfile)
async def get_registry_company_profile(
    wikidata_id: str,
    include_relationships: bool = Query(True, description="Include parent and subsidiary structure"),
    account_id: str = Depends(require_account),
) -> RegistryCompanyProfile:
    """Get a registry-grade company profile by FoundryGraph company id."""
    _check_rate_limit(account_id)

    record = _lookup_registry_company_by_wikidata_id(wikidata_id)
    if not record:
        raise HTTPException(status_code=404, detail=f"Registry company not found: {wikidata_id}")

    return _to_registry_company_profile(record, include_relationships=include_relationships)


@router.post("/companies/resolve", response_model=ResolveResponse)
async def resolve_companies(
    payload: ResolveRequest,
    account_id: str = Depends(require_account),
) -> ResolveResponse:
    """
    Resolve multiple identifiers to fg_ids.
    Preserves input ordering in response.
    """
    _check_rate_limit(account_id)

    from ...services.fg_entity_registry import EntityRegistry

    identifiers = [(i.system, i.value) for i in payload.identifiers or []]
    registry = EntityRegistry()
    resolved_map = await registry.bulk_resolve(identifiers)

    fg_ids = [fg_id for fg_id in resolved_map.values() if fg_id]
    status_map = _lookup_fg_statuses(list({fg_id for fg_id in fg_ids if fg_id}))

    resolved_list: List[ResolvedIdentifier] = []
    unresolved_list: List[ResolveIdentifier] = []

    for ident in payload.identifiers or []:
        key = (ident.system, ident.value)
        fg_id = resolved_map.get(key)
        if fg_id:
            info = status_map.get(fg_id)
            status_value = info.get("status") if info else None
            if info is not None and not status_value:
                status_value = "active"
            resolved_list.append(
                ResolvedIdentifier(
                    system=ident.system,
                    value=ident.value,
                    fg_id=fg_id,
                    status=status_value,
                    redirect_to=info.get("redirect_to_fg_id") if info else None,
                )
            )
        else:
            unresolved_list.append(ResolveIdentifier(system=ident.system, value=ident.value))

    return ResolveResponse(resolved=resolved_list, unresolved=unresolved_list)


@router.get("/search", response_model=NameSearchResult)
async def search_companies(
    q: str = Query(..., min_length=2, description="Search query"),
    limit: int = Query(20, ge=1, le=100),
    include_aliases: bool = Query(True, description="Search aliases too"),
    account_id: str = Depends(require_account),
) -> NameSearchResult:
    """Search companies by name (and aliases if requested)."""
    # Rate limit check
    _check_rate_limit(account_id)

    results: List[Dict[str, Any]] = []
    conn = _get_serving_connection()
    if conn is not None:
        term = f"%{q.lower()}%"
        try:
            rows = conn.execute(
                """
                SELECT
                  fg_id,
                  wikidata_id,
                  label,
                  domain,
                  industries,
                  headquarters,
                  quality_score
                FROM company_profiles
                WHERE lower(label) LIKE ?
                ORDER BY quality_score DESC NULLS LAST
                LIMIT ?
                """,
                [term, limit],
            ).fetchall()
            cols = [c[0] for c in conn.description]
            for row in rows:
                record = dict(zip(cols, row))
                record["industries"] = _coerce_json(record.get("industries"))
                record["headquarters"] = _coerce_json(record.get("headquarters"))
                results.append(record)
        except Exception as exc:
            try:
                rows = conn.execute(
                    """
                    SELECT
                      wikidata_id,
                      label,
                      domain,
                      industries,
                      headquarters,
                      quality_score
                    FROM company_profiles
                    WHERE lower(label) LIKE ?
                    ORDER BY quality_score DESC NULLS LAST
                    LIMIT ?
                    """,
                    [term, limit],
                ).fetchall()
                cols = [c[0] for c in conn.description]
                for row in rows:
                    record = dict(zip(cols, row))
                    record["industries"] = _coerce_json(record.get("industries"))
                    record["headquarters"] = _coerce_json(record.get("headquarters"))
                    results.append(record)
            except Exception as exc2:
                logger.debug("FoundryGraph serving search failed: %s", exc2)

    if conn is None:
        try:
            from ...services import foundrygraph_bq

            results = foundrygraph_bq.search_companies_by_name_term(q, limit=limit)
        except Exception as exc:
            logger.debug("FoundryGraph BigQuery search failed: %s", exc)
            results = []
    profiles = [
        CompanyProfile(
            fg_id=item.get("fg_id"),
            wikidata_id=item.get("wikidata_id"),
            label=item.get("label"),
            domain=item.get("domain"),
            industries=item.get("industries"),
            quality_score=item.get("quality_score"),
        )
        for item in results
    ]
    return NameSearchResult(query=q, results=profiles, total_count=len(profiles))


@router.get("/stats")
async def get_foundrygraph_stats(account_id: str = Depends(require_account)) -> dict:
    """Get FoundryGraph coverage statistics from the Gold tier."""
    client = _get_bq_client()
    table_ref = (
        f"{PROJECT_ID}.foundrygraph_gold.companies_gold"
        if PROJECT_ID
        else "foundrygraph_gold.companies_gold"
    )
    query = f"""
    SELECT
        COUNT(*) as total_companies,
        COUNTIF(domain IS NOT NULL) as with_domain,
        COUNTIF(industries IS NOT NULL) as with_industry,
        COUNTIF(headquarters IS NOT NULL) as with_hq,
        COUNTIF(revenue IS NOT NULL) as with_revenue,
        AVG(quality_score) as avg_quality_score,
        MAX(updated_at) as last_updated
    FROM `{table_ref}`
    """
    result = list(client.query(query).result())[0]
    total = result.total_companies or 1
    return {
        "total_companies": result.total_companies,
        "coverage": {
            "domain": round(100 * result.with_domain / total, 1),
            "industry": round(100 * result.with_industry / total, 1),
            "headquarters": round(100 * result.with_hq / total, 1),
            "revenue": round(100 * result.with_revenue / total, 1),
        },
        "avg_quality_score": round(result.avg_quality_score or 0, 1),
        "last_updated": str(result.last_updated),
    }


# ---------------------------------------------------------------------------
# Fuzzy Match Endpoint - Match company names against FoundryGraph
# ---------------------------------------------------------------------------


class FuzzyMatchRequest(BaseModel):
    """Request for fuzzy matching company names against FoundryGraph."""

    companies: List[dict]  # List of {company: str, domain: str (optional)}
    threshold: float = 0.7
    limit: int = 1000  # Max companies to process


class FuzzyMatchItem(BaseModel):
    """Single match result."""

    row_index: int
    input_company: str
    input_domain: Optional[str] = None
    matched_company: Optional[str] = None
    matched_domain: Optional[str] = None
    wikidata_id: Optional[str] = None
    fg_id: Optional[str] = None
    match_score: Optional[float] = None
    match_method: Optional[str] = None  # 'fuzzy', 'domain_exact', 'no_match'


class FuzzyMatchResponse(BaseModel):
    """Response from fuzzy match endpoint."""

    matches: List[FuzzyMatchItem]
    matched: int = 0
    no_match: int = 0
    low_confidence: int = 0
    rows_scanned: int = 0


def _fetch_foundrygraph_reference(limit: int = 10000) -> List[Dict[str, Any]]:
    """
    Fetch company reference data from FoundryGraph for fuzzy matching.
    Returns top companies by quality score.
    """
    try:
        client = _get_bq_client()
    except Exception as exc:
        logger.warning("FoundryGraph BigQuery client unavailable: %s", exc)
        return []

    table_ref = _table_ref("foundrygraph_gold", "companies_gold")

    try:

        # Fetch high-quality companies for matching reference
        query = f"""
            SELECT fg_id, wikidata_id, label, label_normalized, domain, quality_score
            FROM `{table_ref}`
            WHERE label IS NOT NULL AND label != ''
            ORDER BY quality_score DESC NULLS LAST
            LIMIT {int(limit)}
        """
        rows = list(client.query(query).result())
    except Exception as exc:
        try:
            query = f"""
                SELECT wikidata_id, label, label_normalized, domain, quality_score
                FROM `{table_ref}`
                WHERE label IS NOT NULL AND label != ''
                ORDER BY quality_score DESC NULLS LAST
                LIMIT {int(limit)}
            """
            rows = list(client.query(query).result())
        except Exception:
            logger.warning("FoundryGraph BigQuery reference fetch failed: %s", exc)
            return []

    results = []
    for row in rows:
        results.append({
            "fg_id": getattr(row, "fg_id", None),
            "wikidata_id": getattr(row, "wikidata_id", None),
            "label": getattr(row, "label", None),
            "label_normalized": getattr(row, "label_normalized", None),
            "domain": getattr(row, "domain", None),
            "quality_score": getattr(row, "quality_score", None),
        })
    return results


@router.post("/match", response_model=FuzzyMatchResponse)
async def fuzzy_match_companies(
    payload: FuzzyMatchRequest,
    account_id: str = Depends(require_account),
) -> FuzzyMatchResponse:
    """
    Fuzzy match company names against FoundryGraph reference data.

    Uses the same EngineClient as SFDC matching, but with FoundryGraph
    as the reference dataset. Works without SFDC connection.

    Good for:
    - Fixing typos in company names (e.g., "Ibmer" -> "IBM")
    - Standardizing company names to canonical forms
    - Finding Wikidata IDs for companies
    """
    import pandas as pd
    from ...services.engine_client import EngineClient
    from ...services.intelligence_client import IntelligenceClient

    # Rate limit check
    _check_rate_limit(account_id)

    companies = payload.companies or []
    if not companies:
        return FuzzyMatchResponse(matches=[], matched=0, no_match=0, rows_scanned=0)

    # Limit processing
    companies = companies[: payload.limit]

    # Step 1: Try domain-exact matching first (fast path)
    domains_to_lookup = set()
    for item in companies:
        domain = (item.get("domain") or "").strip().lower()
        if domain:
            if domain.startswith("www."):
                domain = domain[4:]
            domains_to_lookup.add(domain)

    domain_matches: Dict[str, Dict[str, Any]] = {}
    if domains_to_lookup:
        try:
            from ...services.foundrygraph_bq import lookup_companies_by_domain

            domain_matches = lookup_companies_by_domain(list(domains_to_lookup))
        except Exception as exc:
            logger.debug("Domain batch lookup failed: %s", exc)

    # Step 2: Build source DataFrame for fuzzy matching
    source_records = []
    for idx, item in enumerate(companies):
        company = (item.get("company") or "").strip()
        domain = (item.get("domain") or "").strip().lower()
        if domain.startswith("www."):
            domain = domain[4:]
        source_records.append({
            "row_id": str(idx),
            "company_name": company,
            "domain": domain,
        })

    # Step 3: Check domain matches first, build list of unmatched for fuzzy
    results: List[FuzzyMatchItem] = []
    need_fuzzy_match = []

    for idx, item in enumerate(companies):
        company = (item.get("company") or "").strip()
        domain = (item.get("domain") or "").strip().lower()
        if domain.startswith("www."):
            domain = domain[4:]

        # Try domain match first
        if domain and domain in domain_matches:
            dm = domain_matches[domain]
            results.append(FuzzyMatchItem(
                row_index=idx,
                input_company=company,
                input_domain=domain,
                matched_company=dm.get("label"),
                matched_domain=dm.get("domain"),
                wikidata_id=dm.get("wikidata_id"),
                fg_id=dm.get("fg_id"),
                match_score=0.95,  # Domain exact match = high confidence
                match_method="domain_exact",
            ))
        else:
            need_fuzzy_match.append(idx)

    # Step 4: Fuzzy match remaining companies using EngineClient
    if need_fuzzy_match:
        # Fetch reference data
        reference_data = _fetch_foundrygraph_reference(limit=10000)
        if not reference_data:
            # No reference data - mark all as no_match
            for idx in need_fuzzy_match:
                item = companies[idx]
                results.append(FuzzyMatchItem(
                    row_index=idx,
                    input_company=(item.get("company") or "").strip(),
                    input_domain=(item.get("domain") or "").strip(),
                    matched_company=None,
                    matched_domain=None,
                    wikidata_id=None,
                    match_score=None,
                    match_method="no_match",
                ))
        else:
            # Build DataFrames
            source_df = pd.DataFrame([source_records[i] for i in need_fuzzy_match])
            target_df = pd.DataFrame(reference_data)
            target_df["ref_id"] = target_df.index.astype(str)

            # Use IntelligenceClient to auto-configure mappings
            intel = IntelligenceClient()
            try:
                config = await intel.auto_configure(source_df, target_df, goal="lookup")
                mappings = config.get("field_mappings") or config.get("mappings") or [
                    {"source": "company_name", "reference": "label"}
                ]
            except Exception as exc:
                logger.warning("IntelligenceClient auto_configure failed: %s", exc)
                mappings = [{"source": "company_name", "reference": "label"}]

            # Run fuzzy matching
            engine = EngineClient()
            try:
                engine_res = await engine.match_records(
                    source_df,
                    target_df,
                    mappings=mappings,
                    threshold=payload.threshold,
                    blocking_strategy=config.get("blocking") if config else None,
                    source_id_column="row_id",
                    reference_id_column="ref_id",
                )
                matches_df = engine_res.matches if engine_res else pd.DataFrame()
            except Exception as exc:
                logger.warning("EngineClient match failed: %s", exc)
                matches_df = pd.DataFrame()

            # Build lookup from source row_id to match result
            match_lookup = {}
            if not matches_df.empty:
                for _, match_row in matches_df.iterrows():
                    source_id = str(match_row.get("source_id", ""))
                    ref_id = match_row.get("reference_id", "")
                    score = match_row.get("score", 0)
                    if source_id and ref_id is not None:
                        try:
                            ref_idx = int(ref_id)
                            if 0 <= ref_idx < len(reference_data):
                                match_lookup[source_id] = {
                                    "ref": reference_data[ref_idx],
                                    "score": score,
                                }
                        except (ValueError, IndexError):
                            pass

            # Process fuzzy match results
            for idx in need_fuzzy_match:
                item = companies[idx]
                company = (item.get("company") or "").strip()
                domain = (item.get("domain") or "").strip()
                row_id = str(idx)

                if row_id in match_lookup:
                    match_data = match_lookup[row_id]
                    ref = match_data["ref"]
                    results.append(FuzzyMatchItem(
                        row_index=idx,
                        input_company=company,
                        input_domain=domain,
                        matched_company=ref.get("label"),
                        matched_domain=ref.get("domain"),
                        wikidata_id=ref.get("wikidata_id"),
                        fg_id=ref.get("fg_id"),
                        match_score=match_data["score"],
                        match_method="fuzzy",
                    ))
                else:
                    results.append(FuzzyMatchItem(
                        row_index=idx,
                        input_company=company,
                        input_domain=domain,
                        matched_company=None,
                        matched_domain=None,
                        wikidata_id=None,
                        match_score=None,
                        match_method="no_match",
                    ))

    # Sort results by row_index
    results.sort(key=lambda x: x.row_index)

    # Count statistics
    matched = sum(1 for r in results if r.match_method in ("domain_exact", "fuzzy"))
    no_match = sum(1 for r in results if r.match_method == "no_match")
    low_confidence = sum(
        1 for r in results
        if r.match_method == "fuzzy" and r.match_score and r.match_score < 0.85
    )

    return FuzzyMatchResponse(
        matches=results,
        matched=matched,
        no_match=no_match,
        low_confidence=low_confidence,
        rows_scanned=len(companies),
    )
