#!/usr/bin/env python3
"""Export the IAA TRANSPORTATION 2026 exhibitor directory.

The public directory at exhibitors.iaa-transportation.com is a Corussoft EventGuide
single-page app. Its data comes from the endpoints used below; the access token is the
public `configuration.sotAccessToken` served in /branding/branding.js.

Usage:
    python3 fetch_exhibitors.py OUTPUT_DIR

Writes exhibitors.csv and products.csv into OUTPUT_DIR.
"""

import csv
import json
import re
import sys
import urllib.parse
import urllib.request
from concurrent.futures import ThreadPoolExecutor

BASE = "https://live.messebackend.aws.corussoft.de/webservice"
BRANDING = "https://exhibitors.iaa-transportation.com/branding/branding.js"
TOPIC = "2024_iaa_transportation"
APP_URL = "https://exhibitors.iaa-transportation.com"


def access_token():
    raw = urllib.request.urlopen(BRANDING, timeout=30).read().decode("utf-8")
    blob = raw[raw.index("{"): raw.rindex("}") + 1]
    return json.loads(blob)["configuration"]["sotAccessToken"]


def call(endpoint, token, **params):
    body = {
        "topic": TOPIC,
        "os": "web",
        "appUrl": APP_URL,
        "lang": "en",
        "apiVersion": "52",
        "timezoneOffset": "0",
    }
    body.update({k: str(v) for k, v in params.items()})
    request = urllib.request.Request(
        f"{BASE}/{endpoint}",
        data=urllib.parse.urlencode(body).encode("utf-8"),
        headers={
            "beConnectionToken": token,
            "EC-Client": "EventGuide/web[52]",
            "EC-Client-Branding": TOPIC,
            "Accept": "application/json",
            "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        },
    )
    with urllib.request.urlopen(request, timeout=60) as response:
        return json.load(response)


def search_all(token, filterlist, page=100):
    start, out = 0, []
    while True:
        result = call(
            "search", token,
            filterlist=filterlist, startresultrow=start, numresultrows=page, order="lexic",
        )
        out.extend(result.get("entities", []))
        nxt = result.get("nextStartIndex")
        if nxt is None or start >= result.get("count", 0):
            return out
        start = nxt


def flatten(text):
    return re.sub(r"\s+", " ", text or "").strip()


SEP = " || "


def product_groups(entity):
    """(keys, names) of the IAA product groups an exhibitor is filed under."""
    categories = [
        category
        for category in entity.get("categories") or []
        if str(category.get("key", "")).startswith("produktgruppen26_")
    ]
    return (
        [category["key"] for category in categories],
        [category["name"] for category in categories],
    )


def stand_labels(stands):
    return [
        f"{stand.get('hallName', '')} \u00b7 {stand.get('standNameShort', '')}".strip(" \u00b7")
        for stand in stands
    ]


def main(out_dir):
    token = access_token()

    organizations = search_all(token, "entity_orga")
    with ThreadPoolExecutor(max_workers=6) as pool:
        details = list(pool.map(
            lambda org: call(
                "companydetails", token,
                organizationid=org["id"], hideNewsdata="true", showAll="true",
            ),
            organizations,
        ))

    with open(f"{out_dir}/exhibitors.csv", "w", newline="", encoding="utf-8") as handle:
        writer = csv.writer(handle, lineterminator="\n")
        writer.writerow([
            "id", "name", "company_name", "country", "city", "halls", "stands",
            "website", "email", "phone", "founding_year", "industry",
            "product_group_keys", "product_groups", "teaser", "description",
        ])
        for org, detail in zip(organizations, details):
            stands = detail.get("stands") or org.get("stands") or []
            keys, names = product_groups(org)
            writer.writerow([
                org["id"],
                org.get("name", ""),
                detail.get("companyName", ""),
                org.get("country", ""),
                org.get("city", ""),
                SEP.join(sorted({s.get("hallName", "") for s in stands})),
                SEP.join(stand_labels(stands)),
                detail.get("web", ""),
                detail.get("email", ""),
                detail.get("phone", ""),
                detail.get("foundingYear", ""),
                detail.get("industry", ""),
                SEP.join(keys),
                SEP.join(names),
                flatten(org.get("teaser")),
                flatten((detail.get("description") or {}).get("text")),
            ])

    products = search_all(token, "entity_prod")
    with open(f"{out_dir}/products.csv", "w", newline="", encoding="utf-8") as handle:
        writer = csv.writer(handle, lineterminator="\n")
        writer.writerow(["id", "name", "organization", "halls", "stands", "categories", "teaser"])
        for product in products:
            stands = product.get("stands") or []
            writer.writerow([
                product["id"],
                product.get("name", ""),
                (product.get("organization") or {}).get("name", ""),
                SEP.join(sorted({s.get("hallName", "") for s in stands})),
                SEP.join(stand_labels(stands)),
                SEP.join(c["name"] for c in product.get("categories") or []),
                flatten(product.get("descriptionTeaser")),
            ])


if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else ".")
