"""POS ↔ laundry.order link healthcheck.

Run via:
    docker exec -i odoo-laundry-system-odoo19-1 odoo shell \\
        -c /etc/odoo/odoo.conf -d dev --no-http \\
        < extra-addons/laundry_management/tools/laundry_pos_link_healthcheck.py

Read-only by default. To heal missing links + repair the tracking-code
sequence, set HEAL=True at the top.

What it does
────────────
1. Reports the laundry tracking-code sequence vs MAX(tracking_code) —
   this is the most common cause of the silent "no laundry.order
   created" symptom (UniqueViolation swallowed by the savepoint).
2. Lists the latest 10 POS orders with linkage status + line scan.
3. Lists the latest 10 laundry.order rows.
4. Counts unlinked POS orders that SHOULD have a laundry.order
   (have ≥1 laundry-service line, no settlement-only).
5. Verifies that the laundry-side hand-off is callable as a Cashier-
   group user (permission smoke).
6. Optional heal: backfills missing links + repairs the sequence.
"""
import traceback

env = self.env  # noqa: F821  — provided by odoo shell

# Toggle to True to backfill missing links + repair the sequence.
HEAL = False

print("=" * 70)
print("POS ↔ LAUNDRY LINK HEALTHCHECK    HEAL =", HEAL)
print("=" * 70)

# ── 1. Sequence sanity check ──────────────────────────────────────────
SEQ_CODE = "laundry.order.line.tracking"
seq = env["ir.sequence"].sudo().search([("code", "=", SEQ_CODE)], limit=1)
if seq:
    env.cr.execute(
        """SELECT COALESCE(MAX(
               CAST(NULLIF(REGEXP_REPLACE(tracking_code,'[^0-9]','','g'),'')
                    AS INTEGER)
           ), 0) FROM laundry_order_line WHERE tracking_code IS NOT NULL"""
    )
    max_existing = env.cr.fetchone()[0] or 0
    drift = seq.number_next - (max_existing + 1)
    status = "OK" if drift >= 0 else f"BEHIND by {-drift} (collisions imminent)"
    print(f"\n[1] sequence {SEQ_CODE}")
    print(f"    number_next = {seq.number_next}    "
          f"max_existing = {max_existing}    drift = {drift}    {status}")
else:
    print(f"\n[1] !!! sequence {SEQ_CODE} not found")

# ── 2. Latest 10 POS orders with linkage ─────────────────────────────
print("\n[2] last 10 pos.order with linkage")
env.cr.execute("""
    SELECT id, name, pos_reference, partner_id, amount_total, state,
           create_date, laundry_order_id
    FROM pos_order
    ORDER BY id DESC
    LIMIT 10
""")
for row in env.cr.dictfetchall():
    link = row["laundry_order_id"] or "-"
    print(f"   pos.id={row['id']:>3}  name={(row['name'] or '')[:24]:<24}  "
          f"ref={(row['pos_reference'] or '-'):<22}  "
          f"link={link:>4}  state={row['state']}")

# ── 3. Latest 10 laundry.order ────────────────────────────────────────
print("\n[3] last 10 laundry.order")
env.cr.execute("""
    SELECT id, name, pos_order_id, partner_id, amount_total, amount_due, state
    FROM laundry_order
    ORDER BY id DESC
    LIMIT 10
""")
for row in env.cr.dictfetchall():
    print(f"   lo.id={row['id']:>3}  name={row['name']:<22}  "
          f"pos={row['pos_order_id'] or '-':>4}  "
          f"total={row['amount_total']:>7}  due={row['amount_due']:>5}  "
          f"state={row['state']}")

# ── 4. Unlinked POS orders that SHOULD have a laundry.order ──────────
print("\n[4] unlinked POS orders that have laundry-service lines")
unlinked = env["pos.order"].search(
    [("laundry_order_id", "=", False)], order="id desc",
)
should_have = []
no_laundry_lines = []
for o in unlinked:
    has_laundry = any(
        line.product_id
        and line.product_id.product_tmpl_id.is_laundry_service
        and not line.product_id.product_tmpl_id.is_laundry_settlement
        for line in o.lines
    )
    if has_laundry:
        should_have.append(o)
    else:
        no_laundry_lines.append(o)
print(f"   should-have-link (skipped): {len(should_have)}")
print(f"   no-laundry-lines (correct skip): {len(no_laundry_lines)}")
for o in should_have[:10]:
    print(f"     pos.id={o.id:>3}  name={o.name!r}  partner={o.partner_id.name!r}")

# ── 5. Permission smoke — Cashier can run the hand-off ───────────────
print("\n[5] permission smoke — call _maybe_create_laundry_order as Cashier")
cashier_group = env.ref(
    "laundry_management.group_laundry_cashier", raise_if_not_found=False,
)
# Odoo 19: res.groups.users → res.groups.user_ids
group_users = (
    getattr(cashier_group, "user_ids", None)
    or getattr(cashier_group, "users", None)
) if cashier_group else None
if group_users:
    cashier = group_users[0]
    print(f"   using existing cashier: {cashier.login}")
    if should_have:
        target = should_have[0]
        try:
            with env.cr.savepoint():
                target.with_user(cashier).sudo()._maybe_create_laundry_order()
            print(f"   [OK] handoff callable as Cashier on POS {target.id}")
        except Exception:
            print(f"   !!! permission failure:")
            traceback.print_exc()
else:
    print("   no cashier user available — skipping (admin-only env)")

# ── 6. Optional heal ─────────────────────────────────────────────────
if HEAL:
    print("\n[6] HEALING")
    if seq:
        env["laundry.order.line"].sudo()._repair_tracking_sequence()
        seq_after = env["ir.sequence"].sudo().search(
            [("code", "=", SEQ_CODE)], limit=1,
        )
        print(f"   sequence repaired → number_next = {seq_after.number_next}")
    healed = 0
    for o in should_have:
        try:
            with env.cr.savepoint():
                o.sudo()._maybe_create_laundry_order()
            if o.laundry_order_id:
                healed += 1
        except Exception:
            print(f"   heal failed on POS {o.id}:")
            traceback.print_exc()
    print(f"   healed {healed} of {len(should_have)} missing links")
    env.cr.commit()
    print("   committed.")
else:
    print("\n[6] heal SKIPPED — set HEAL=True at top of script to backfill")

print("\n" + "=" * 70)
print("HEALTHCHECK DONE")
print("=" * 70)
