"""
Fix reversal invoices: link credit invoices to their original via return_against + PLE.
These are cancellation invoices created in legacy with no credit payment — just billed_amt = total_amt.
"""
import frappe, os, pymysql, time
os.chdir("/home/frappe/frappe-bench/sites")
frappe.init(site="erp.gigafibre.ca", sites_path=".")
frappe.connect()
print("Connected:", frappe.local.site)

t0 = time.time()

legacy = pymysql.connect(
    host="legacy-db", user="facturation", password="*******",
    database="gestionclient", cursorclass=pymysql.cursors.DictCursor
)

# Get all unlinked settled return invoices
unlinked = frappe.db.sql("""
    SELECT name, legacy_invoice_id, grand_total, customer, posting_date
    FROM "tabSales Invoice"
    WHERE docstatus = 1 AND is_return = 1
      AND (return_against IS NULL OR return_against = '')
      AND outstanding_amount < -0.005
    ORDER BY outstanding_amount ASC
""", as_dict=True)
print("Unlinked settled returns: {}".format(len(unlinked)))

# Build legacy_invoice_id → SINV name map
inv_map = {}
map_rows = frappe.db.sql("""
    SELECT name, legacy_invoice_id FROM "tabSales Invoice"
    WHERE legacy_invoice_id IS NOT NULL AND docstatus = 1 AND is_return != 1
""", as_dict=True)
for r in map_rows:
    inv_map[str(r['legacy_invoice_id'])] = r['name']
print("Invoice map: {} entries".format(len(inv_map)))

# Match each reversal to its original
matches = []  # (credit_sinv, target_sinv, amount)
unmatched = 0

with legacy.cursor() as cur:
    for ret in unlinked:
        leg_id = ret['legacy_invoice_id']
        amount = float(ret['grand_total'])
        target_amount = -amount

        cur.execute("SELECT account_id, date_orig FROM invoice WHERE id = %s", (leg_id,))
        credit_inv = cur.fetchone()
        if not credit_inv:
            unmatched += 1
            continue

        cur.execute("""
            SELECT id FROM invoice
            WHERE account_id = %s
              AND ROUND(total_amt, 2) = ROUND(%s, 2)
              AND total_amt > 0
              AND date_orig <= %s
              AND date_orig >= UNIX_TIMESTAMP('2024-04-08')
            ORDER BY date_orig DESC
            LIMIT 1
        """, (credit_inv['account_id'], target_amount, credit_inv['date_orig']))
        match = cur.fetchone()

        if match:
            target_sinv = inv_map.get(str(match['id']))
            if target_sinv:
                matches.append((ret['name'], target_sinv, amount))
            else:
                unmatched += 1
        else:
            unmatched += 1

legacy.close()
print("Matched: {} | Unmatched: {}".format(len(matches), unmatched))

# Load into temp table
frappe.db.sql("DROP TABLE IF EXISTS _tmp_reversal_match")
frappe.db.commit()
frappe.db.sql("""
    CREATE TABLE _tmp_reversal_match (
        id SERIAL PRIMARY KEY,
        credit_sinv VARCHAR(140),
        target_sinv VARCHAR(140),
        amount DOUBLE PRECISION
    )
""")
frappe.db.commit()

for i in range(0, len(matches), 5000):
    batch = matches[i:i+5000]
    values = ",".join(["('{}', '{}', {})".format(c, t, a) for c, t, a in batch])
    frappe.db.sql("INSERT INTO _tmp_reversal_match (credit_sinv, target_sinv, amount) VALUES {}".format(values))
    frappe.db.commit()
print("Loaded {} matches into temp table".format(len(matches)))

# Set return_against
frappe.db.sql("""
    UPDATE "tabSales Invoice" si
    SET return_against = rm.target_sinv
    FROM _tmp_reversal_match rm
    WHERE si.name = rm.credit_sinv
""")
frappe.db.commit()
print("Set return_against on {} credit invoices".format(len(matches)))

# Delete self-referencing PLE for these credit invoices
frappe.db.sql("""
    DELETE FROM "tabPayment Ledger Entry"
    WHERE name IN (
        SELECT 'ple-' || rm.credit_sinv FROM _tmp_reversal_match rm
    )
""")
frappe.db.commit()

