import csv
import requests
import time
from datetime import datetime

# ==========================
# 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"   # <<< replace me!

CSV_FILE = "Contacts_Matched_test.csv"
FAILED_LOG_FILE = "failed_updates.csv"

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",
}

# ==========================
# SAFE REQUEST
# ==========================
def safe_request(method, url, **kwargs):
    for attempt in range(MAX_RETRIES):
        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.3 * (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


# ==========================
# CACHE STRUCTURES
# ==========================
contact_id_cache = {}
account_id_cache = {}
contact_account_links = {}    # contact_id -> [account_ids]


# ==========================
# LOOKUP FUNCTIONS
# ==========================
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 new
    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)


# ==========================
# OPTIMIZED ACCOUNT LINKER
# ==========================
def link_account(cid, new_aid):
    if not cid or not new_aid:
        return

    existing_accounts = contact_account_links.get(cid, [])

    # Already correct → skip expensive API calls
    if str(new_aid) in existing_accounts:
        return

    # Delete old account links
    for old_aid in existing_accounts:
        if old_aid != str(new_aid):
            # DELETE only links belonging to this contact+old account
            # Fetch link IDs (FAST)
            r = safe_request(
                "GET",
                f"{API_URL}/accountContacts",
                params={"filters[contact]": cid}
            )
            if r.status_code == 200:
                for link in r.json().get("accountContacts", []):
                    if link.get("account") == old_aid:
                        link_id = link.get("id")
                        safe_request("DELETE", f"{API_URL}/accountContacts/{link_id}")

    # Now create new link
    r2 = safe_request(
        "POST",
        f"{API_URL}/accountContacts",
        json={"accountContact": {"contact": str(cid), "account": str(new_aid)}}
    )

    if r2.status_code in (200, 201):
        contact_account_links[cid] = [str(new_aid)]
    else:
        print(f"⚠ Could not link {cid} → {new_aid}: {r2.status_code} {r2.text[:200]}")


# ==========================
# MAIN
# ==========================
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")

    # Sort rows by company for stable batching
    rows.sort(key=lambda r: r.get(ACCOUNT_COL, "") or "")

    # Phase 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")

    # Phase 2: Prefetch contact IDs
    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)

    # Phase 3: Prefetch account IDs
    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)

    # Phase 4: Prefetch existing account links
    print("\n🔗 Prefetching existing account links...")
    cid_list = [cid for cid in contact_id_cache.values() if cid]
    iterator = tqdm(cid_list) if HAS_TQDM else cid_list

    for cid in iterator:
        r = safe_request(
            "GET",
            f"{API_URL}/accountContacts",
            params={"filters[contact]": cid}
        )
        if r.status_code == 200:
            accounts = [link.get("account") for link in r.json().get("accountContacts", [])]
            contact_account_links[cid] = accounts
        else:
            contact_account_links[cid] = []

    # Phase 5: Process updates
    print("\n🛠 Updating contacts...")
    failures = []
    ok = skip = fail = 0

    iterator = tqdm(range(total)) if HAS_TQDM else range(total)

    for idx in iterator:
        row = rows[idx]

        # Throttle every 100 contacts
        if idx > 0 and idx % 100 == 0:
            time.sleep(1)

        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

        # Contact update
        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})

        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

        # Account linking optimization
        company = (row.get(ACCOUNT_COL, "") or "").strip()
        if company:
            aid = account_id_cache.get(company)
            if aid:
                link_account(cid, aid)

        ok += 1

    # 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("🎉 Completed with optimized account linking + smart batching!")