#!/usr/bin/env python3
"""
Clean reimport of native Subscriptions from Service Subscription (source of truth).

Fixes the migration issue where Phase 3/4 created hex customer IDs (CUST-04ea550dcf)
instead of mapping to the correct sequential IDs (CUST-988).

Steps:
  1. Purge all native Subscriptions + Plan Details
  2. Recreate Subscriptions from Service Subscription data (correct customer mapping)
  3. Delete orphaned hex customers (no remaining FK references)
  4. Verify totals match

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

Log: stdout (run with tee)
"""
import sys
import os
import uuid
import time
import html
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
if DRY_RUN:
    print("*** DRY RUN — no changes will be written ***")

ADMIN = "Administrator"
COMPANY = "TARGO"
TAX_TEMPLATE = "QC TPS 5% + TVQ 9.975% - T"


def uid(prefix=""):
    return prefix + uuid.uuid4().hex[:10]


def now():
    return datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")


# ═══════════════════════════════════════════════════════════════
# STEP 1: PURGE native Subscriptions
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 1: PURGE EXISTING SUBSCRIPTIONS")
print("=" * 70)

sub_count = frappe.db.sql('SELECT COUNT(*) FROM "tabSubscription"')[0][0]
spd_count = frappe.db.sql('SELECT COUNT(*) FROM "tabSubscription Plan Detail"')[0][0]
sp_count = frappe.db.sql('SELECT COUNT(*) FROM "tabSubscription Plan"')[0][0]

print(f"  Current counts: {sub_count} Subscriptions, {spd_count} Plan Details, {sp_count} Plans")

if not DRY_RUN:
    frappe.db.sql('DELETE FROM "tabSubscription Plan Detail"')
    frappe.db.sql('DELETE FROM "tabSubscription"')
    frappe.db.sql('DELETE FROM "tabSubscription Plan"')
    frappe.db.commit()
    print("  Purged all Subscriptions, Plan Details, and Plans.")
else:
    print("  [DRY RUN] Would purge all Subscriptions, Plan Details, and Plans.")


# ═══════════════════════════════════════════════════════════════
# STEP 2: LOAD Service Subscriptions (source of truth)
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 2: LOAD SERVICE SUBSCRIPTIONS")
print("=" * 70)

ss_rows = frappe.db.sql("""
    SELECT name, customer, service_location, plan_name, monthly_price,
           status, service_category, billing_cycle, start_date, end_date,
           speed_down, speed_up, cancellation_date, cancellation_reason,
           legacy_service_id, product_sku, radius_user, radius_password, notes
    FROM "tabService Subscription"
    ORDER BY customer, service_location, monthly_price DESC
""", as_dict=True)

total = len(ss_rows)
active = sum(1 for r in ss_rows if r["status"] == "Actif")
customers = len(set(r["customer"] for r in ss_rows))
print(f"  Loaded {total} Service Subscriptions ({active} active) for {customers} customers")


# ═══════════════════════════════════════════════════════════════
# STEP 3: BUILD Subscription Plans from unique SKUs
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 3: CREATE SUBSCRIPTION PLANS")
print("=" * 70)
t0 = time.time()

# Collect unique SKUs with their typical price and category
sku_info = {}
for r in ss_rows:
    sku = r.get("product_sku") or ""
    if not sku:
        continue
    if sku not in sku_info:
        sku_info[sku] = {
            "price": abs(float(r["monthly_price"] or 0)),
            "category": r.get("service_category") or "",
            "billing_cycle": r.get("billing_cycle") or "Mensuel",
            "plan_name": r.get("plan_name") or sku,
        }

# Also add a fallback plan for records without SKU
plan_map = {}  # plan_name → plan_id
plans_created = 0

