#!/usr/bin/env python3
"""
Rename all doctype IDs to zero-padded 10-digit numeric format.

Format: {PREFIX}-{legacy_id:010d}
  SINV-0000001234  (Sales Invoice)
  PE-0000001234    (Payment Entry)
  ISS-0000001234   (Issue)
  LOC-0000001234   (Service Location)
  EQP-0000001234   (Service Equipment)
  SUB-0000001234   (Service Subscription)

Two-phase rename to avoid PK collisions.
Uses temp mapping tables + bulk UPDATE ... FROM for FK references.

Run inside erpnext-backend-1:
  /home/frappe/frappe-bench/env/bin/python /home/frappe/frappe-bench/rename_all_doctypes.py [--dry-run]
"""
import sys
import os
import time

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
if DRY_RUN:
    print("*** DRY RUN ***\n")


def rename_doctype(label, table, prefix, legacy_col, fk_refs, pad=10):
    """
    Rename a doctype's records to {prefix}-{legacy_id:0{pad}d}.

    Args:
        label: Human-readable label
        table: PostgreSQL table name (e.g. "tabSales Invoice")
        prefix: ID prefix (e.g. "SINV")
        legacy_col: Column containing the legacy numeric ID
        fk_refs: List of (fk_table, fk_column, extra_where) tuples
        pad: Zero-padding width (default 10)
    """
    print("\n" + "=" * 70)
    print(f"RENAMING: {label} → {prefix}-{('0' * pad)}")
    print("=" * 70)
    t_start = time.time()

    # Step 1: Build mapping
    rows = frappe.db.sql(f"""
        SELECT name, {legacy_col} FROM "{table}"
        WHERE {legacy_col} IS NOT NULL AND {legacy_col} > 0
    """, as_dict=True)

    mapping = {}
    new_used = set()
    collisions = 0
    for r in rows:
        old_name = r["name"]
        new_name = f"{prefix}-{int(r[legacy_col]):0{pad}d}"
        if new_name in new_used:
            collisions += 1
            new_name = f"{new_name}d{collisions}"
        new_used.add(new_name)
        if old_name != new_name:
            mapping[old_name] = new_name

    # Also handle records WITHOUT legacy ID
    no_legacy = frappe.db.sql(f"""
        SELECT COUNT(*) FROM "{table}"
        WHERE {legacy_col} IS NULL OR {legacy_col} = 0
    """)[0][0]

    total = len(rows) + no_legacy
    print(f"  Total records: {total}")
    print(f"  Will rename: {len(mapping)}")
    print(f"  Already correct: {len(rows) - len(mapping)}")
    print(f"  No legacy ID (skip): {no_legacy}")
    if collisions:
        print(f"  Collisions: {collisions}")

    if not mapping:
        print("  Nothing to rename!")
        return

    # Show samples
    samples = list(mapping.items())[:5]
    for old, new in samples:
        print(f"    {old:30s} → {new}")

    if DRY_RUN:
        return

    # Step 2: Create temp mapping table
    map_table = f"_rename_map_{prefix.lower()}"
    frappe.db.sql(f'DROP TABLE IF EXISTS {map_table}')
    frappe.db.sql(f"""
        CREATE TEMP TABLE {map_table} (
            old_name VARCHAR(140) PRIMARY KEY,
            new_name VARCHAR(140) NOT NULL
        )
    """)

    # Insert mappings in batches
    items = list(mapping.items())
    batch_size = 1000
    for start in range(0, len(items), batch_size):
        batch = items[start:start + batch_size]
        frappe.db.sql(
            f"INSERT INTO {map_table} (old_name, new_name) VALUES " +
            ",".join(["(%s, %s)"] * len(batch)),
            [v for pair in batch for v in pair]
        )
    frappe.db.commit()

    # Step 3: Update FK references
    for fk_table, fk_col, extra_where in fk_refs:
        t0 = time.time()
        try:
            frappe.db.sql(f"""
                UPDATE "{fk_table}" t
                SET "{fk_col}" = m.new_name
                FROM {map_table} m
                WHERE t."{fk_col}" = m.old_name
                {extra_where}
            """)
            frappe.db.commit()
            print(f"    FK {fk_table}.{fk_col} [{time.time()-t0:.1f}s]")
        except Exception as e:
            frappe.db.rollback()
            err = str(e)[:80]
            if "does not exist" not in err:
                print(f"    FK {fk_table}.{fk_col} ERR: {err}")

    # Step 4: Two-phase rename
    # Phase A: old → _TEMP_new
    print(f"  Phase A: temp rename...")
    t0 = time.time()
    for start in range(0, len(items), 500):
        batch = items[start:start + 500]
        cases = " ".join(f"WHEN '{old}' THEN '_TEMP_{new}'" for old, new in batch)
        old_names = "','".join(old for old, _ in batch)
        frappe.db.sql(f"""
            UPDATE "{table}"
            SET name = CASE name {cases} END
            WHERE name IN ('{old_names}')
        """)
    frappe.db.commit()
    print(f"    Phase A done [{time.time()-t0:.1f}s]")

    # Phase B: _TEMP_new → new
    print(f"  Phase B: final rename...")
    t0 = time.time()
    for start in range(0, len(items), 500):
        batch = items[start:start + 500]
        cases = " ".join(f"WHEN '_TEMP_{new}' THEN '{new}'" for _, new in batch)
        temp_names = "','".join(f"_TEMP_{new}" for _, new in batch)
        frappe.db.sql(f"""
            UPDATE "{table}"
            SET name = CASE name {cases} END
            WHERE name IN ('{temp_names}')
        """)
    frappe.db.commit()
    print(f"    Phase B done [{time.time()-t0:.1f}s]")

    # Clean up
    frappe.db.sql(f'DROP TABLE IF EXISTS {map_table}')
    frappe.db.commit()

    elapsed = time.time() - t_start
    print(f"  ✓ {label} complete [{elapsed:.1f}s]")


