"""Export Intel API — Foodlinkk B2B global export intelligence."""
from __future__ import annotations

import csv
import io
from typing import Any, Optional

from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

from app.db import execute, execute_returning, fetch_all, fetch_one
from app.export_halal_scoring import COUNTRY_HALAL_MARKET, halal_pin_tier, score_entity
from app.export_intel_crm import push_entities_to_crm

HALAL_REASON_NL: dict[str, str] = {
    "sterke_halal_markt": "Sterke halal-markt in dit land",
    "halal_in_naam": "Halal expliciet in bedrijfsnaam",
    "halal_signaal": "Kebab/döner/vlees-signaal in naam",
    "direct_vlees_kanaal": "Direct vlees-verkoopkanaal",
    "B2B_kanaal": "B2B inkoop / distributie",
    "heeft_contact": "Contactpersoon in registry",
}


def _halal_reason_labels(reasons: list[str]) -> list[str]:
    out: list[str] = []
    for r in reasons:
        if r in HALAL_REASON_NL:
            out.append(HALAL_REASON_NL[r])
        elif r.startswith("type:"):
            out.append("Type: " + r.split(":", 1)[1].replace("_", " "))
        else:
            out.append(r.replace("_", " "))
    return out


def _enrich_entity(row: dict[str, Any], contacts: list[dict[str, Any]]) -> dict[str, Any]:
    row["contacts"] = contacts
    row["contact_count"] = len(contacts)
    for c in contacts:
        if not row.get("email") and c.get("email"):
            row["email"] = c["email"]
        if not row.get("phone"):
            phone = c.get("phone") or c.get("mobile")
            if phone:
                row["phone"] = phone
    primary = contacts[0] if contacts else None
    row["primary_contact"] = primary
    h_score, h_reasons = score_entity(row)
    row["halal_score"] = h_score
    row["halal_reasons"] = h_reasons
    row["halal_reason_labels"] = _halal_reason_labels(h_reasons)
    if row.get("lat") and row.get("lon"):
        row["maps_url"] = f"https://www.google.com/maps?q={row['lat']},{row['lon']}"
    return row
from app.export_intel_sync import (
    enrich_contacts_country,
    materialize_caterer_presence,
    sync_all_priority,
    sync_caterers,
    sync_contacts,
    sync_customers,
    sync_distributors,
    sync_osm_country,
    sync_ted_tenders,
    sync_world_regions,
)

router = APIRouter(prefix="/export-intel", tags=["export-intel"])

DISTRIBUTOR_TYPES = ("distributor", "wholesaler", "importer", "logistics", "cold_storage", "port_agent")
CUSTOMER_TYPES = ("restaurant", "doner_shoarma", "butcher", "foodservice")
CATERER_TYPES = ("contract_caterer")


class ContactCreateIn(BaseModel):
    role: str = "general"
    name: Optional[str] = None
    email: Optional[str] = None
    phone: Optional[str] = None
    mobile: Optional[str] = None
    is_primary: bool = False


class SyncRequestIn(BaseModel):
    country_iso2: Optional[str] = None
    region_code: Optional[str] = None
    max_priority: int = 2
    entity_types: Optional[list[str]] = None


class EntityIdsIn(BaseModel):
    entity_ids: list[int]


class FavoritesIn(BaseModel):
    entity_ids: list[int]
    favorite: bool = True


class CrmPushIn(BaseModel):
    entity_ids: list[int]
    create_deals: bool = True
    client_stage: str = "intake"
    deal_stage: str = "lead"