if not DRY_RUN:
    ts = now()
    for sku, info in sku_info.items():
        plan_name = f"PLAN-{sku}"
        plan_id = uid("SP-")
        billing_interval = "Year" if info["billing_cycle"] == "Annuel" else "Month"

        try:
            frappe.db.sql("""
                INSERT INTO "tabSubscription Plan" (
                    name, creation, modified, modified_by, owner, docstatus, idx,
                    plan_name, item, currency, price_determination, cost,
                    billing_interval, billing_interval_count
                ) VALUES (%s, %s, %s, %s, %s, 0, 0,
                          %s, %s, 'CAD', 'Fixed Rate', %s, %s, 1)
            """, (plan_id, ts, ts, ADMIN, ADMIN, plan_name, sku, info["price"], billing_interval))
            plan_map[plan_name] = plan_id
            plans_created += 1
        except Exception as e:
            frappe.db.rollback()
            print(f"  ERR plan {sku}: {str(e)[:80]}")

    frappe.db.commit()

print(f"  Created {plans_created} Subscription Plans [{time.time()-t0:.1f}s]")


# ═══════════════════════════════════════════════════════════════
# STEP 4: CREATE Subscriptions from Service Subscriptions
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 4: CREATE SUBSCRIPTIONS")
print("=" * 70)
t0 = time.time()

# Verify all customers exist
all_customers = set(r["customer"] for r in ss_rows)
existing_customers = set(
    r[0] for r in frappe.db.sql('SELECT name FROM "tabCustomer"')
)
missing_customers = all_customers - existing_customers
if missing_customers:
    print(f"  WARNING: {len(missing_customers)} customers referenced but don't exist!")
    for c in list(missing_customers)[:10]:
        print(f"    {c}")

sub_ok = sub_err = 0
ts = now()

for i, ss in enumerate(ss_rows):
    if DRY_RUN:
        sub_ok += 1
        continue

    cust = ss["customer"]
    if cust in missing_customers:
        sub_err += 1
        continue

    # Map status
    if ss["status"] == "Actif":
        status = "Active"
    elif ss["status"] == "Annulé":
        status = "Cancelled"
    elif ss["status"] == "Suspendu":
        status = "Past Due Date"
    else:
        status = "Active"  # default

    # Price
    price = float(ss["monthly_price"] or 0)

    # Start date
    start_date = ss.get("start_date") or "2020-01-01"
    if hasattr(start_date, "strftime"):
        start_date = start_date.strftime("%Y-%m-%d")

    # End/cancellation date
    end_date = ss.get("end_date")
    if hasattr(end_date, "strftime"):
        end_date = end_date.strftime("%Y-%m-%d")
    cancel_date = ss.get("cancellation_date")
    if hasattr(cancel_date, "strftime"):
        cancel_date = cancel_date.strftime("%Y-%m-%d")

    # Billing frequency
    billing_freq = "A" if ss.get("billing_cycle") == "Annuel" else "M"

    # SKU / plan
    sku = ss.get("product_sku") or ""
    plan_name = f"PLAN-{sku}" if sku else ""

    # Category
    category = ss.get("service_category") or ""

    # Description: plan_name from SS (unescape HTML entities from legacy data)
    description = html.unescape(ss.get("plan_name") or "")

    sub_id = uid("SUB-")

    try:
        frappe.db.sql("""
            INSERT INTO "tabSubscription" (
                name, creation, modified, modified_by, owner, docstatus, idx,
                party_type, party, company, status,
                start_date, generate_invoice_at, days_until_due,
                follow_calendar_months, generate_new_invoices_past_due_date,
                submit_invoice, cancel_at_period_end,
                sales_tax_template,
                additional_discount_amount,
                radius_user, radius_pwd, legacy_service_id,
                service_location,
                actual_price, custom_description, item_code, item_group,
                billing_frequency,
                cancelation_date
            ) VALUES (
                %s, %s, %s, %s, %s, 0, 0,
                'Customer', %s, %s, %s,
                %s, 'Beginning of the current subscription period', 30,
                1, 1, 0, 0,
                %s, 0,
                %s, %s, %s,
                %s,
                %s, %s, %s, %s, %s,
                %s
            )
        """, (sub_id, ts, ts, ADMIN, ADMIN,
              cust, COMPANY, status,
              start_date,
              TAX_TEMPLATE,
              ss.get("radius_user") or None,
              ss.get("radius_password") or None,
              ss.get("legacy_service_id"),
              ss.get("service_location") or None,
              price, description, sku, category, billing_freq,
              cancel_date))

        # Create plan detail child record
        if plan_name and plan_name in plan_map:
            frappe.db.sql("""
                INSERT INTO "tabSubscription Plan Detail" (
                    name, creation, modified, modified_by, owner, docstatus, idx,
                    plan, qty,
                    parent, parentfield, parenttype
                ) VALUES (%s, %s, %s, %s, %s, 0, 1,
                          %s, 1,
                          %s, 'plans', 'Subscription')
            """, (uid("SPD-"), ts, ts, ADMIN, ADMIN, plan_name, sub_id))

        sub_ok += 1

        if sub_ok % 2000 == 0:
            frappe.db.commit()
            print(f"  [{sub_ok}/{total}] created... [{time.time()-t0:.0f}s]")

    except Exception as e:
        sub_err += 1
        frappe.db.rollback()
        if sub_err <= 20:
            print(f"  ERR {cust}/{ss['name']}: {str(e)[:100]}")

