#!/usr/bin/env python3
"""
Rename all Customer IDs to CUST-{legacy_customer_id}.

Handles all FK references across the entire ERPNext database:
  - Sales Invoice, Payment Entry, GL Entry, PLE, Issues, Locations,
    Subscriptions, Equipment, Dispatch Jobs, Dynamic Links, Comments, etc.

Approach:
  1. Build old_name → new_name mapping from legacy_customer_id
  2. Create temp mapping table in PostgreSQL
  3. Bulk UPDATE each FK table using JOIN to mapping table
  4. Rename Customer records themselves
  5. Set naming series counter for new customers
  6. Verify all references updated

Run inside erpnext-backend-1:
  /home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/rename_customers.py [--dry-run]

Estimated time: 5-10 minutes (millions of rows to update)
"""
import sys
import os
import time
from datetime import datetime, timezone

os.chdir("/home/frappe/frappe-bench/sites")
import frappe

frappe.init(site="erp.gigafibre.ca", sites_path=".")
frappe.connect()
frappe.local.flags.ignore_permissions = True
print(f"Connected: {frappe.local.site}")

DRY_RUN = "--dry-run" in sys.argv
# Use legacy customer_id as the new name: CUST-{legacy_customer_id}
USE_LEGACY_CODE = "--legacy-code" in sys.argv
# Use zero-padded numeric: CUST-{legacy_account_id:015d}
USE_NUMERIC = "--numeric" in sys.argv

if not USE_LEGACY_CODE and not USE_NUMERIC:
    USE_LEGACY_CODE = True  # default

if DRY_RUN:
    print("*** DRY RUN — no changes will be written ***")

mode_label = "legacy_customer_id" if USE_LEGACY_CODE else "zero-padded numeric"
print(f"Naming mode: {mode_label}")


# ═══════════════════════════════════════════════════════════════
# STEP 1: BUILD MAPPING
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 1: BUILD NAME MAPPING")
print("=" * 70)
t0 = time.time()

customers = frappe.db.sql("""
    SELECT name, legacy_account_id, legacy_customer_id, customer_name
    FROM "tabCustomer"
    ORDER BY legacy_account_id
""", as_dict=True)

mapping = {}  # old_name → new_name
collisions = []
new_names_used = set()

for c in customers:
    old_name = c["name"]

    if USE_LEGACY_CODE:
        cid = c.get("legacy_customer_id") or ""
        if cid:
            new_name = f"CUST-{cid}"
        else:
            # Fallback to zero-padded account ID
            new_name = f"CUST-{c['legacy_account_id']:015d}"
    else:
        new_name = f"CUST-{c['legacy_account_id']:015d}"

    # Check for collision
    if new_name in new_names_used:
        collisions.append((old_name, new_name, c["customer_name"]))
        # Append account ID to disambiguate
        new_name = f"{new_name}-{c['legacy_account_id']}"

    new_names_used.add(new_name)

    if old_name != new_name:
        mapping[old_name] = new_name

# Stats
unchanged = len(customers) - len(mapping)
print(f"  Total customers: {len(customers)}")
print(f"  Will rename: {len(mapping)}")
print(f"  Already correct: {unchanged}")
print(f"  Collisions resolved: {len(collisions)}")

if collisions:
    print(f"  Collision examples:")
    for old, new, name in collisions[:5]:
        print(f"    {old} → {new} ({name})")

# Show sample mappings
print(f"\n  Sample renames:")
for old, new in list(mapping.items())[:10]:
    cust = next((c for c in customers if c["name"] == old), {})
    print(f"    {old:25s} → {new:35s}  ({cust.get('customer_name', '')})")

if DRY_RUN:
    print(f"\n  [{time.time()-t0:.1f}s]")
    print("\n*** DRY RUN complete — no changes made ***")
    sys.exit(0)


# ═══════════════════════════════════════════════════════════════
# STEP 2: CREATE TEMP MAPPING TABLE
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 2: CREATE TEMP MAPPING TABLE")
print("=" * 70)

frappe.db.sql('DROP TABLE IF EXISTS _customer_rename_map')
frappe.db.sql("""
    CREATE TEMP TABLE _customer_rename_map (
        old_name VARCHAR(140) PRIMARY KEY,
        new_name VARCHAR(140) NOT NULL
    )
""")

# Insert in batches
batch = []
for old, new in mapping.items():
    batch.append((old, new))
    if len(batch) >= 1000:
        frappe.db.sql(
            "INSERT INTO _customer_rename_map (old_name, new_name) VALUES " +
            ",".join(["(%s, %s)"] * len(batch)),
            [v for pair in batch for v in pair]
        )
        batch = []
if batch:
    frappe.db.sql(
        "INSERT INTO _customer_rename_map (old_name, new_name) VALUES " +
        ",".join(["(%s, %s)"] * len(batch)),
        [v for pair in batch for v in pair]
    )

frappe.db.commit()
print(f"  Inserted {len(mapping)} mappings into temp table")


# ═══════════════════════════════════════════════════════════════
# STEP 3: UPDATE ALL FK REFERENCES
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 3: UPDATE FK REFERENCES")
print("=" * 70)

# All tables with customer references: (table, column, extra_where)
fk_tables = [
    ("tabSales Invoice", "customer", ""),
    ("tabPayment Entry", "party", ""),
    ("tabGL Entry", "party", ""),
    ("tabPayment Ledger Entry", "party", ""),
    ("tabIssue", "customer", ""),
    ("tabService Location", "customer", ""),
    ("tabService Subscription", "customer", ""),
    ("tabSubscription", "party", ""),
    ("tabService Equipment", "customer", ""),
    ("tabDispatch Job", "customer", ""),
    ("tabDynamic Link", "link_name", "AND t.link_doctype = 'Customer'"),
    ("tabComment", "reference_name", "AND t.reference_doctype = 'Customer'"),
    ("tabCommunication", "reference_name", "AND t.reference_doctype = 'Customer'"),
    # Version log
    ("tabVersion", "docname", "AND t.ref_doctype = 'Customer'"),
    # Sales taxes (parent references invoice, not customer directly)
    # Payment Entry Reference has no direct customer column
]