def _entity_filters(
    country: Optional[str],
    region: Optional[str],
    entity_type: Optional[str],
    entity_types: Optional[str],
    favorite_only: bool = False,
    crm_linked: Optional[bool] = None,
) -> tuple[str, list[Any]]:
    clauses: list[str] = ["1=1"]
    params: list[Any] = []
    if country:
        clauses.append("e.country_iso2 = %s")
        params.append(country.upper())
    if region:
        clauses.append("t.region_code = %s")
        params.append(region)
        clauses.append("e.territory_code = t.code")
    if entity_type:
        clauses.append("e.entity_type = %s")
        params.append(entity_type)
    if entity_types:
        types = [t.strip() for t in entity_types.split(",") if t.strip()]
        if types:
            clauses.append("e.entity_type = ANY(%s)")
            params.append(types)
    if favorite_only:
        clauses.append("e.is_favorite = TRUE")
    if crm_linked is True:
        clauses.append("e.client_id IS NOT NULL")
    elif crm_linked is False:
        clauses.append("e.client_id IS NULL")
    return " AND ".join(clauses), params


@router.get("/stats")
def export_stats(
    country: Optional[str] = Query(None),
    region: Optional[str] = Query(None),
) -> dict[str, Any]:
    entity_where = ""
    params: list[Any] = []
    if country:
        entity_where = " WHERE country_iso2 = %s"
        params = [country.upper()]
    elif region:
        entity_where = " WHERE territory_code IN (SELECT code FROM export_territories WHERE region_code = %s)"
        params = [region]

    def count(sql_suffix: str, extra_params: tuple[Any, ...] = ()) -> int:
        w = entity_where
        if sql_suffix:
            w = f"{entity_where} AND {sql_suffix}" if entity_where else f" WHERE {sql_suffix}"
        row = fetch_one(
            f"SELECT COUNT(*) AS n FROM export_market_entities{w}",
            tuple(params) + extra_params if params or extra_params else None,
        )
        return int(row["n"] or 0)

    entities_n = count("")
    distributors_n = count("entity_type IN ('distributor','wholesaler','importer','logistics')")
    caterers_n = count("entity_type = 'contract_caterer'")
    contacts = fetch_one("SELECT COUNT(*) AS n FROM export_entity_contacts")
    with_email = fetch_one(
        "SELECT COUNT(DISTINCT entity_id) AS n FROM export_entity_contacts WHERE email IS NOT NULL AND email <> ''"
    )
    territories = fetch_one("SELECT COUNT(*) AS n FROM export_territories WHERE is_active = TRUE")
    presence = fetch_one("SELECT COUNT(*) AS n FROM export_caterer_presence WHERE is_active = TRUE")
    tenders_open = 0
    try:
        row = fetch_one("SELECT COUNT(*) AS n FROM export_tenders WHERE status = 'open'")
        tenders_open = int(row["n"] or 0) if row else 0
    except Exception:
        pass

    fav_row = fetch_one("SELECT COUNT(*) AS n FROM export_market_entities WHERE is_favorite = TRUE")
    crm_row = fetch_one("SELECT COUNT(*) AS n FROM export_market_entities WHERE client_id IS NOT NULL")

    return {
        "entities": entities_n,
        "distributors": distributors_n,
        "caterers": caterers_n,
        "contacts": int(contacts["n"] or 0),
        "entities_with_email": int(with_email["n"] or 0),
        "territories": int(territories["n"] or 0),
        "caterer_presence": int(presence["n"] or 0),
        "tenders_open": tenders_open,
        "favorites": int(fav_row["n"] or 0) if fav_row else 0,
        "crm_linked": int(crm_row["n"] or 0) if crm_row else 0,
        "phase": "B",
        "enrichment_status": "active",
    }


@router.get("/regions")
def list_regions() -> list[dict[str, Any]]:
    return fetch_all("SELECT * FROM export_regions ORDER BY sort_order, code")


@router.get("/territories")
def list_territories(region: Optional[str] = Query(None)) -> list[dict[str, Any]]:
    if region:
        return fetch_all(
            "SELECT * FROM export_territories WHERE is_active = TRUE AND region_code = %s ORDER BY sync_priority, name_nl",
            (region,),
        )
    return fetch_all(
        "SELECT * FROM export_territories WHERE is_active = TRUE ORDER BY sync_priority, name_nl"
    )