if not DRY_RUN:
    frappe.db.commit()

print(f"  Created {sub_ok} Subscriptions, {sub_err} errors [{time.time()-t0:.1f}s]")


# ═══════════════════════════════════════════════════════════════
# STEP 5: CLEANUP — Delete orphaned hex customers
# ═══════════════════════════════════════════════════════════════
print("\n" + "=" * 70)
print("STEP 5: CLEANUP ORPHANED HEX CUSTOMERS")
print("=" * 70)
t0 = time.time()

# Find hex-pattern customers (non-numeric suffix after CUST-)
hex_customers = frappe.db.sql("""
    SELECT name, customer_name, legacy_account_id
    FROM "tabCustomer"
    WHERE name ~ '^CUST-' AND name !~ '^CUST-[0-9]+$'
""", as_dict=True)

print(f"  Found {len(hex_customers)} hex-pattern customers")

if hex_customers:
    # Check which hex customers still have FK references using subqueries (avoid ANY() limits)
    hex_subquery = """(SELECT name FROM "tabCustomer" WHERE name ~ '^CUST-' AND name !~ '^CUST-[0-9]+$')"""

    fk_tables = [
        ("tabSales Invoice", "customer"),
        ("tabPayment Entry", "party"),
        ("tabIssue", "customer"),
        ("tabService Location", "customer"),
        ("tabService Subscription", "customer"),
        ("tabSubscription", "party"),
        ("tabService Equipment", "customer"),
    ]

    referenced = set()
    for table, col in fk_tables:
        try:
            refs = frappe.db.sql(
                f'SELECT DISTINCT "{col}" FROM "{table}" WHERE "{col}" IN {hex_subquery}'
            )
            for r in refs:
                referenced.add(r[0])
            print(f"    {table:30s} {col:15s} → {len(refs)} hex refs")
        except Exception as e:
            frappe.db.rollback()
            print(f"    ERR {table}: {str(e)[:60]}")

    orphaned = [c for c in hex_customers if c["name"] not in referenced]
    still_used = [c for c in hex_customers if c["name"] in referenced]

    print(f"  Still referenced (keep): {len(still_used)}")
    print(f"  Orphaned (can delete):   {len(orphaned)}")

    if orphaned and not DRY_RUN:
        # Delete orphaned hex customers using subquery (avoid ANY() limits)
        # First, create a temp table of referenced hex customers
        frappe.db.sql("""
            DELETE FROM "tabDynamic Link"
            WHERE link_doctype = 'Customer'
            AND link_name IN (SELECT name FROM "tabCustomer"
                WHERE name ~ '^CUST-' AND name !~ '^CUST-[0-9]+$'
                AND name NOT IN (SELECT DISTINCT party FROM "tabSubscription" WHERE party IS NOT NULL)
                AND name NOT IN (SELECT DISTINCT customer FROM "tabSales Invoice" WHERE customer IS NOT NULL)
                AND name NOT IN (SELECT DISTINCT party FROM "tabPayment Entry" WHERE party IS NOT NULL)
                AND name NOT IN (SELECT DISTINCT customer FROM "tabIssue" WHERE customer IS NOT NULL)
                AND name NOT IN (SELECT DISTINCT customer FROM "tabService Location" WHERE customer IS NOT NULL)
                AND name NOT IN (SELECT DISTINCT customer FROM "tabService Subscription" WHERE customer IS NOT NULL)
                AND name NOT IN (SELECT DISTINCT customer FROM "tabService Equipment" WHERE customer IS NOT NULL)
            )
        """)
        frappe.db.commit()

        deleted = frappe.db.sql("""
            DELETE FROM "tabCustomer"
            WHERE name ~ '^CUST-' AND name !~ '^CUST-[0-9]+$'
            AND name NOT IN (SELECT DISTINCT party FROM "tabSubscription" WHERE party IS NOT NULL)
            AND name NOT IN (SELECT DISTINCT customer FROM "tabSales Invoice" WHERE customer IS NOT NULL)
            AND name NOT IN (SELECT DISTINCT party FROM "tabPayment Entry" WHERE party IS NOT NULL)
            AND name NOT IN (SELECT DISTINCT customer FROM "tabIssue" WHERE customer IS NOT NULL)
            AND name NOT IN (SELECT DISTINCT customer FROM "tabService Location" WHERE customer IS NOT NULL)
            AND name NOT IN (SELECT DISTINCT customer FROM "tabService Subscription" WHERE customer IS NOT NULL)
            AND name NOT IN (SELECT DISTINCT customer FROM "tabService Equipment" WHERE customer IS NOT NULL)
        """)
        frappe.db.commit()
        print(f"  Deleted orphaned hex customers")
    elif orphaned:
        print(f"  [DRY RUN] Would delete {len(orphaned)} orphaned hex customers")