total_updated = 0
for table, col, extra_where in fk_tables:
    t0 = time.time()
    try:
        result = frappe.db.sql(f"""
            UPDATE "{table}" t
            SET "{col}" = m.new_name
            FROM _customer_rename_map m
            WHERE t."{col}" = m.old_name
            {extra_where}
        """)
        # Get affected rows
        affected = frappe.db.sql(f"""
            SELECT COUNT(*) FROM "{table}" t
            INNER JOIN _customer_rename_map m ON t."{col}" = m.old_name
            {extra_where.replace('t.', f'"{table}".')}
        """)
        # Actually just count what we updated - the UPDATE already ran
        frappe.db.commit()
        elapsed = time.time() - t0
        print(f"  {table:35s} {col:20s} updated [{elapsed:.1f}s]")
        total_updated += 1
    except Exception as e:
        frappe.db.rollback()
        err_msg = str(e)[:80]
        if "does not exist" in err_msg or "column" in err_msg:
            pass  # Table/column doesn't exist, skip
        else:
            print(f"  {table:35s} {col:20s} ERR: {err_msg}")

print(f"\n  Updated {total_updated} FK tables")


# ═══════════════════════════════════════════════════════════════
# STEP 4: RENAME CUSTOMER RECORDS THEMSELVES
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 4: RENAME CUSTOMER RECORDS")
print("=" * 70)
t0 = time.time()

# Must rename customer records — update the `name` column
# Do this AFTER FK updates to avoid FK constraint issues
frappe.db.sql("""
    UPDATE "tabCustomer" t
    SET name = m.new_name
    FROM _customer_rename_map m
    WHERE t.name = m.old_name
""")
frappe.db.commit()
print(f"  Renamed {len(mapping)} customers [{time.time()-t0:.1f}s]")


# ═══════════════════════════════════════════════════════════════
# STEP 5: SET NAMING SERIES COUNTER
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 5: SET NAMING SERIES COUNTER")
print("=" * 70)

# For new customers, ERPNext will use naming_series.
# We need to make sure it doesn't collide.
# Since legacy customer_ids are alphanumeric, numeric auto-increment won't collide.
# But let's set a safe starting point anyway.

# Check if CUST- series exists
series = frappe.db.sql("""
    SELECT name, current FROM "tabSeries" WHERE name = 'CUST-'
""", as_dict=True)

if series:
    print(f"  Current CUST- series: {series[0]['current']}")
else:
    print("  No CUST- series found, creating...")
    frappe.db.sql("""
        INSERT INTO "tabSeries" (name, current) VALUES ('CUST-', 100000)
        ON CONFLICT (name) DO UPDATE SET current = GREATEST("tabSeries".current, 100000)
    """)
    frappe.db.commit()
    print("  Set CUST- counter to 100000")

# Note: The naming_series format should produce CUST-NNNNN or CUST-.#####
# New customers will get CUST-100001, CUST-100002, etc.


# ═══════════════════════════════════════════════════════════════
# STEP 6: CLEANUP TEMP TABLE
# ═══════════════════════════════════════════════════════════════
frappe.db.sql('DROP TABLE IF EXISTS _customer_rename_map')
frappe.db.commit()


# ═══════════════════════════════════════════════════════════════
# STEP 7: VERIFY
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 7: VERIFICATION")
print("=" * 70)

cust_count = frappe.db.sql('SELECT COUNT(*) FROM "tabCustomer"')[0][0]
print(f"  Customers: {cust_count}")

# Check for any remaining hex-pattern IDs
hex_remaining = frappe.db.sql("""
    SELECT COUNT(*) FROM "tabCustomer"
    WHERE name ~ '^CUST-[0-9a-f]{8,}'
    AND name !~ '^CUST-[0-9]+$'
""")[0][0]
print(f"  Hex-pattern IDs remaining: {hex_remaining} (should be 0)")

# Spot checks
spots = [
    (4, "LPB4"),
    (2393, "Vegpro"),
    (13814, "Vegpro 114796350603272"),
]
for legacy_id, label in spots:
    c = frappe.db.sql("""
        SELECT name, customer_name, legacy_customer_id
        FROM "tabCustomer" WHERE legacy_account_id = %s
    """, (legacy_id,), as_dict=True)
    if c:
        print(f"  {label}: {c[0]['name']} ({c[0]['customer_name']})")

# Check FK consistency: any orphaned references?
for table, col in [("tabSales Invoice", "customer"), ("tabSubscription", "party"), ("tabIssue", "customer")]:
    orphans = frappe.db.sql(f"""
        SELECT COUNT(*) FROM "{table}" t
        WHERE t."{col}" IS NOT NULL
        AND t."{col}" != ''
        AND NOT EXISTS (SELECT 1 FROM "tabCustomer" c WHERE c.name = t."{col}")
    """)[0][0]
    if orphans > 0:
        print(f"  WARNING: {orphans} orphaned references in {table}.{col}!")
    else:
        print(f"  {table}.{col}: all references valid ✓")


# ═══════════════════════════════════════════════════════════════
# DONE
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("COMPLETE")
print("=" * 70)
print("  Next: bench --site erp.gigafibre.ca clear-cache")

frappe.clear_cache()
print("  Cache cleared")