@router.get("/entities")
def list_entities(
    country: Optional[str] = Query(None),
    region: Optional[str] = Query(None),
    entity_type: Optional[str] = Query(None),
    entity_types: Optional[str] = Query(None),
    q: Optional[str] = Query(None),
    favorite_only: bool = Query(False),
    crm_linked: Optional[bool] = Query(None),
    limit: int = Query(200, ge=1, le=500),
    offset: int = Query(0, ge=0),
) -> dict[str, Any]:
    join = ""
    if region:
        join = "JOIN export_territories t ON e.territory_code = t.code"
    where_sql, params = _entity_filters(
        country, region, entity_type, entity_types, favorite_only, crm_linked
    )
    if q:
        where_sql += " AND (e.name ILIKE %s OR e.city ILIKE %s)"
        params.extend([f"%{q}%", f"%{q}%"])

    rows = fetch_all(
        f"""
        SELECT e.*,
               cl.name AS crm_client_name,
               (SELECT COUNT(*) FROM export_entity_contacts ec WHERE ec.entity_id = e.id) AS contact_count,
               (SELECT ec.email FROM export_entity_contacts ec WHERE ec.entity_id = e.id AND ec.is_primary = TRUE LIMIT 1) AS primary_email,
               (SELECT ec.phone FROM export_entity_contacts ec WHERE ec.entity_id = e.id AND ec.is_primary = TRUE LIMIT 1) AS primary_phone
        FROM export_market_entities e
        LEFT JOIN clients cl ON cl.id = e.client_id
        {join}
        WHERE {where_sql}
        ORDER BY e.confidence DESC, e.name
        LIMIT %s OFFSET %s
        """,
        tuple(params) + (limit, offset),
    )
    total = fetch_one(
        f"SELECT COUNT(*) AS n FROM export_market_entities e LEFT JOIN clients cl ON cl.id = e.client_id {join} WHERE {where_sql}",
        tuple(params),
    )
    return {"items": rows, "total": int(total["n"] or 0), "limit": limit, "offset": offset}


@router.get("/entities/{entity_id}")
def get_entity(entity_id: int) -> dict[str, Any]:
    row = fetch_one(
        """
        SELECT e.*, cl.name AS crm_client_name, d.title AS crm_deal_title, d.stage AS crm_deal_stage
        FROM export_market_entities e
        LEFT JOIN clients cl ON cl.id = e.client_id
        LEFT JOIN deals d ON d.id = e.deal_id
        WHERE e.id = %s
        """,
        (entity_id,),
    )
    if not row:
        raise HTTPException(404, "Entity not found")
    contacts = fetch_all(
        "SELECT * FROM export_entity_contacts WHERE entity_id = %s ORDER BY is_primary DESC, confidence DESC",
        (entity_id,),
    )
    return _enrich_entity(row, contacts)


@router.get("/contacts")
def list_contacts(
    country: Optional[str] = Query(None),
    entity_type: Optional[str] = Query(None),
    has_email: Optional[bool] = Query(None),
    q: Optional[str] = Query(None),
    limit: int = Query(200, ge=1, le=500),
    offset: int = Query(0, ge=0),
) -> dict[str, Any]:
    clauses = ["1=1"]
    params: list[Any] = []
    if country:
        clauses.append("e.country_iso2 = %s")
        params.append(country.upper())
    if entity_type:
        clauses.append("e.entity_type = %s")
        params.append(entity_type)
    if has_email:
        clauses.append("c.email IS NOT NULL AND c.email <> ''")
    if q:
        clauses.append(
            "(e.name ILIKE %s OR c.email ILIKE %s OR c.name ILIKE %s OR e.city ILIKE %s)"
        )
        like = f"%{q}%"
        params.extend([like, like, like, like])

    where = " AND ".join(clauses)
    rows = fetch_all(
        f"""
        SELECT c.*, e.name AS entity_name, e.entity_type, e.country_iso2, e.city AS entity_city
        FROM export_entity_contacts c
        JOIN export_market_entities e ON e.id = c.entity_id
        WHERE {where}
        ORDER BY e.name, c.is_primary DESC
        LIMIT %s OFFSET %s
        """,
        tuple(params) + (limit, offset),
    )
    total = fetch_one(
        f"""
        SELECT COUNT(*) AS n FROM export_entity_contacts c
        JOIN export_market_entities e ON e.id = c.entity_id
        WHERE {where}
        """,
        tuple(params),
    )
    return {"items": rows, "total": int(total["n"] or 0)}


