import csv
import requests
import time
import threading
from pathlib import Path
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed

# ====================================================
# Optional tqdm progress bar
# ====================================================
try:
    from tqdm import tqdm
    HAS_TQDM = True
except:
    HAS_TQDM = False

# ====================================================
# CONFIG
# ====================================================
API_URL = "https://inp.api-us1.com/api/3"
API_KEY = "0493c4d2d33662a670c7b347077c74dce411fe0261fb00affa7e8cdd34041ec16861355a"

base = Path(__file__).resolve().parent
CSV_FILE = base / "Contacts_Matched.csv"
FAILED_LOG_FILE = base / "failed_updates.csv"

# AC Retry Codes
RETRY_STATUS = {429, 500, 502, 503, 504}
MAX_RETRIES = 5

EMAIL_COL = "Email"

CUSTOM_FIELD_IDS = {
    "id": 17, "cancel date": 79, "newmbr date": 86, "paid thru": 264, "Company paid thru": 279, "Company cancel date": 307, "Company status": 360,
    "co id": 16, "cert status": 183, "pcl status": 287,
    "pcl completion date": 289, "pf1 status": 291,
    "pf1 completion date": 292, "pf2 status": 293,
    "pf2 completion date": 294, "intro to accounting status": 295,
    "intro to accounting completion date": 296, "state province": 4,
    "c p a   certification": 182, "last  act": 278, "french": 7,
    "last  infoline": 276, "last conf": 277,
    "Processed member type": 18, "new lead status": 2,
    "New DO ID": 20, "Number of Passed Courses": 297,
    "trc": 300, "wera": 301, "pcp we": 302
}

HEADERS = {
    "Api-Token": API_KEY,
    "Content-Type": "application/json",
}

# ====================================================
# RATE LIMITER (token bucket: max 5 requests/sec)
# ====================================================
MAX_CALLS_PER_SECOND = 5
rate_limiter = threading.Semaphore(MAX_CALLS_PER_SECOND)

def refill_tokens():
    while True:
        while rate_limiter._value < MAX_CALLS_PER_SECOND:
            rate_limiter.release()
        time.sleep(1)

threading.Thread(target=refill_tokens, daemon=True).start()

# ====================================================
# SAFE REQUEST WITH RETRIES + RATE LIMITING
# ====================================================
def safe_request(method, url, **kwargs):
    for attempt in range(MAX_RETRIES):

        rate_limiter.acquire()

        try:
            r = requests.request(method, url, headers=HEADERS, timeout=20, **kwargs)
        except:
            time.sleep(1)
            continue

        if r.status_code in RETRY_STATUS:
            time.sleep(1.2 * (attempt + 1))
            continue

        return r

    return r

# ====================================================
# HELPERS
# ====================================================
def _safe_date(v):
    if not v or not v.strip():
        return ""
    s = v.strip()
    for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%m/%d/%Y", "%d/%m/%Y"):
        try:
            return datetime.strptime(s, fmt).strftime("%Y-%m-%d")
        except:
            pass
    return s

# ====================================================
# THREAD WORKER (NO CONTACT ID)
# ====================================================
def process_row(row):
    email = (row.get(EMAIL_COL, "") or "").strip().lower()
    if not email:
        return ("skip", email)

    core = {"email": email}
    custom = []

    for col, fid in CUSTOM_FIELD_IDS.items():
        val = (row.get(col, "") or "").strip()
        if val:
            if col.lower() == "paid thru":
                val = _safe_date(val)
            custom.append({"field": str(fid), "value": val})

    # BUILD CORRECT AC PAYLOAD
    payload = {"contact": core}
    if custom:
        payload["contact"]["fieldValues"] = custom   # <-- FIXED

    r = safe_request("POST", f"{API_URL}/contact/sync", json=payload)

    if r.status_code not in (200, 201):
        return ("fail", email, r.status_code, row)

    return ("ok", email)

# ====================================================
# MAIN PROGRAM
# ====================================================
if __name__ == "__main__":

    print("📥 Loading CSV...")
    with open(CSV_FILE, newline='', encoding="utf-8-sig") as f:
        rows = list(csv.DictReader(f))

    total = len(rows)
    print(f"Found {total:,} rows.")

    print("\n🛠 Updating contacts with multithreading...")

    failures = []
    ok = skip = fail = 0

    NUM_THREADS = 5   # Safe on local machine

    with ThreadPoolExecutor(max_workers=NUM_THREADS) as executor:
        futures = [executor.submit(process_row, row) for row in rows]

        iterator = tqdm(as_completed(futures), total=total) if HAS_TQDM else as_completed(futures)

        for f in iterator:
            res = f.result()

            if res[0] == "ok":
                ok += 1
            elif res[0] == "skip":
                skip += 1
            else:
                fail += 1
                failures.append({
                    "email": res[1],
                    "error": res[2],
                    **res[3]
                })

    if failures:
        with open(FAILED_LOG_FILE, "w", newline='', encoding="utf-8-sig") as f:
            writer = csv.DictWriter(f, fieldnames=failures[0].keys())
            writer.writeheader()
            writer.writerows(failures)
        print(f"\n❌ Logged {len(failures)} failures → {FAILED_LOG_FILE}")

    print("\n==============================")
    print(f"✅ Success: {ok}")
    print(f"⚠️ Skipped: {skip}")
    print(f"❌ Failed: {fail}")
    print("==============================")
    print("🎉 Multithreaded UPSERT complete — NO ID lookup needed!")