def set_series(prefix, start_at):
    """Set the naming series counter."""
    existing = frappe.db.sql(
        'SELECT current FROM "tabSeries" WHERE name = %s', (prefix,), as_dict=True
    )
    if existing:
        if existing[0]["current"] < start_at:
            frappe.db.sql('UPDATE "tabSeries" SET current = %s WHERE name = %s', (start_at, prefix))
            print(f"  Series {prefix} updated to {start_at}")
        else:
            print(f"  Series {prefix} already at {existing[0]['current']}")
    else:
        frappe.db.sql('INSERT INTO "tabSeries" (name, current) VALUES (%s, %s)', (prefix, start_at))
        print(f"  Series {prefix} created at {start_at}")
    frappe.db.commit()


# ═══════════════════════════════════════════════════════════════
# 1. SALES INVOICE
# ═══════════════════════════════════════════════════════════════
# Current: SINV-1 through SINV-99999 (legacy invoice.id)
# The 'name' is already SINV-{id}, we just need to zero-pad
# Legacy column: the name itself encodes the ID. We need to extract it.
# Actually, let's check if there's a legacy column...

# Sales invoices were imported as SINV-{legacy_id}. The name IS the legacy ref.
# We need to extract the number from the name and zero-pad.
# Approach: use a direct SQL rename since legacy_id is embedded in name.

print("\n" + "=" * 70)
print("RENAMING: Sales Invoice → SINV-0000XXXXXX")
print("=" * 70)
t_start = time.time()