@router.get("/contacts/export.csv")
def export_contacts_csv(
    country: Optional[str] = Query(None),
    entity_type: Optional[str] = Query(None),
) -> StreamingResponse:
    data = list_contacts(country=country, entity_type=entity_type, limit=5000, offset=0)
    buf = io.StringIO()
    writer = csv.writer(buf)
    writer.writerow(
        ["entity_name", "entity_type", "country", "role", "name", "email", "phone", "mobile", "source", "confidence"]
    )
    for r in data["items"]:
        writer.writerow(
            [
                r.get("entity_name"),
                r.get("entity_type"),
                r.get("country_iso2"),
                r.get("role"),
                r.get("name"),
                r.get("email"),
                r.get("phone"),
                r.get("mobile"),
                r.get("source"),
                r.get("confidence"),
            ]
        )
    buf.seek(0)
    return StreamingResponse(
        iter([buf.getvalue()]),
        media_type="text/csv",
        headers={"Content-Disposition": "attachment; filename=export-intel-contacts.csv"},
    )


@router.post("/entities/{entity_id}/contacts")
def add_contact(entity_id: int, payload: ContactCreateIn) -> dict[str, Any]:
    ent = fetch_one("SELECT id FROM export_market_entities WHERE id = %s", (entity_id,))
    if not ent:
        raise HTTPException(404, "Entity not found")
    row = execute_returning(
        """
        INSERT INTO export_entity_contacts (entity_id, role, name, email, phone, mobile, is_primary, source, confidence)
        VALUES (%s, %s, %s, %s, %s, %s, %s, 'manual', 80)
        RETURNING *
        """,
        (
            entity_id,
            payload.role,
            payload.name,
            payload.email,
            payload.phone,
            payload.mobile,
            payload.is_primary,
        ),
    )
    return dict(row)


@router.get("/caterers/brands")
def list_caterer_brands() -> list[dict[str, Any]]:
    return fetch_all(
        """
        SELECT b.*,
               (SELECT COUNT(*) FROM export_caterer_presence p WHERE p.brand_code = b.code AND p.is_active) AS country_count
        FROM export_caterer_brands b
        ORDER BY b.tier, b.name
        """
    )


@router.get("/caterers/presence")
def list_caterer_presence(country: Optional[str] = Query(None)) -> list[dict[str, Any]]:
    if country:
        return fetch_all(
            """
            SELECT p.*, b.name AS brand_name, b.tier, b.website AS brand_website
            FROM export_caterer_presence p
            JOIN export_caterer_brands b ON b.code = p.brand_code
            WHERE p.country_iso2 = %s AND p.is_active = TRUE
            ORDER BY b.tier, b.name
            """,
            (country.upper(),),
        )
    return fetch_all(
        """
        SELECT p.*, b.name AS brand_name, b.tier, b.website AS brand_website
        FROM export_caterer_presence p
        JOIN export_caterer_brands b ON b.code = p.brand_code
        WHERE p.is_active = TRUE
        ORDER BY p.country_iso2, b.tier, b.name
        """
    )


