#!/usr/bin/env python3
"""
Phase 6: Import tickets → ERPNext Issue + Communication.
- Creates Issue Types from ticket_dept
- Imports open+pending tickets (1,395) as Issues
- Imports closed tickets (241K) as closed Issues
- Imports ticket_msg as Communication
- Maps parent/child (incident pattern)

Direct PostgreSQL. Detached.
Log: /tmp/migrate_tickets.log
"""
import os
import pymysql
import psycopg2
import uuid
from datetime import datetime, timezone
from html import unescape

# Creds via env (secret hors repo). Défauts alignés sur /opt/targo-sync (sync_services_incremental.py).
LEGACY = {"host": os.environ.get("LEGACY_HOST", "10.100.80.100"), "user": "facturation",
          "password": os.environ.get("LEGACY_PW", ""), "database": "gestionclient",
          "connect_timeout": 30, "read_timeout": 600}
PG = {"host": os.environ.get("PG_HOST", "db"), "port": 5432, "user": "postgres",
      "password": os.environ.get("PG_PW", "123"), "dbname": os.environ.get("PG_DB", "_eb65bdc0c4b1b2d6")}
PREVIEW = os.environ.get("PREVIEW", "0") == "1"  # compte les manquants + sort, N'ÉCRIT RIEN

ADMIN = "Administrator"
COMPANY = "TARGO"

# Priority mapping: legacy (0=urgent,1=high,2=normal,3=low)
PRIORITY_MAP = {0: "Urgent", 1: "High", 2: "Medium", 3: "Low"}
STATUS_MAP = {"open": "Open", "pending": "On Hold", "closed": "Closed"}

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

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