if not DRY_RUN:
    # Build mapping from current names
    sinv_rows = frappe.db.sql("""
        SELECT name FROM "tabSales Invoice" WHERE name ~ '^SINV-[0-9]+$'
    """)
    sinv_map = {}
    for r in sinv_rows:
        old = r[0]
        num = int(old.replace("SINV-", ""))
        new = f"SINV-{num:010d}"
        if old != new:
            sinv_map[old] = new

    print(f"  Will rename: {len(sinv_map)} invoices")
    for old, new in list(sinv_map.items())[:5]:
        print(f"    {old:20s} → {new}")

    if sinv_map:
        # Create temp mapping
        frappe.db.sql('DROP TABLE IF EXISTS _rename_sinv')
        frappe.db.sql("""
            CREATE TEMP TABLE _rename_sinv (
                old_name VARCHAR(140) PRIMARY KEY,
                new_name VARCHAR(140) NOT NULL
            )
        """)
        items = list(sinv_map.items())
        for start in range(0, len(items), 1000):
            batch = items[start:start + 1000]
            frappe.db.sql(
                "INSERT INTO _rename_sinv (old_name, new_name) VALUES " +
                ",".join(["(%s, %s)"] * len(batch)),
                [v for pair in batch for v in pair]
            )
        frappe.db.commit()

        # Update FK refs
        sinv_fks = [
            ("tabSales Invoice Item", "parent", ""),
            ("tabSales Invoice Payment", "parent", ""),
            ("tabSales Taxes and Charges", "parent", "AND t.parenttype = 'Sales Invoice'"),
            ("tabGL Entry", "voucher_no", "AND t.voucher_type = 'Sales Invoice'"),
            ("tabPayment Ledger Entry", "voucher_no", "AND t.voucher_type = 'Sales Invoice'"),
            ("tabPayment Entry Reference", "reference_name", "AND t.reference_doctype = 'Sales Invoice'"),
            ("tabComment", "reference_name", "AND t.reference_doctype = 'Sales Invoice'"),
            ("tabVersion", "docname", "AND t.ref_doctype = 'Sales Invoice'"),
            ("tabDynamic Link", "link_name", "AND t.link_doctype = 'Sales Invoice'"),
        ]
        for fk_table, fk_col, extra_where in sinv_fks:
            t0 = time.time()
            try:
                frappe.db.sql(f"""
                    UPDATE "{fk_table}" t
                    SET "{fk_col}" = m.new_name
                    FROM _rename_sinv m
                    WHERE t."{fk_col}" = m.old_name
                    {extra_where}
                """)
                frappe.db.commit()
                print(f"    FK {fk_table}.{fk_col} [{time.time()-t0:.1f}s]")
            except Exception as e:
                frappe.db.rollback()
                err = str(e)[:80]
                if "does not exist" not in err:
                    print(f"    FK {fk_table}.{fk_col} ERR: {err}")

        # Two-phase rename
        print("  Phase A...")
        t0 = time.time()
        for start in range(0, len(items), 500):
            batch = items[start:start + 500]
            cases = " ".join(f"WHEN '{old}' THEN '_T_{new}'" for old, new in batch)
            old_names = "','".join(old for old, _ in batch)
            frappe.db.sql(f"""
                UPDATE "tabSales Invoice"
                SET name = CASE name {cases} END
                WHERE name IN ('{old_names}')
            """)
            if (start + 500) % 10000 < 500:
                frappe.db.commit()
                print(f"    {min(start + 500, len(items))}/{len(items)}")
        frappe.db.commit()
        print(f"    Phase A done [{time.time()-t0:.1f}s]")

        print("  Phase B...")
        t0 = time.time()
        for start in range(0, len(items), 500):
            batch = items[start:start + 500]
            cases = " ".join(f"WHEN '_T_{new}' THEN '{new}'" for _, new in batch)
            temp_names = "','".join(f"_T_{new}" for _, new in batch)
            frappe.db.sql(f"""
                UPDATE "tabSales Invoice"
                SET name = CASE name {cases} END
                WHERE name IN ('{temp_names}')
            """)
            if (start + 500) % 10000 < 500:
                frappe.db.commit()
                print(f"    {min(start + 500, len(items))}/{len(items)}")
        frappe.db.commit()
        print(f"    Phase B done [{time.time()-t0:.1f}s]")

        frappe.db.sql('DROP TABLE IF EXISTS _rename_sinv')
        frappe.db.commit()

    print(f"  ✓ Sales Invoice [{time.time()-t_start:.1f}s]")
else:
    sinv_count = frappe.db.sql("SELECT COUNT(*) FROM \"tabSales Invoice\" WHERE name ~ '^SINV-[0-9]+$'")[0][0]
    print(f"  [DRY] Would rename ~{sinv_count} invoices")


# ═══════════════════════════════════════════════════════════════
# 2. PAYMENT ENTRY — same pattern as SINV
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("RENAMING: Payment Entry → PE-0000XXXXXX")
print("=" * 70)
t_start = time.time()