@router.get("/gov-sources")
def list_gov_sources(country: Optional[str] = Query(None)) -> list[dict[str, Any]]:
    if country:
        return fetch_all(
            "SELECT * FROM export_gov_data_sources WHERE is_active = TRUE AND country_iso2 = %s ORDER BY category, name",
            (country.upper(),),
        )
    return fetch_all(
        "SELECT * FROM export_gov_data_sources WHERE is_active = TRUE ORDER BY country_iso2, category, name"
    )


@router.get("/tenders")
def list_tenders(
    country: Optional[str] = Query(None),
    status: Optional[str] = Query("open"),
    limit: int = Query(100, ge=1, le=200),
) -> dict[str, Any]:
    clauses = ["1=1"]
    params: list[Any] = []
    if country:
        clauses.append("country_iso2 = %s")
        params.append(country.upper())
    if status:
        clauses.append("status = %s")
        params.append(status)
    where = " AND ".join(clauses)
    rows = fetch_all(
        f"SELECT * FROM export_tenders WHERE {where} ORDER BY created_at DESC LIMIT %s",
        tuple(params) + (limit,),
    )
    total = fetch_one(f"SELECT COUNT(*) AS n FROM export_tenders WHERE {where}", tuple(params))
    return {"items": rows, "total": int(total["n"] or 0)}