# Insert PLE: credit allocation against target invoice
frappe.db.sql("""
    INSERT INTO "tabPayment Ledger Entry" (
        name, owner, creation, modified, modified_by, docstatus,
        posting_date, company,
        account_type, account, account_currency,
        party_type, party,
        due_date,
        voucher_type, voucher_no,
        against_voucher_type, against_voucher_no,
        amount, amount_in_account_currency,
        delinked, remarks
    )
    SELECT
        'plr-' || rm.id, 'Administrator', NOW(), NOW(), 'Administrator', 1,
        si.posting_date, 'TARGO',
        'Receivable', 'Comptes clients - T', 'CAD',
        'Customer', si.customer,
        COALESCE(si.due_date, si.posting_date),
        'Sales Invoice', rm.credit_sinv,
        'Sales Invoice', rm.target_sinv,
        rm.amount, rm.amount,
        0, 'Reversal allocation'
    FROM _tmp_reversal_match rm
    JOIN "tabSales Invoice" si ON si.name = rm.credit_sinv
""")
frappe.db.commit()
new_ple = frappe.db.sql("SELECT COUNT(*) FROM \"tabPayment Ledger Entry\" WHERE name LIKE 'plr-%%'")[0][0]
print("Created {} reversal PLE entries".format(new_ple))

# Set outstanding = 0 on linked credit invoices
frappe.db.sql("""
    UPDATE "tabSales Invoice"
    SET outstanding_amount = 0, status = 'Return'
    WHERE name IN (SELECT credit_sinv FROM _tmp_reversal_match)
""")
frappe.db.commit()

# Recalculate outstanding on target invoices (original invoices being cancelled)
frappe.db.sql("""
    UPDATE "tabSales Invoice" si
    SET outstanding_amount = COALESCE((
        SELECT SUM(ple.amount)
        FROM "tabPayment Ledger Entry" ple
        WHERE ple.against_voucher_type = 'Sales Invoice'
          AND ple.against_voucher_no = si.name
          AND ple.delinked = 0
    ), si.grand_total)
    WHERE si.name IN (SELECT target_sinv FROM _tmp_reversal_match)
""")
frappe.db.commit()

# Update statuses for affected target invoices
frappe.db.sql("""UPDATE "tabSales Invoice" SET status = 'Paid'
    WHERE name IN (SELECT target_sinv FROM _tmp_reversal_match)
      AND is_return != 1 AND ROUND(outstanding_amount::numeric, 2) = 0""")
frappe.db.sql("""UPDATE "tabSales Invoice" SET status = 'Overdue'
    WHERE name IN (SELECT target_sinv FROM _tmp_reversal_match)
      AND is_return != 1 AND outstanding_amount > 0.005
      AND COALESCE(due_date, posting_date) < CURRENT_DATE""")
frappe.db.sql("""UPDATE "tabSales Invoice" SET status = 'Unpaid'
    WHERE name IN (SELECT target_sinv FROM _tmp_reversal_match)
      AND is_return != 1 AND outstanding_amount > 0.005
      AND COALESCE(due_date, posting_date) >= CURRENT_DATE""")
frappe.db.sql("""UPDATE "tabSales Invoice" SET status = 'Credit Note Issued'
    WHERE name IN (SELECT target_sinv FROM _tmp_reversal_match)
      AND is_return != 1 AND outstanding_amount < -0.005""")
frappe.db.commit()

# Cleanup
frappe.db.sql("DROP TABLE IF EXISTS _tmp_reversal_match")
frappe.db.commit()

# Verification
print("\n=== Verification ===")
outstanding = frappe.db.sql("""
    SELECT
        ROUND(SUM(CASE WHEN outstanding_amount > 0.005 THEN outstanding_amount ELSE 0 END)::numeric, 2) as owed,
        ROUND(SUM(CASE WHEN outstanding_amount < -0.005 THEN outstanding_amount ELSE 0 END)::numeric, 2) as overpaid
    FROM "tabSales Invoice" WHERE docstatus = 1
""", as_dict=True)[0]
print("  Outstanding: ${} owed | ${} overpaid".format(outstanding['owed'], outstanding['overpaid']))

statuses = frappe.db.sql("""
    SELECT status, COUNT(*) as cnt
    FROM "tabSales Invoice" WHERE docstatus = 1
    GROUP BY status ORDER BY cnt DESC
""", as_dict=True)
print("\n  Status breakdown:")
for s in statuses:
    print("    {}: {}".format(s['status'], s['cnt']))

elapsed = time.time() - t0
print("\nDone in {:.0f}s".format(elapsed))