if not DRY_RUN:
    pe_rows = frappe.db.sql("SELECT name FROM \"tabPayment Entry\" WHERE name ~ '^PE-[0-9]+$'")
    pe_map = {}
    for r in pe_rows:
        old = r[0]
        num = int(old.replace("PE-", ""))
        new = f"PE-{num:010d}"
        if old != new:
            pe_map[old] = new

    print(f"  Will rename: {len(pe_map)} payments")
    for old, new in list(pe_map.items())[:3]:
        print(f"    {old:20s} → {new}")

    if pe_map:
        frappe.db.sql('DROP TABLE IF EXISTS _rename_pe')
        frappe.db.sql("""
            CREATE TEMP TABLE _rename_pe (
                old_name VARCHAR(140) PRIMARY KEY, new_name VARCHAR(140) NOT NULL
            )
        """)
        items = list(pe_map.items())
        for start in range(0, len(items), 1000):
            batch = items[start:start + 1000]
            frappe.db.sql(
                "INSERT INTO _rename_pe (old_name, new_name) VALUES " +
                ",".join(["(%s, %s)"] * len(batch)),
                [v for pair in batch for v in pair]
            )
        frappe.db.commit()

        pe_fks = [
            ("tabPayment Entry Reference", "parent", ""),
            ("tabGL Entry", "voucher_no", "AND t.voucher_type = 'Payment Entry'"),
            ("tabPayment Ledger Entry", "voucher_no", "AND t.voucher_type = 'Payment Entry'"),
            ("tabComment", "reference_name", "AND t.reference_doctype = 'Payment Entry'"),
            ("tabVersion", "docname", "AND t.ref_doctype = 'Payment Entry'"),
            ("tabDynamic Link", "link_name", "AND t.link_doctype = 'Payment Entry'"),
        ]
        for fk_table, fk_col, extra_where in pe_fks:
            t0 = time.time()
            try:
                frappe.db.sql(f"""
                    UPDATE "{fk_table}" t SET "{fk_col}" = m.new_name
                    FROM _rename_pe m WHERE t."{fk_col}" = m.old_name {extra_where}
                """)
                frappe.db.commit()
                print(f"    FK {fk_table}.{fk_col} [{time.time()-t0:.1f}s]")
            except Exception as e:
                frappe.db.rollback()
                err = str(e)[:80]
                if "does not exist" not in err:
                    print(f"    FK {fk_table}.{fk_col} ERR: {err}")

        # Two-phase rename
        print("  Phase A...")
        t0 = time.time()
        for start in range(0, len(items), 500):
            batch = items[start:start + 500]
            cases = " ".join(f"WHEN '{old}' THEN '_T_{new}'" for old, new in batch)
            old_names = "','".join(old for old, _ in batch)
            frappe.db.sql(f'UPDATE "tabPayment Entry" SET name = CASE name {cases} END WHERE name IN (\'{old_names}\')')
            if (start + 500) % 10000 < 500:
                frappe.db.commit()
        frappe.db.commit()
        print(f"    Phase A [{time.time()-t0:.1f}s]")

        print("  Phase B...")
        t0 = time.time()
        for start in range(0, len(items), 500):
            batch = items[start:start + 500]
            cases = " ".join(f"WHEN '_T_{new}' THEN '{new}'" for _, new in batch)
            temp_names = "','".join(f"_T_{new}" for _, new in batch)
            frappe.db.sql(f'UPDATE "tabPayment Entry" SET name = CASE name {cases} END WHERE name IN (\'{temp_names}\')')
            if (start + 500) % 10000 < 500:
                frappe.db.commit()
        frappe.db.commit()
        print(f"    Phase B [{time.time()-t0:.1f}s]")

        frappe.db.sql('DROP TABLE IF EXISTS _rename_pe')
        frappe.db.commit()

    print(f"  ✓ Payment Entry [{time.time()-t_start:.1f}s]")
else:
    pe_count = frappe.db.sql("SELECT COUNT(*) FROM \"tabPayment Entry\" WHERE name ~ '^PE-[0-9]+$'")[0][0]
    print(f"  [DRY] Would rename ~{pe_count} payments")


# ═══════════════════════════════════════════════════════════════
# 3. ISSUE — has legacy_ticket_id column
# ═══════════════════════════════════════════════════════════════
rename_doctype(
    label="Issue",
    table="tabIssue",
    prefix="ISS",
    legacy_col="legacy_ticket_id",
    fk_refs=[
        ("tabDispatch Job", "issue", ""),
        ("tabComment", "reference_name", "AND t.reference_doctype = 'Issue'"),
        ("tabVersion", "docname", "AND t.ref_doctype = 'Issue'"),
        ("tabCommunication", "reference_name", "AND t.reference_doctype = 'Issue'"),
        ("tabDynamic Link", "link_name", "AND t.link_doctype = 'Issue'"),
    ],
)


# ═══════════════════════════════════════════════════════════════
# 4. SERVICE LOCATION — has legacy_delivery_id column
# ═══════════════════════════════════════════════════════════════
rename_doctype(
    label="Service Location",
    table="tabService Location",
    prefix="LOC",
    legacy_col="legacy_delivery_id",
    fk_refs=[
        ("tabService Subscription", "service_location", ""),
        ("tabSubscription", "service_location", ""),
        ("tabService Equipment", "service_location", ""),
        ("tabIssue", "service_location", ""),
        ("tabDispatch Job", "service_location", ""),
        ("tabComment", "reference_name", "AND t.reference_doctype = 'Service Location'"),
        ("tabDynamic Link", "link_name", "AND t.link_doctype = 'Service Location'"),
    ],
)