@router.get("/map/bundle")
def map_bundle(
    country: Optional[str] = Query(None),
    region: Optional[str] = Query(None),
    entity_type: Optional[str] = Query(None),
    entity_types: Optional[str] = Query(None),
    q: Optional[str] = Query(None),
    halal_min: Optional[float] = Query(None, ge=0, le=100),
    favorite_only: bool = Query(False),
    crm_linked: Optional[bool] = Query(None),
) -> dict[str, Any]:
    join = ""
    if region:
        join = "JOIN export_territories t ON e.territory_code = t.code"
    filter_sql, params = _entity_filters(
        country, region, entity_type, entity_types, favorite_only, crm_linked
    )
    where = f"e.lat IS NOT NULL AND e.lon IS NOT NULL AND {filter_sql}"
    if q:
        where += " AND (e.name ILIKE %s OR e.city ILIKE %s)"
        params.extend([f"%{q}%", f"%{q}%"])

    entities = fetch_all(
        f"""
        SELECT e.id, e.name, e.entity_type, e.country_iso2, e.lat, e.lon,
               e.confidence, e.pipeline_stage, e.city, e.address_line,
               e.email, e.phone, e.website, e.volume_band, e.source,
               e.is_favorite, e.client_id, e.deal_id, e.crm_pushed_at,
               e.product_interest, e.halal_cert_notes,
               (SELECT c.email FROM export_entity_contacts c
                WHERE c.entity_id = e.id AND c.email IS NOT NULL AND c.email <> ''
                ORDER BY c.is_primary DESC NULLS LAST LIMIT 1) AS contact_email,
               (SELECT c.phone FROM export_entity_contacts c
                WHERE c.entity_id = e.id AND c.phone IS NOT NULL AND c.phone <> ''
                ORDER BY c.is_primary DESC NULLS LAST LIMIT 1) AS contact_phone,
               (SELECT COUNT(*) FROM export_entity_contacts c WHERE c.entity_id = e.id) AS contact_count
        FROM export_market_entities e
        {join}
        WHERE {where}
        LIMIT 20000
        """,
        tuple(params) if params else None,
    )

    scored: list[tuple[dict[str, Any], float, list[str]]] = []
    halal_by_country: dict[str, dict[str, Any]] = {}
    for row in entities:
        if row.get("lat") is None or row.get("lon") is None:
            continue
        h_score, h_reasons = score_entity(row)
        iso = row.get("country_iso2") or ""
        bucket = halal_by_country.setdefault(
            iso,
            {"country_iso2": iso, "market_score": COUNTRY_HALAL_MARKET.get(iso, 40), "entity_count": 0, "avg_halal_score": 0.0, "hot_count": 0},
        )
        bucket["entity_count"] += 1
        bucket["avg_halal_score"] += h_score
        if h_score >= 75:
            bucket["hot_count"] += 1
        scored.append((row, h_score, h_reasons))

    for bucket in halal_by_country.values():
        n = bucket["entity_count"] or 1
        bucket["avg_halal_score"] = round(bucket["avg_halal_score"] / n, 1)
        bucket["combined_score"] = round(bucket["market_score"] * 0.4 + bucket["avg_halal_score"] * 0.6, 1)

    if halal_min is not None:
        scored = [s for s in scored if s[1] >= halal_min]

    scored.sort(key=lambda x: x[1], reverse=True)

    features = [
        {
            "type": "Feature",
            "geometry": {"type": "Point", "coordinates": [row["lon"], row["lat"]]},
            "properties": {
                "id": row["id"],
                "name": row["name"],
                "entity_type": row["entity_type"],
                "country_iso2": row["country_iso2"],
                "confidence": row["confidence"],
                "pipeline_stage": row["pipeline_stage"],
                "city": row.get("city"),
                "address_line": row.get("address_line"),
                "email": row.get("email") or row.get("contact_email"),
                "phone": row.get("phone") or row.get("contact_phone"),
                "website": row.get("website"),
                "volume_band": row.get("volume_band"),
                "source": row.get("source"),
                "contact_count": int(row.get("contact_count") or 0),
                "halal_score": h_score,
                "halal_tier": halal_pin_tier(h_score),
                "halal_reasons": h_reasons[:4],
                "is_favorite": bool(row.get("is_favorite")),
                "client_id": row.get("client_id"),
                "deal_id": row.get("deal_id"),
            },
        }
        for row, h_score, h_reasons in scored
    ]

    top_markets = sorted(halal_by_country.values(), key=lambda x: x["combined_score"], reverse=True)[:15]
    top_entities = [
        {
            "id": row["id"],
            "name": row["name"],
            "entity_type": row["entity_type"],
            "country_iso2": row["country_iso2"],
            "city": row.get("city"),
            "address_line": row.get("address_line"),
            "email": row.get("email") or row.get("contact_email"),
            "phone": row.get("phone") or row.get("contact_phone"),
            "website": row.get("website"),
            "halal_score": h_score,
            "halal_reason_labels": _halal_reason_labels(h_reasons),
            "is_favorite": bool(row.get("is_favorite")),
            "client_id": row.get("client_id"),
            "has_contact": bool(
                row.get("email") or row.get("contact_email")
                or row.get("phone") or row.get("contact_phone")
                or int(row.get("contact_count") or 0) > 0
            ),
        }
        for row, h_score, h_reasons in scored[:25]
    ]

    density_rows = fetch_all(
        """
        SELECT country_iso2, COUNT(*) AS n
        FROM export_market_entities
        WHERE lat IS NOT NULL AND lon IS NOT NULL
        GROUP BY country_iso2
        """
    )
    trade_choropleth = {r["country_iso2"]: r["n"] for r in density_rows}

    stats = export_stats(country=country, region=region)
    return {
        "entities": {"type": "FeatureCollection", "features": features},
        "trade_choropleth": trade_choropleth,
        "halal_by_country": halal_by_country,
        "meta": {
            "entity_count": len(features),
            "countries_with_trade": len(trade_choropleth),
            "halal_min": halal_min,
            "top_markets": top_markets,
            "top_halal_entities": top_entities,
            "phase": "B",
            "note": "Halal-score: type + land + naam/signaal + contact",
        },
        "stats": stats,
    }