def ts_to_date(unix_ts):
    if not unix_ts or unix_ts <= 0:
        return None
    try:
        return datetime.fromtimestamp(int(unix_ts), tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
    except (ValueError, OSError):
        return None

def ts_to_dateonly(unix_ts):
    if not unix_ts or unix_ts <= 0:
        return None
    try:
        return datetime.fromtimestamp(int(unix_ts), tz=timezone.utc).strftime("%Y-%m-%d")
    except (ValueError, OSError):
        return None

def clean(val):
    if not val:
        return ""
    return unescape(str(val)).strip()

def log(msg):
    print(msg, flush=True)

def main():
    ts = now()
    log("=== Phase 6: Tickets Migration ===")

    # 1. Read legacy data
    log("Reading legacy data...")
    mc = pymysql.connect(**LEGACY)
    cur = mc.cursor(pymysql.cursors.DictCursor)

    cur.execute("SELECT * FROM ticket_dept ORDER BY id")
    depts = cur.fetchall()

    cur.execute("SELECT id, username, first_name, last_name, email FROM staff ORDER BY id")
    staff_list = cur.fetchall()

    # ALL tickets (open, pending, closed) — only needed columns (avoid wizard/wizard_fibre blobs)
    cur.execute("""SELECT id, account_id, delivery_id, subject, status, priority,
                          dept_id, date_create, parent, open_by, assign_to, important
                   FROM ticket ORDER BY id""")
    tickets = cur.fetchall()

    # Messages for open/pending tickets + last message for closed (for context)
    cur.execute("""
        SELECT m.id, m.ticket_id, m.staff_id, m.msg, m.date_orig FROM ticket_msg m
        JOIN ticket t ON m.ticket_id = t.id
        WHERE t.status IN ('open', 'pending')
        ORDER BY m.ticket_id, m.id
    """)
    open_msgs = cur.fetchall()

    # For closed tickets: just count (we'll import messages in batches later)
    cur.execute("SELECT COUNT(*) as cnt FROM ticket_msg WHERE ticket_id IN (SELECT id FROM ticket WHERE status = 'closed')")
    closed_msg_count = cur.fetchone()["cnt"]

    mc.close()
    log("  {} depts, {} staff, {} tickets".format(len(depts), len(staff_list), len(tickets)))
    log("  {} messages for open/pending tickets".format(len(open_msgs)))
    log("  {} messages for closed tickets (deferred)".format(closed_msg_count))

    # 2. Connect ERPNext PG
    log("Connecting to ERPNext PostgreSQL...")
    pg = psycopg2.connect(**PG)
    pgc = pg.cursor()

    # Customer mapping
    pgc.execute('SELECT legacy_account_id, name FROM "tabCustomer" WHERE legacy_account_id > 0')
    cust_map = {r[0]: r[1] for r in pgc.fetchall()}

    # Staff → User mapping (by email)
    pgc.execute('SELECT name, email FROM "tabUser" WHERE enabled = 1')
    user_by_email = {r[1]: r[0] for r in pgc.fetchall() if r[1]}

    staff_to_user = {}
    staff_name_by_id = {}   # ticket.assign_to → « Prénom Nom » (custom field assigned_staff, cf update_assigned_staff.py)
    for s in staff_list:
        email = s.get("email", "")
        if email and email in user_by_email:
            staff_to_user[s["id"]] = user_by_email[email]
        nm = ((s.get("first_name") or "") + " " + (s.get("last_name") or "")).strip() or (s.get("username") or "")
        if nm:
            staff_name_by_id[s["id"]] = nm

    # Existing Issue Types
    pgc.execute('SELECT name FROM "tabIssue Type"')
    existing_types = set(r[0] for r in pgc.fetchall())

    # Existing Issues by legacy_ticket_id
    pgc.execute('SELECT legacy_ticket_id FROM "tabIssue" WHERE legacy_ticket_id IS NOT NULL AND legacy_ticket_id > 0')
    existing_issues = set(r[0] for r in pgc.fetchall())

    # Issue Priority
    pgc.execute('SELECT name FROM "tabIssue Priority"')
    existing_priorities = set(r[0] for r in pgc.fetchall())

    log("  {} customers mapped, {} staff→user mapped".format(len(cust_map), len(staff_to_user)))

    # PREVIEW — compte les tickets manquants (pas encore en Issue), par statut, puis SORT sans écrire
    if PREVIEW:
        from collections import Counter
        miss = [t for t in tickets if t["id"] not in existing_issues]
        by_status = Counter(t.get("status", "?") for t in miss)
        log("")
        log("=== PREVIEW (aucune écriture) ===")
        log("  Tickets F total       : {}".format(len(tickets)))
        log("  Déjà en Issue (skip)  : {}".format(len(tickets) - len(miss)))
        log("  MANQUANTS à importer  : {}  {}".format(len(miss), dict(by_status)))
        if miss:
            log("  plage id manquants    : {} … {}".format(min(t["id"] for t in miss), max(t["id"] for t in miss)))
        pg.close()
        return

    # 3. Create Issue Priorities
    for pname in ["Urgent", "High", "Medium", "Low"]:
        if pname not in existing_priorities:
            pgc.execute("""
                INSERT INTO "tabIssue Priority" (name, creation, modified, modified_by, owner, docstatus, idx)
                VALUES (%s, %s, %s, %s, %s, 0, 0)
            """, (pname, ts, ts, ADMIN, ADMIN))
    pg.commit()
    log("  Issue Priorities created")

    # 4. Create Issue Types from ticket_dept
    log("")
    log("--- Creating Issue Types ---")
    dept_map = {}  # dept_id → Issue Type name
    types_created = 0

    for d in depts:
        name = clean(d["name"])
        if not name:
            continue
        dept_map[d["id"]] = name
        if name in existing_types:
            continue
        pgc.execute("""
            INSERT INTO "tabIssue Type" (name, creation, modified, modified_by, owner, docstatus, idx)
            VALUES (%s, %s, %s, %s, %s, 0, 0)
        """, (name, ts, ts, ADMIN, ADMIN))
        existing_types.add(name)
        types_created += 1

    pg.commit()
    log("  {} Issue Types created".format(types_created))

    # 5. Import Tickets as Issues
    log("")
    log("--- Importing Tickets → Issues ---")

    # Build message lookup by ticket_id
    msgs_by_ticket = {}
    for m in open_msgs:
        msgs_by_ticket.setdefault(m["ticket_id"], []).append(m)

    # Track legacy_ticket_id → ERPNext issue name for parent mapping
    ticket_to_issue = {}
    i_ok = i_skip = i_err = 0
    comm_ok = 0

    for i, t in enumerate(tickets):
        tid = t["id"]
        if tid in existing_issues:
            i_skip += 1
            continue

        subject = clean(t.get("subject")) or "Ticket #{}".format(tid)
        status = STATUS_MAP.get(t.get("status", "open"), "Open")
        priority = PRIORITY_MAP.get(t.get("priority", 2), "Medium")
        dept_name = dept_map.get(t.get("dept_id"), None)
        cust_name = cust_map.get(t.get("account_id"))
        is_important = 1 if t.get("important") else 0
        assigned_staff = staff_name_by_id.get(t.get("assign_to")) if t.get("assign_to") else None

        opening_date = ts_to_dateonly(t.get("date_create"))
        opening_time = None
        if t.get("date_create") and t["date_create"] > 0:
            try:
                opening_time = datetime.fromtimestamp(int(t["date_create"]), tz=timezone.utc).strftime("%H:%M:%S")
            except (ValueError, OSError):
                pass

        # Is this an incident (parent)?
        is_incident = 0
        # Check if other tickets reference this one as parent
        # We'll do a second pass for this

        issue_name = uid("ISS-")

        try:
            # Savepoint so errors only rollback THIS ticket, not the whole batch
            pgc.execute("SAVEPOINT sp_ticket")
            pgc.execute("""
                INSERT INTO "tabIssue" (
                    name, creation, modified, modified_by, owner, docstatus, idx,
                    naming_series, subject, status, priority, issue_type,
                    customer, company, opening_date, opening_time,
                    legacy_ticket_id, is_incident, is_important,
                    parent_incident, service_location, assigned_staff
                ) VALUES (
                    %s, %s, %s, %s, %s, 0, 0,
                    'ISS-.YYYY.-', %s, %s, %s, %s,
                    %s, %s, %s, %s,
                    %s, %s, %s, %s, %s, %s
                )
            """, (issue_name, ts, ts, ADMIN, ADMIN,
                  subject[:255], status, priority, dept_name,
                  cust_name, COMPANY, opening_date, opening_time,
                  tid, 0, is_important, None, None, assigned_staff))

            ticket_to_issue[tid] = issue_name
            i_ok += 1

            # Import messages for open/pending tickets
            for m in msgs_by_ticket.get(tid, []):
                msg_text = m.get("msg") or ""
                if not msg_text.strip():
                    continue
                sender = staff_to_user.get(m.get("staff_id"), ADMIN)
                msg_date = ts_to_date(m.get("date_orig"))

                comm_name = uid("COM-")
                try:
                    pgc.execute("""
                        INSERT INTO "tabComment" (
                            name, creation, modified, modified_by, owner, docstatus, idx,
                            comment_type, comment_by, content,
                            reference_doctype, reference_name
                        ) VALUES (
                            %s, %s, %s, %s, %s, 0, 0,
                            'Comment', %s, %s,
                            'Issue', %s
                        )
                    """, (comm_name, msg_date or ts, msg_date or ts, ADMIN, ADMIN,
                          sender, msg_text,
                          issue_name))
                    comm_ok += 1
                except Exception:
                    pgc.execute("ROLLBACK TO SAVEPOINT sp_ticket")
                    pgc.execute("SAVEPOINT sp_ticket")
                    # Skip message but keep the Issue

            pgc.execute("RELEASE SAVEPOINT sp_ticket")

        except Exception as e:
            i_err += 1
            pgc.execute("ROLLBACK TO SAVEPOINT sp_ticket")
            pgc.execute("RELEASE SAVEPOINT sp_ticket")
            if i_err <= 20:
                log("  ERR ticket#{} -> {}".format(tid, str(e)[:100]))
            continue

        if i_ok % 2000 == 0:
            pg.commit()
            log("  [{}/{}] issues={} comm={} skip={} err={}".format(
                i+1, len(tickets), i_ok, comm_ok, i_skip, i_err))

    pg.commit()

    # 6. Second pass: set parent_incident for sub-tickets
    log("")
    log("--- Setting parent/child relationships ---")
    parent_set = 0
    incident_set = 0

    for t in tickets:
        if t.get("parent") and t["parent"] > 0:
            child_issue = ticket_to_issue.get(t["id"])
            parent_issue = ticket_to_issue.get(t["parent"])
            if child_issue and parent_issue:
                pgc.execute("""
                    UPDATE "tabIssue" SET parent_incident = %s WHERE name = %s
                """, (parent_issue, child_issue))
                parent_set += 1

                # Mark parent as incident
                pgc.execute("""
                    UPDATE "tabIssue" SET is_incident = 1 WHERE name = %s AND is_incident = 0
                """, (parent_issue,))
                incident_set += 1

    # Update affected_clients count for incidents
    pgc.execute("""
        UPDATE "tabIssue" i SET affected_clients = (
            SELECT COUNT(*) FROM "tabIssue" child WHERE child.parent_incident = i.name
        ) WHERE i.is_incident = 1
    """)

    pg.commit()

    log("  {} parent links set, {} incidents identified".format(parent_set, incident_set))

    # 7. Update is_important on ALL tickets (existing + new)
    log("")
    log("--- Updating is_important flag ---")
    imp_map = {t["id"]: 1 for t in tickets if t.get("important")}
    imp_updated = 0
    if imp_map:
        imp_ids = list(imp_map.keys())
        pgc.execute("""
            UPDATE "tabIssue" SET is_important = 1
            WHERE legacy_ticket_id = ANY(%s) AND (is_important IS NULL OR is_important = 0)
        """, (imp_ids,))
        imp_updated = pgc.rowcount
        pg.commit()
    log("  {} tickets marked as important".format(imp_updated))

    pg.close()

    log("")
    log("=" * 60)
    log("Issue Types:    {} created".format(types_created))
    log("Issues:         {} created, {} skipped, {} errors".format(i_ok, i_skip, i_err))
    log("Communications: {} (open/pending tickets only)".format(comm_ok))
    log("Parent/child:   {} links, {} incidents".format(parent_set, incident_set))
    log("Closed msgs:    {} deferred (import separately if needed)".format(closed_msg_count))
    log("=" * 60)
    log("")
    log("Next: bench --site erp.gigafibre.ca clear-cache")

if __name__ == "__main__":
    main()
