import csv
import requests
import time
from datetime import datetime

# ==========================
# Try to import tqdm (safe)
# ==========================
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"
CSV_FILE = "Contacts_Matched_test.csv"
FAILED_LOG_FILE = "failed_updates.csv"

MAX_CALLS_PER_SECOND = 5
RETRY_STATUS = {429, 500, 502, 503, 504}
MAX_RETRIES = 5

EMAIL_COL = "Email"
ACCOUNT_COL = "company"

CUSTOM_FIELD_IDS = {
    "id": 17, "cancel date": 79, "newmbr date": 86, "paid thru": 264,
    "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
}

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

# ==========================
# RATE LIMIT + SAFE REQUEST
# ==========================
last_calls = []

def rate_limited_request():
    global last_calls
    now = time.time()
    last_calls.append(now)
    last_calls[:] = [t for t in last_calls if now - t < 1]

    if len(last_calls) >= MAX_CALLS_PER_SECOND:
        sleep_time = 1 - (now - last_calls[0])
        if sleep_time > 0:
            time.sleep(sleep_time)

def safe_request(method, url, **kwargs):
    for attempt in range(MAX_RETRIES):
        rate_limited_request()
        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


# ==========================
# SMART BATCH CACHES
# ==========================
contact_id_cache = {}
account_id_cache = {}
account_link_cache = set()


# ==========================
# AC LOOKUPS
# ==========================
def fetch_contact_id(email):
    r = safe_request("GET", f"{API_URL}/contacts", params={"email": email})
    if r.status_code != 200:
        return None
    contacts = r.json().get("contacts", [])
    return contacts[0]["id"] if contacts else None

def fetch_account_id(name):
    r = safe_request("GET", f"{API_URL}/accounts", params={"search": name})
    if r.status_code == 200:
        for a in r.json().get("accounts", []):
            if a.get("name", "").lower() == name.lower():
                return a["id"]

    # Create
    r = safe_request("POST", f"{API_URL}/accounts", json={"account": {"name": name}})
    if r.status_code in (200, 201):
        return r.json()["account"]["id"]

    return None


def update_contact(cid, core, custom):
    payload = {"contact": core}
    if custom:
        payload["contact"]["fieldValues"] = custom
    return safe_request("PUT", f"{API_URL}/contacts/{cid}", json=payload)


def link_account(cid, aid):
    key = (cid, aid)
    if key in account_link_cache:
        return

    r = safe_request("GET", f"{API_URL}/accountContacts",
                     params={"filters[contact]": cid})

    if r.status_code == 200:
        for l in r.json().get("accountContacts", []):
            if l.get("account") == str(aid):
                account_link_cache.add(key)
                return

    safe_request("POST", f"{API_URL}/accountContacts",
                 json={"accountContact": {"contact": cid, "account": aid}})
    account_link_cache.add(key)


# ==========================
# MAIN — SMART BATCH PROCESSING
# ==========================
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:,} contacts.\n")

    # --------------------------
    # STEP 1. BUILD UNIQUE SETS
    # --------------------------
    all_emails = {row.get(EMAIL_COL, "").strip().lower() for row in rows if row.get(EMAIL_COL)}
    all_companies = {row.get(ACCOUNT_COL, "").strip() for row in rows if row.get(ACCOUNT_COL)}

    print(f"Unique emails: {len(all_emails):,}")
    print(f"Unique companies: {len(all_companies):,}\n")

    # --------------------------
    # STEP 2. BATCH CONTACT LOOKUP
    # --------------------------
    print("📡 Fetching contact IDs...")

    iterator = tqdm(all_emails) if HAS_TQDM else all_emails
    for email in iterator:
        contact_id_cache[email] = fetch_contact_id(email)

    # --------------------------
    # STEP 3. BATCH ACCOUNT LOOKUP
    # --------------------------
    print("\n🏢 Fetching account IDs...")

    iterator = tqdm(all_companies) if HAS_TQDM else all_companies
    for comp in iterator:
        account_id_cache[comp] = fetch_account_id(comp)

    # --------------------------
    # STEP 4. UPDATE CONTACTS
    # --------------------------
    print("\n🛠 Updating contacts...")

    failures = []
    ok = skip = fail = 0

    iterator = tqdm(rows) if HAS_TQDM else rows

    for row in iterator:
        email = row.get(EMAIL_COL, "").strip().lower()
        if not email:
            skip += 1
            continue

        cid = contact_id_cache.get(email)
        if not cid:
            skip += 1
            continue

        core = {"email": email}

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

        r = update_contact(cid, core, custom)
        if r.status_code not in (200, 201):
            failures.append({"email": email, "error": r.status_code, **row})
            fail += 1
            continue

        company = row.get(ACCOUNT_COL, "").strip()
        if company:
            aid = account_id_cache.get(company)
            if aid:
                link_account(cid, aid)

        ok += 1

    # --------------------------
    # STEP 5: SAVE FAILURES
    # --------------------------
    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("🎉 Finished with smart batching + progress bar!")