@router.get("/halal/markets")
def halal_markets(
    region: Optional[str] = Query(None),
    country: Optional[str] = Query(None),
    limit: int = Query(30, ge=1, le=100),
) -> dict[str, Any]:
    join = ""
    where = "e.lat IS NOT NULL"
    params: list[Any] = []
    if region:
        join = "JOIN export_territories t ON e.territory_code = t.code"
        where += " AND t.region_code = %s"
        params.append(region)
    if country:
        where += " AND e.country_iso2 = %s"
        params.append(country.upper())

    rows = fetch_all(
        f"""
        SELECT e.id, e.name, e.entity_type, e.country_iso2, e.city,
               e.confidence, e.product_interest, e.halal_cert_notes, e.email,
               (SELECT COUNT(*) FROM export_entity_contacts c WHERE c.entity_id = e.id) AS contact_count
        FROM export_market_entities e
        {join}
        WHERE {where}
        LIMIT 15000
        """,
        tuple(params) if params else None,
    )
    by_country: dict[str, dict[str, Any]] = {}
    top_entities: list[dict[str, Any]] = []
    for row in rows:
        h_score, reasons = score_entity(row)
        iso = row.get("country_iso2") or ""
        bucket = by_country.setdefault(
            iso,
            {"country_iso2": iso, "market_score": COUNTRY_HALAL_MARKET.get(iso, 40), "entities": 0, "score_sum": 0.0, "hot": 0},
        )
        bucket["entities"] += 1
        bucket["score_sum"] += h_score
        if h_score >= 75:
            bucket["hot"] += 1
        top_entities.append({
            "id": row["id"],
            "name": row["name"],
            "entity_type": row["entity_type"],
            "country_iso2": iso,
            "city": row.get("city"),
            "halal_score": h_score,
            "halal_reasons": reasons[:3],
        })

    markets = []
    for iso, b in by_country.items():
        n = b["entities"] or 1
        avg = round(b["score_sum"] / n, 1)
        markets.append({
            "country_iso2": iso,
            "market_score": b["market_score"],
            "entity_count": b["entities"],
            "avg_halal_score": avg,
            "hot_entities": b["hot"],
            "combined_score": round(b["market_score"] * 0.4 + avg * 0.6, 1),
        })
    markets.sort(key=lambda x: x["combined_score"], reverse=True)
    top_entities.sort(key=lambda x: x["halal_score"], reverse=True)

    return {
        "markets": markets[:limit],
        "top_entities": top_entities[:50],
        "total_entities": len(rows),
    }


@router.post("/entities/favorites")
def set_favorites(payload: FavoritesIn) -> dict[str, Any]:
    if not payload.entity_ids:
        raise HTTPException(400, "entity_ids required")
    fav_val = payload.favorite
    execute(
        """
        UPDATE export_market_entities
        SET is_favorite = %s,
            favorited_at = CASE WHEN %s THEN NOW() ELSE NULL END,
            updated_at = NOW()
        WHERE id = ANY(%s)
        """,
        (fav_val, fav_val, payload.entity_ids),
    )
    return {"ok": True, "updated": len(payload.entity_ids), "favorite": fav_val}


@router.get("/entities/favorites")
def list_favorite_entities(
    limit: int = Query(200, ge=1, le=500),
    country: Optional[str] = Query(None),
    region: Optional[str] = Query(None),
) -> dict[str, Any]:
    return list_entities(
        country=country,
        region=region,
        favorite_only=True,
        limit=limit,
        offset=0,
    )


@router.post("/crm/push")
def crm_push(payload: CrmPushIn) -> dict[str, Any]:
    if not payload.entity_ids:
        raise HTTPException(400, "entity_ids required")
    if len(payload.entity_ids) > 100:
        raise HTTPException(400, "max 100 entities per request")
    return push_entities_to_crm(
        payload.entity_ids,
        create_deals=payload.create_deals,
        client_stage=payload.client_stage,
        deal_stage=payload.deal_stage,
    )