# ═══════════════════════════════════════════════════════════════
# 5. SERVICE EQUIPMENT — has legacy_device_id column
# ═══════════════════════════════════════════════════════════════
rename_doctype(
    label="Service Equipment",
    table="tabService Equipment",
    prefix="EQP",
    legacy_col="legacy_device_id",
    fk_refs=[
        ("tabComment", "reference_name", "AND t.reference_doctype = 'Service Equipment'"),
        ("tabDynamic Link", "link_name", "AND t.link_doctype = 'Service Equipment'"),
    ],
)


# ═══════════════════════════════════════════════════════════════
# 6. SERVICE SUBSCRIPTION — has legacy_service_id column
# ═══════════════════════════════════════════════════════════════
rename_doctype(
    label="Service Subscription",
    table="tabService Subscription",
    prefix="SUB",
    legacy_col="legacy_service_id",
    fk_refs=[
        ("tabComment", "reference_name", "AND t.reference_doctype = 'Service Subscription'"),
        ("tabDynamic Link", "link_name", "AND t.link_doctype = 'Service Subscription'"),
    ],
)


# ═══════════════════════════════════════════════════════════════
# 7. SUBSCRIPTION (native) — also has legacy_service_id
# ═══════════════════════════════════════════════════════════════
rename_doctype(
    label="Subscription (native)",
    table="tabSubscription",
    prefix="ASUB",
    legacy_col="legacy_service_id",
    fk_refs=[
        ("tabSubscription Plan Detail", "parent", ""),
        ("tabComment", "reference_name", "AND t.reference_doctype = 'Subscription'"),
        ("tabDynamic Link", "link_name", "AND t.link_doctype = 'Subscription'"),
    ],
)


# ═══════════════════════════════════════════════════════════════
# SET NAMING SERIES COUNTERS
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("NAMING SERIES COUNTERS")
print("=" * 70)

if not DRY_RUN:
    set_series("SINV-", 100000)
    set_series("PE-", 100000)
    set_series("ISS-", 250000)   # max legacy=244240
    set_series("LOC-", 100000)
    set_series("EQP-", 100000)
    set_series("SUB-", 100000)   # max legacy=74184
    set_series("ASUB-", 100000)


# ═══════════════════════════════════════════════════════════════
# VERIFICATION
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("VERIFICATION")
print("=" * 70)

checks = [
    ("tabSales Invoice", "SINV-"),
    ("tabPayment Entry", "PE-"),
    ("tabIssue", "ISS-"),
    ("tabService Location", "LOC-"),
    ("tabService Equipment", "EQP-"),
    ("tabService Subscription", "SUB-"),
    ("tabSubscription", "ASUB-"),
]

for table, prefix in checks:
    try:
        total = frappe.db.sql(f'SELECT COUNT(*) FROM "{table}"')[0][0]
        correct = frappe.db.sql(f"""
            SELECT COUNT(*) FROM "{table}"
            WHERE name ~ %s
        """, (f'^{prefix}[0-9]{{10}}$',))[0][0]
        print(f"  {table:30s}  total={total:>8d}  correct_format={correct:>8d}  {'✓' if total == correct else f'({total - correct} remaining)'}")
    except Exception as e:
        frappe.db.rollback()
        print(f"  {table}: ERR {str(e)[:50]}")

# Spot checks
print("\n  Spot checks:")
for table, field, value in [
    ("tabSales Invoice", "name", None),
    ("tabPayment Entry", "name", None),
    ("tabIssue", "name", None),
]:
    sample = frappe.db.sql(f'SELECT name FROM "{table}" ORDER BY name LIMIT 3')
    print(f"    {table}: {[s[0] for s in sample]}")

# FK orphan checks
print("\n  FK consistency:")
fk_checks = [
    ("tabSales Invoice Item", "parent", "tabSales Invoice"),
    ("tabPayment Entry Reference", "parent", "tabPayment Entry"),
    ("tabGL Entry", "voucher_no", None),  # multi-type, skip
    ("tabSubscription Plan Detail", "parent", "tabSubscription"),
]
for fk_table, fk_col, parent_table in fk_checks:
    if not parent_table:
        continue
    try:
        orphans = frappe.db.sql(f"""
            SELECT COUNT(*) FROM "{fk_table}" t
            WHERE NOT EXISTS (SELECT 1 FROM "{parent_table}" p WHERE p.name = t."{fk_col}")
        """)[0][0]
        status = "✓" if orphans == 0 else f"WARNING: {orphans} orphaned!"
        print(f"    {fk_table}.{fk_col} → {parent_table}: {status}")
    except Exception as e:
        frappe.db.rollback()

frappe.clear_cache()
print("\n✓ All done — cache cleared")
print("  Next: bench --site erp.gigafibre.ca clear-cache")