else:
    print("  No hex-pattern customers found.")


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

new_sub_count = frappe.db.sql('SELECT COUNT(*) FROM "tabSubscription"')[0][0]
new_spd_count = frappe.db.sql('SELECT COUNT(*) FROM "tabSubscription Plan Detail"')[0][0]
new_sp_count = frappe.db.sql('SELECT COUNT(*) FROM "tabSubscription Plan"')[0][0]
cust_count = frappe.db.sql('SELECT COUNT(*) FROM "tabCustomer"')[0][0]

print(f"  Subscriptions:     {new_sub_count}")
print(f"  Plan Details:      {new_spd_count}")
print(f"  Plans:             {new_sp_count}")
print(f"  Customers:         {cust_count}")

# Check for customer ID consistency: all subscriptions should point to sequential customers
hex_refs = frappe.db.sql("""
    SELECT COUNT(*) FROM "tabSubscription"
    WHERE party ~ '^CUST-' AND party !~ '^CUST-[0-9]+$'
""")[0][0]
print(f"  Subscriptions pointing to hex customers: {hex_refs} (should be 0)")

# Spot-check: LPB4 customer (legacy_account_id = 4)
lpb4 = frappe.db.sql("""
    SELECT name FROM "tabCustomer" WHERE legacy_account_id = 4
""", as_dict=True)
if lpb4:
    cust_name = lpb4[0]["name"]
    lpb4_subs = frappe.db.sql("""
        SELECT actual_price, item_code, custom_description, status, billing_frequency
        FROM "tabSubscription"
        WHERE party = %s
        ORDER BY actual_price DESC
    """, (cust_name,), as_dict=True)
    total_price = sum(float(s["actual_price"] or 0) for s in lpb4_subs if s["status"] == "Active")
    print(f"\n  Spot-check: {cust_name} (LPB4)")
    for s in lpb4_subs:
        print(f"    {s['item_code'] or '':15s}  {float(s['actual_price'] or 0):>8.2f}  {s['billing_frequency']}  {s['status']}  {s['custom_description'] or ''}")
    print(f"    {'TOTAL':15s}  {total_price:>8.2f}$/mo")

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