@router.get("/crm/pipeline")
def crm_pipeline(
    limit: int = Query(100, ge=1, le=500),
    country: Optional[str] = Query(None),
    region: Optional[str] = Query(None),
) -> dict[str, Any]:
    join = ""
    if region:
        join = "JOIN export_territories t ON e.territory_code = t.code"
    where_sql, params = _entity_filters(country, region, None, None, False, True)
    rows = fetch_all(
        f"""
        SELECT e.id, e.name, e.entity_type, e.country_iso2, e.city,
               e.pipeline_stage, e.crm_pushed_at, e.is_favorite,
               e.client_id, e.deal_id, e.email, e.phone,
               cl.name AS crm_client_name, cl.stage AS crm_client_stage,
               d.title AS crm_deal_title, d.stage AS crm_deal_stage
        FROM export_market_entities e
        LEFT JOIN clients cl ON cl.id = e.client_id
        LEFT JOIN deals d ON d.id = e.deal_id
        {join}
        WHERE {where_sql}
        ORDER BY e.crm_pushed_at DESC NULLS LAST, e.updated_at DESC
        LIMIT %s
        """,
        tuple(params) + (limit,),
    )
    return {"items": rows, "total": len(rows)}


@router.post("/sync/contacts")
def api_sync_contacts(payload: SyncRequestIn) -> dict[str, Any]:
    try:
        result = sync_contacts(payload.country_iso2, payload.region_code)
        return {"status": "completed", "phase": "B", "result": result}
    except Exception as exc:
        raise HTTPException(500, str(exc)) from exc


@router.post("/sync/caterers")
def api_sync_caterers(payload: SyncRequestIn) -> dict[str, Any]:
    try:
        result = sync_caterers(payload.country_iso2, payload.region_code)
        return {"status": "completed", "phase": "B", "result": result}
    except Exception as exc:
        raise HTTPException(500, str(exc)) from exc


@router.post("/sync/distributors")
def api_sync_distributors(payload: SyncRequestIn) -> dict[str, Any]:
    try:
        result = sync_distributors(payload.country_iso2, payload.region_code)
        return {"status": "completed", "phase": "B", "result": result}
    except Exception as exc:
        raise HTTPException(500, str(exc)) from exc


@router.post("/sync/customers")
def api_sync_customers(payload: SyncRequestIn) -> dict[str, Any]:
    if not payload.country_iso2 and not payload.region_code:
        raise HTTPException(400, "country_iso2 or region_code required")
    try:
        result = sync_customers(payload.country_iso2, payload.region_code)
        return {"status": "completed", "phase": "B", "result": result}
    except Exception as exc:
        raise HTTPException(500, str(exc)) from exc


@router.post("/sync/tenders")
def api_sync_tenders() -> dict[str, Any]:
    try:
        result = sync_ted_tenders()
        return {"status": "completed", "phase": "B", "result": result}
    except Exception as exc:
        raise HTTPException(500, str(exc)) from exc


@router.post("/sync/all")
def api_sync_all(payload: SyncRequestIn = SyncRequestIn()) -> dict[str, Any]:
    try:
        result = sync_all_priority(max_priority=payload.max_priority, region_code=payload.region_code)
        return {"status": "completed", "phase": "B", "result": result}
    except Exception as exc:
        raise HTTPException(500, str(exc)) from exc


@router.post("/sync/world")
def api_sync_world(payload: SyncRequestIn = SyncRequestIn()) -> dict[str, Any]:
    """Sync Europe, Middle East, Africa, Americas — long running."""
    try:
        result = sync_world_regions(max_priority=payload.max_priority)
        return {"status": "completed", "phase": "B", "result": result}
    except Exception as exc:
        raise HTTPException(500, str(exc)) from exc


@router.post("/sync/region")
def api_sync_region(payload: SyncRequestIn) -> dict[str, Any]:
    if not payload.region_code:
        raise HTTPException(400, "region_code required")
    try:
        result = sync_all_priority(max_priority=payload.max_priority, region_code=payload.region_code)
        return {"status": "completed", "phase": "B", "result": result}
    except Exception as exc:
        raise HTTPException(500, str(exc)) from exc
