from __future__ import annotations

import sys
from typing import Optional

from PySide6.QtWidgets import (
    QApplication,
    QMainWindow,
    QWidget,
    QTabWidget,
    QListWidget,
    QVBoxLayout,
    QHBoxLayout,
    QPushButton,
    QLabel,
    QLineEdit,
    QMessageBox,
    QDialog,
    QFormLayout,
    QComboBox,
    QCheckBox,
    QTextEdit,
    QTreeWidget,
    QTreeWidgetItem,
    QCompleter,
    QToolBar,
    QHeaderView,
    QAbstractItemView,
    QGroupBox,
)
from PySide6.QtCore import Qt, QStringListModel
from PySide6.QtGui import QKeySequence, QShortcut, QIcon, QAction, QColor, QBrush
import pathlib

from . import db
from .gui_helpers import (
    create_labeled_input,
    create_button_row,
    create_search_bar,
    create_section_header,
    create_list_section,
    create_form_section,
)

"""
Try to import compiled Qt resources (generated by `pyside6-rcc`) so
resource paths like `:/icons/add.svg` are available. This import is
optional — code will fall back to the asset files if resources are
not compiled.
"""
try:
    from . import resources_rc  # type: ignore
except Exception:
    pass


def _load_icon(name: str) -> QIcon:
    """Always load an icon from the package assets folder, skipping Qt resource path."""
    p = pathlib.Path(__file__).parent / "assets" / "icons" / f"{name}.svg"
    if p.exists():
        return QIcon(str(p))
    return QIcon()  # return empty icon if not found


def _attach_dynamic_completer(line_edit: QLineEdit, fetch_fn, debounce_ms: int = 150):
    """Attach a debounced QCompleter to `line_edit` that updates suggestions by calling `fetch_fn(prefix)`.

    `fetch_fn` should accept a prefix string and return an iterable of suggestion strings.
    The completer updates after `debounce_ms` milliseconds of idle typing.
    """
    model = QStringListModel()
    completer = QCompleter(model, line_edit)
    completer.setCaseSensitivity(Qt.CaseInsensitive)
    completer.setFilterMode(Qt.MatchContains)
    line_edit.setCompleter(completer)

    from PySide6.QtCore import QTimer

    timer = QTimer(line_edit)
    timer.setSingleShot(True)

    # store last text to avoid redundant DB calls
    state = {"last": ""}

    def do_update():
        text = line_edit.text().strip()
        if text == state["last"]:
            return
        state["last"] = text
        try:
            suggestions = list(fetch_fn(text) or [])
            strs = [s if isinstance(s, str) else str(s) for s in suggestions]
            model.setStringList(strs)
        except Exception:
            model.setStringList([])

    def on_text_edited(_text: str):
        timer.start(debounce_ms)

    timer.timeout.connect(do_update)
    line_edit.textEdited.connect(on_text_edited)
    return completer


class SimpleDialog(QDialog):
    def __init__(self, title: str, parent: Optional[QWidget] = None):
        super().__init__(parent)
        self.setWindowTitle(title)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Licensing Specialist (PySide6)")
        db.init_db()
        self._build()

    def _build(self) -> None:
        central = QWidget()
        main_layout = QVBoxLayout(central)
        self.tabs = QTabWidget()
        main_layout.addWidget(self.tabs)
        self.setCentralWidget(central)

        # toolbar with visible accelerators
        tb = QToolBar("Main")
        tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        add_act = QAction(_load_icon("add"), "Add (Ctrl+N)", self)
        add_act.setShortcut(QKeySequence("Ctrl+N"))
        add_act.triggered.connect(self._global_add)
        edit_act = QAction(_load_icon("edit"), "Edit (Ctrl+E)", self)
        edit_act.setShortcut(QKeySequence("Ctrl+E"))
        edit_act.triggered.connect(self._global_edit)
        del_act = QAction(_load_icon("delete"), "Delete (Del)", self)
        del_act.setShortcut(QKeySequence("Del"))
        del_act.triggered.connect(self._global_delete)
        tb.addAction(add_act); tb.addAction(edit_act); tb.addAction(del_act)
        self.addToolBar(tb)

        # Tabs
        from .tabs.recruiter_tab import setup_recruiter_tab
        setup_recruiter_tab(self)
        from .tabs.trainee_tab import setup_trainee_tab
        setup_trainee_tab(self)
        from .tabs.class_tab import setup_class_tab
        setup_class_tab(self)
        from .tabs.exam_tab import setup_exam_tab
        setup_exam_tab(self)
        from .tabs.license_tab import setup_license_tab
        setup_license_tab(self)

        # persistent status bar
        try:
            self.statusBar().showMessage("Ready")
        except Exception:
            # fallback: keep a label if statusBar not available
            self.statusLabel = QLabel("")
            self.statusLabel.setObjectName("statusLabel")
            self.statusLabel.setStyleSheet("color: gray; padding: 6px;")
            main_layout.addWidget(self.statusLabel)

        # helper for showing transient status messages

        def _show_status(msg: str, timeout: int = 5000) -> None:
            try:
                self.statusBar().showMessage(msg, timeout)
            except Exception:
                try:
                    self.statusLabel.setText(msg)
                except Exception:
                    pass

        # Information panel for provincial exam data (move to _build_exams_tab only)

        # attach helper to instance for use across methods
        self._show_status = _show_status


        # Connect trainee selection to info panel update (moved to _build_exams_tab)

    def _update_prov_exam_info(self):
        self.prov_exam_info.clear()
        t = self.exam_trainee.currentText()
        if not t or ':' not in t:
            return
        tid = int(t.split(":", 1)[0])
        # Fetch all exams for this trainee
        exams = [e for e in db.list_exams() if e['trainee_id'] == tid]
        for e in exams:
            mod = e['module'] if 'module' in e.keys() and e['module'] is not None else ''
            score = e['score'] if 'score' in e.keys() and e['score'] is not None else ''
            result = 'Pass' if ('passed' in e.keys() and e['passed'] == 1) else ('Fail' if ('passed' in e.keys() and e['passed'] == 0) else '—')
            notes = e['notes'] if 'notes' in e.keys() and e['notes'] is not None else ''
            date = e['exam_date'] if 'exam_date' in e.keys() and e['exam_date'] is not None else ''
            QTreeWidgetItem(self.prov_exam_info, [str(date), str(mod), str(score), result, notes])
        # Update practice exam panel for the current trainee
        self._update_practice_exam_panel()
        # keyboard shortcuts (global, act based on current tab)
        self._sc_new = QShortcut(QKeySequence("Ctrl+N"), self)
        self._sc_new.activated.connect(self._global_add)
        self._sc_edit = QShortcut(QKeySequence("Ctrl+E"), self)
        self._sc_edit.activated.connect(self._global_edit)
        self._sc_del = QShortcut(QKeySequence("Del"), self)
        self._sc_del.activated.connect(self._global_delete)

    # ---------- Recruiters ----------
    def _build_recruiters_tab(self) -> None:
        w = QWidget()
        l = QHBoxLayout(w)

        # Use helper for form section
        self.rec_name = QLineEdit()
        self.rec_email = QLineEdit()
        self.rec_phone = QLineEdit()
        self.rec_rep = QLineEdit()
        add_btn = QPushButton("Add Recruiter")
        add_btn.clicked.connect(self._add_recruiter)
        form_fields = [
            ("Name", self.rec_name),
            ("Phone", self.rec_phone),
            ("Rep code (5 alnum)", self.rec_rep),
        ]
        left = create_form_section(form_fields)
        left.addWidget(add_btn)

        # dynamic completers that query the DB as the user types
        try:
            self._rec_name_completer = _attach_dynamic_completer(self.rec_name, lambda p: [r['name'] for r in db.search_recruiters_by_name(p)])
            self._rec_name_completer.activated.connect(self._on_rec_name_completer)
        except Exception:
            self._rec_name_completer = None
        try:
            self._rec_rep_completer = _attach_dynamic_completer(self.rec_rep, lambda p: [r['rep_code'] for r in db.search_recruiters_by_rep(p)])
            self._rec_rep_completer.activated.connect(self._on_rec_rep_completer)
        except Exception:
            self._rec_rep_completer = None

        l.addLayout(left, 1)

        right = QVBoxLayout()
        # Search + multi-column recruiters view
        self.rec_search = create_search_bar("Search recruiters...", self._filter_recruiters)
        right.addWidget(self.rec_search)

        self.rec_table = QTreeWidget()
        self.rec_table.setColumnCount(4)
        self.rec_table.setHeaderLabels(["ID", "Name", "Email", "Rep"])
        self.rec_table.itemSelectionChanged.connect(self._on_rec_select)
        # table polish
        self.rec_table.setAlternatingRowColors(True)
        self.rec_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        try:
            self.rec_table.header().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        except Exception:
            try:
                self.rec_table.header().setSectionResizeMode(QHeaderView.Stretch)
            except Exception:
                pass
        right.addWidget(self.rec_table)

        btns = create_button_row([
            ("Edit", self._edit_recruiter),
            ("Delete", self._delete_recruiter),
        ])
        right.addLayout(btns)

        self.rec_details = QTreeWidget()
        self.rec_details.setHeaderHidden(True)
        right.addWidget(create_section_header("Details"))
        right.addWidget(self.rec_details)

        l.addLayout(right, 2)
        self.tabs.addTab(w, "Recruiters")
        self._refresh_recruiters()

    def _add_recruiter(self) -> None:
        name = self.rec_name.text().strip()
        if not name:
            QMessageBox.warning(self, "Validation", "Name is required")
            return
        try:
            db.add_recruiter(name, self.rec_email.text().strip() or None, self.rec_phone.text().strip() or None, self.rec_rep.text().strip() or None)
        except Exception as exc:
            QMessageBox.critical(self, "Error", f"Failed to add recruiter: {exc}")
            return
        self.rec_name.clear(); self.rec_email.clear(); self.rec_phone.clear(); self.rec_rep.clear()
        self._refresh_recruiters()
        try:
            self._refresh_tr_dropdowns()
        except Exception:
            pass
        try:
            self._show_status(f"Added recruiter: {name}")
        except Exception:
            pass

    def _refresh_recruiters(self) -> None:
        self.rec_table.clear()
        self._rec_rows = db.list_recruiters()
        for r in self._rec_rows:
            it = QTreeWidgetItem([str(r['id']), r['name'] or '', r['email'] or '', r['rep_code'] or ''])
            it.setData(0, Qt.UserRole, r['id'])
            self.rec_table.addTopLevelItem(it)
        self._filter_recruiters()

    def _filter_recruiters(self) -> None:
        q = self.rec_search.text().strip().lower()
        for i in range(self.rec_table.topLevelItemCount()):
            it = self.rec_table.topLevelItem(i)
            if not q:
                it.setHidden(False)
                continue
            found = False
            for c in range(self.rec_table.columnCount()):
                if q in (it.text(c) or '').lower():
                    found = True; break
            it.setHidden(not found)

    def _on_rec_select(self) -> None:
        items = self.rec_table.selectedItems()
        self.rec_details.clear()
        if not items:
            return
        sel_item = items[0]
        rid = sel_item.data(0, Qt.UserRole)
        r = next((x for x in self._rec_rows if x['id'] == rid), None)
        if r is None:
            return
        root = QTreeWidgetItem([f"Recruiter: {r['name']} (ID: {r['id']})"])
        if r['email']:
            QTreeWidgetItem(root, [f"Email: {r['email']}"])
        if r['phone']:
            QTreeWidgetItem(root, [f"Phone: {r['phone']}"])
        if r['rep_code']:
            QTreeWidgetItem(root, [f"Rep code: {r['rep_code']}"])

        # trainees
        conn = db.get_conn()
        cur = conn.cursor()
        cur.execute("SELECT * FROM trainee WHERE recruiter_id = ? ORDER BY last_name, first_name", (r['id'],))
        trainees = cur.fetchall()
        if trainees:
            tp = QTreeWidgetItem(root, ["Trainees"])
            for t in trainees:
                tn = QTreeWidgetItem(tp, [f"{t['id']}: {t['last_name']}, {t['first_name']} (IBA Date: {t['dob'] or '—'})"])
                try:
                    modules_complete = db.all_practice_modules_marked_complete(t['id'])
                    # Get first provincial exam date
                    cur2 = db.get_conn().cursor()
                    cur2.execute("SELECT MIN(exam_date) as first_date FROM exam WHERE trainee_id = ? AND is_practice = 0", (t['id'],))
                    first_exam_row = cur2.fetchone()
                    first_exam_date = first_exam_row['first_date'] if first_exam_row else None
                    seewhy = db.check_seewhy_guarantee(t['id'], first_exam_date) if first_exam_date else False
                    status_item = QTreeWidgetItem(tn, [f"Practice Exams: {'Y' if modules_complete else 'N'} | SeeWhy Guarantee: {'Y' if seewhy else 'N'}"])
                    if seewhy:
                        status_item.setForeground(0, QBrush(QColor("green")))
                except Exception:
                    pass

                # classes
                cur.execute("SELECT c.* FROM class c JOIN trainee_class tc ON c.id = tc.class_id WHERE tc.trainee_id = ? ORDER BY c.start_date", (t['id'],))
                classes = cur.fetchall()
                if classes:
                    cp = QTreeWidgetItem(tn, ["Classes"]) 
                    for c in classes:
                        QTreeWidgetItem(cp, [f"{c['id']}: {c['name']} ({c['start_date'] or '—'} → {c['end_date'] or '—'})"])

                # exams
                cur.execute("SELECT * FROM exam WHERE trainee_id = ? ORDER BY exam_date DESC", (t['id'],))
                exams = cur.fetchall()
                if exams:
                    ep = QTreeWidgetItem(tn, ["Exams"]) 
                    for e in exams:
                        mod = f"[{e['module']}] " if 'module' in e.keys() and e['module'] else ''
                        practice = "(practice) " if 'is_practice' in e.keys() and e['is_practice'] else ''
                        passed = 'Pass' if ('passed' in e.keys() and e['passed'] == 1) else ('Fail' if ('passed' in e.keys() and e['passed'] == 0) else '—')
                        reimb = ' [Reimb requested]' if ('reimbursement_requested' in e.keys() and e['reimbursement_requested']) else ''
                        QTreeWidgetItem(ep, [f"{e['id']}: {e['exam_date'] or '—'} | {mod}{practice}Score: {e['score'] or '—'} | {passed}{reimb}"])

                # licenses
                cur.execute("SELECT * FROM license WHERE trainee_id = ? ORDER BY application_submitted_date DESC", (t['id'],))
                licenses = cur.fetchall()
                if licenses:
                    lp = QTreeWidgetItem(tn, ["Licenses"]) 
                    for l in licenses:
                        QTreeWidgetItem(lp, [f"{l['id']}: Applied: {l['application_submitted_date'] or '—'} | Approved: {l['approval_date'] or '—'} | Status: {l['status'] or '—'}"])

        conn.close()
        self.rec_details.addTopLevelItem(root)
        root.setExpanded(True)
        # status
        try:
            self._show_status(f"Selected recruiter: {r['name']}")
        except Exception:
            pass

    def _edit_recruiter(self) -> None:
        items = self.rec_table.selectedItems()
        if not items:
            QMessageBox.warning(self, "Edit Recruiter", "Select a recruiter to edit")
            return
        rid = items[0].data(0, Qt.UserRole)
        r = next((x for x in self._rec_rows if x['id'] == rid), None)
        if not r:
            QMessageBox.warning(self, "Edit Recruiter", "Selected recruiter not found")
            return
        dlg = QDialog(self)
        dlg.setWindowTitle("Edit Recruiter")
        form = QFormLayout(dlg)
        name = QLineEdit(r['name'] or "")
        email = QLineEdit(r['email'] or "")
        phone = QLineEdit(r['phone'] or "")
        rep = QLineEdit(r['rep_code'] or "")
        form.addRow("Name", name)
        form.addRow("Email", email)
        form.addRow("Phone", phone)
        form.addRow("Rep code", rep)
        btns = QHBoxLayout()
        save = QPushButton("Save")
        delete = QPushButton("Delete")
        cancel = QPushButton("Cancel")
        btns.addWidget(save); btns.addWidget(delete); btns.addWidget(cancel)
        form.addRow(btns)

        def do_save():
            try:
                db.update_recruiter(r['id'], name.text().strip(), email.text().strip() or None, phone.text().strip() or None, rep.text().strip() or None)
            except Exception as exc:
                QMessageBox.critical(self, "Error", f"Failed to update recruiter: {exc}"); return
            dlg.accept()

        def do_delete():
            if QMessageBox.question(self, "Delete Recruiter", f"Delete recruiter '{r['name']}'?") != QMessageBox.StandardButton.Yes:
                return
            db.delete_recruiter(r['id']); dlg.accept()

        save.clicked.connect(do_save)
        delete.clicked.connect(do_delete)
        cancel.clicked.connect(dlg.reject)
        if dlg.exec() == QDialog.Accepted:
            self._refresh_recruiters()
            # refresh completers
            try:
                self._rec_name_completer.model().setStringList([r['name'] for r in db.list_recruiters()])
            except Exception:
                pass
            try:
                self._rec_rep_completer.model().setStringList([r['rep_code'] for r in db.list_recruiters() if r['rep_code']])
            except Exception:
                pass
            try:
                self._show_status(f"Updated recruiter: {r['name']}")
            except Exception:
                pass

    def _delete_recruiter(self) -> None:
        items = self.rec_table.selectedItems()
        if not items:
            return
        rid = items[0].data(0, Qt.UserRole)
        r = next((x for x in self._rec_rows if x['id'] == rid), None)
        if not r:
            return
        if QMessageBox.question(self, "Delete Recruiter", f"Delete recruiter '{r['name']}'?") == QMessageBox.StandardButton.Yes:
            db.delete_recruiter(r['id'])
            self._refresh_recruiters()
            try:
                self._rec_name_completer.model().setStringList([r['name'] for r in db.list_recruiters()])
            except Exception:
                pass
            try:
                self._rec_rep_completer.model().setStringList([r['rep_code'] for r in db.list_recruiters() if r['rep_code']])
            except Exception:
                pass
            try:
                self._show_status(f"Deleted recruiter: {r['name']}")
            except Exception:
                pass

    def _on_rec_name_completer(self, text: str) -> None:
        # populate fields when a name is selected from completer
        try:
            r = db.find_recruiter_by_name(text)
            if r:
                self.rec_email.setText(r['email'] or "")
                self.rec_phone.setText(r['phone'] or "")
                self.rec_rep.setText(r['rep_code'] or "")
        except Exception:
            pass

    def _on_rec_rep_completer(self, text: str) -> None:
        try:
            r = db.find_recruiter_by_rep_code(text)
            if r:
                self.rec_name.setText(r['name'] or "")
                self.rec_email.setText(r['email'] or "")
                self.rec_phone.setText(r['phone'] or "")
        except Exception:
            pass

    # ---------- Trainees ----------
    def _build_trainees_tab(self) -> None:
        w = QWidget()
        l = QHBoxLayout(w)

        # Use helper for form section
        self.tr_first = QLineEdit()
        self.tr_last = QLineEdit()
        self.tr_dob = QLineEdit()
        self.tr_rep = QLineEdit()
        self.tr_recruiter = QComboBox()
        btn_add = QPushButton("Add Trainee")
        btn_add.clicked.connect(self._add_trainee)
        btn_edit = QPushButton("Edit Trainee")
        btn_edit.clicked.connect(self._edit_trainee)
        btn_delete = QPushButton("Delete Trainee")
        btn_delete.clicked.connect(self._delete_trainee)
        form_fields = [
            ("First name", self.tr_first),
            ("Last name", self.tr_last),
            ("IBA Date (YYYY-MM-DD)", self.tr_dob),
            ("Rep code (5 alnum)", self.tr_rep),
            ("Recruiter", self.tr_recruiter),
        ]
        left = create_form_section(form_fields)
        btns = create_button_row([
            ("Add Trainee", self._add_trainee),
            ("Edit Trainee", self._edit_trainee),
            ("Delete Trainee", self._delete_trainee),
        ])
        left.addLayout(btns)

        l.addLayout(left, 1)

        right = QVBoxLayout()
        self.tr_search = create_search_bar("Search trainees...", self._filter_trainees)
        right.addWidget(self.tr_search)

        self.tr_table = QTreeWidget()
        self.tr_table.setColumnCount(4)
        self.tr_table.setHeaderLabels(["ID", "Last", "First", "Recruiter"])
        self.tr_table.itemSelectionChanged.connect(self._on_tr_select)
        self.tr_table.setAlternatingRowColors(True)
        self.tr_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        try:
            self.tr_table.header().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        except Exception:
            try:
                self.tr_table.header().setSectionResizeMode(QHeaderView.Stretch)
            except Exception:
                pass
        right.addWidget(create_section_header("Trainees"))
        right.addWidget(self.tr_table)
        right.addWidget(create_section_header("Details"))
        self.tr_details = QTreeWidget()
        self.tr_details.setHeaderHidden(True)
        right.addWidget(self.tr_details)

        l.addLayout(right, 2)
        self.tabs.addTab(w, "Trainees")
        self._refresh_tr_dropdowns()
        self._refresh_trainees()
        # setup simple completers for trainee name/rep fields
        try:
            self._tr_name_completer = _attach_dynamic_completer(self.tr_first, lambda p: [f"{t['first_name']} {t['last_name']}" for t in db.search_trainees_by_name(p)])
            self._tr_name_completer.activated.connect(self._on_tr_name_completer)
        except Exception:
            self._tr_name_completer = None
        try:
            self._tr_rep_completer = _attach_dynamic_completer(self.tr_rep, lambda p: [t['rep_code'] for t in db.search_trainees_by_rep(p)])
            self._tr_rep_completer.activated.connect(self._on_tr_rep_completer)
        except Exception:
            self._tr_rep_completer = None

    def _on_tr_name_completer(self, text: str) -> None:
        try:
            parts = text.split()
            if len(parts) >= 2:
                first = parts[0]
                last = parts[-1]
                t = db.find_trainee_by_name(first, last)
                if t:
                    self.tr_first.setText(t['first_name'] or "")
                    self.tr_last.setText(t['last_name'] or "")
                    self.tr_dob.setText(t['dob'] or "")
                    self.tr_rep.setText(t['rep_code'] or "")
                    if t['recruiter_id']:
                        for i in range(self.tr_recruiter.count()):
                            if self.tr_recruiter.itemText(i).startswith(str(t['recruiter_id']) + ':'):
                                self.tr_recruiter.setCurrentIndex(i)
                                break
        except Exception:
            pass

    def _on_tr_rep_completer(self, text: str) -> None:
        try:
            t = db.find_trainee_by_rep_code(text)
            if t:
                self.tr_first.setText(t['first_name'] or "")
                self.tr_last.setText(t['last_name'] or "")
                self.tr_dob.setText(t['dob'] or "")
                if t['recruiter_id']:
                    for i in range(self.tr_recruiter.count()):
                        if self.tr_recruiter.itemText(i).startswith(str(t['recruiter_id']) + ':'):
                            self.tr_recruiter.setCurrentIndex(i)
                            break
        except Exception:
            pass

    def _add_trainee(self) -> None:
        first = self.tr_first.text().strip()
        last = self.tr_last.text().strip()
        if not first or not last:
            QMessageBox.warning(self, "Validation", "First and last names are required")
            return
        recruiter_id = None
        rec = self.tr_recruiter.currentText()
        if rec:
            recruiter_id = int(rec.split(":", 1)[0])
        dob = self.tr_dob.text().strip() or None
        try:
            db.add_trainee(first, last, dob, recruiter_id, self.tr_rep.text().strip() or None)
        except Exception as exc:
            QMessageBox.critical(self, "Error", f"Failed to add trainee: {exc}")
            return
        self.tr
        self._refresh_tr_dropdowns(); self._refresh_trainees()
        try:
            self._refresh_tc_dropdowns()
        except Exception:
            pass
        try:
            self._show_status(f"Added trainee: {first} {last}")
        except Exception:
            pass

    def _edit_trainee(self) -> None:
        items = self.tr_table.selectedItems()
        if not items:
            QMessageBox.warning(self, "Edit Trainee", "Select a trainee to edit")
            return
        tid = items[0].data(0, Qt.UserRole)
        t = next((x for x in self._tr_rows if x['id'] == tid), None)
        if not t:
            QMessageBox.warning(self, "Edit Trainee", "Selected trainee not found")
            return
        dlg = QDialog(self)
        dlg.setWindowTitle("Edit Trainee")
        form = QFormLayout(dlg)
        first = QLineEdit(t['first_name'] or "")
        last = QLineEdit(t['last_name'] or "")
        dob = QLineEdit(t['dob'] or "")
        rep = QLineEdit(t['rep_code'] or "")
        rec_combo = QComboBox()
        recs = [f"{r['id']}: {r['name']}" for r in db.list_recruiters()]
        rec_combo.addItems(recs)
        if t['recruiter_id']:
            for i in range(rec_combo.count()):
                if rec_combo.itemText(i).startswith(str(t['recruiter_id']) + ':'):
                    rec_combo.setCurrentIndex(i)
                    break
        form.addRow("First name", first)
        form.addRow("Last name", last)
        form.addRow("IBA Date", dob)
        form.addRow("Rep code", rep)
        form.addRow("Recruiter", rec_combo)
        btns = QHBoxLayout()
        save = QPushButton("Save")
        delete = QPushButton("Delete")
        cancel = QPushButton("Cancel")
        btns.addWidget(save); btns.addWidget(delete); btns.addWidget(cancel)
        form.addRow(btns)

        def do_save():
            first_s = first.text().strip(); last_s = last.text().strip()
            if not first_s or not last_s:
                QMessageBox.warning(self, "Validation", "First and last names are required"); return
            recruiter_id = None
            rec_text = rec_combo.currentText()
            if rec_text:
                recruiter_id = int(rec_text.split(":",1)[0])
            try:
                db.update_trainee(tid, first_s, last_s, dob.text().strip() or None, recruiter_id, rep.text().strip() or None)
            except Exception as exc:
                QMessageBox.critical(self, "Error", f"Failed to update trainee: {exc}"); return
            dlg.accept()

        def do_delete():
            if QMessageBox.question(self, 'Delete Trainee', 'Delete this trainee? Exams, licenses and class links will be removed.') != QMessageBox.StandardButton.Yes:
                return
            db.delete_trainee(tid); dlg.accept()

        save.clicked.connect(do_save)
        delete.clicked.connect(do_delete)
        cancel.clicked.connect(dlg.reject)
        if dlg.exec() == QDialog.Accepted:
            self._refresh_tr_dropdowns(); self._refresh_trainees(); self._refresh_tc_dropdowns(); self._refresh_exam_dropdowns(); self._refresh_license_dropdowns()

    def _delete_trainee(self) -> None:
        items = self.tr_table.selectedItems()
        if not items:
            return
        tid = items[0].data(0, Qt.UserRole)
        t = next((x for x in self._tr_rows if x['id'] == tid), None)
        if not t:
            return
        if QMessageBox.question(self, 'Delete Trainee', 'Delete this trainee? Exams, licenses and class links will be removed.') != QMessageBox.StandardButton.Yes:
            return
        db.delete_trainee(tid)
        self._refresh_tr_dropdowns(); self._refresh_trainees(); self._refresh_tc_dropdowns(); self._refresh_exam_dropdowns(); self._refresh_license_dropdowns()
        try:
            self._show_status(f"Deleted trainee: {t['first_name']} {t['last_name']}")
        except Exception:
            pass

    def _refresh_tr_dropdowns(self) -> None:
        recs = db.list_recruiters()
        items = [f"{r['id']}: {r['name']}" for r in recs]
        self.tr_recruiter.clear(); self.tr_recruiter.addItems(items)

    def _refresh_trainees(self) -> None:
        self.tr_table.clear()
        rows = db.list_trainees()
        self._tr_rows = rows
        for t in rows:
            recruiter_name = t['recruiter_name'] or ''
            it = QTreeWidgetItem([str(t['id']), t['last_name'] or '', t['first_name'] or '', recruiter_name])
            it.setData(0, Qt.UserRole, t['id'])
            self.tr_table.addTopLevelItem(it)
        self._filter_trainees()

    def _filter_trainees(self) -> None:
        q = self.tr_search.text().strip().lower()
        for i in range(self.tr_table.topLevelItemCount()):
            it = self.tr_table.topLevelItem(i)
            if not q:
                it.setHidden(False)
                continue
            found = False
            for c in range(self.tr_table.columnCount()):
                if q in (it.text(c) or '').lower():
                    found = True; break
            it.setHidden(not found)

    def _refresh_trainees(self) -> None:
        self.tr_table.clear()
        rows = db.list_trainees()
        self._tr_rows = rows
        for t in rows:
            recruiter_name = t['recruiter_name'] or ''
            it = QTreeWidgetItem([str(t['id']), t['last_name'] or '', t['first_name'] or '', recruiter_name])
            it.setData(0, Qt.UserRole, t['id'])
            self.tr_table.addTopLevelItem(it)
        self._filter_trainees()

    def _on_tr_select(self) -> None:
        items = self.tr_table.selectedItems()
        self.tr_details.clear()
        if not items:
            return
        sel_item = items[0]
        tid = sel_item.data(0, Qt.UserRole)
        t = next((x for x in self._tr_rows if x['id'] == tid), None)
        if t is None:
            return
        root = QTreeWidgetItem([f"Trainee: {t['first_name']} {t['last_name']} (ID: {t['id']})"])
        if t['dob']:
            QTreeWidgetItem(root, [f"IBA Date: {t['dob']}"])
        try:
            modules_complete = db.all_practice_modules_marked_complete(t['id'])
            # Get first provincial exam date
            conn2 = db.get_conn()
            cur2 = conn2.cursor()
            cur2.execute("SELECT MIN(exam_date) as first_date FROM exam WHERE trainee_id = ? AND is_practice = 0", (t['id'],))
            first_exam_row = cur2.fetchone()
            conn2.close()
            first_exam_date = first_exam_row['first_date'] if first_exam_row else None
            seewhy = db.check_seewhy_guarantee(t['id'], first_exam_date) if first_exam_date else False
            status_item = QTreeWidgetItem(root, [f"Practice Exams: {'Y' if modules_complete else 'N'} | SeeWhy Guarantee: {'Y' if seewhy else 'N'}"])
            if seewhy:
                status_item.setForeground(0, QBrush(QColor("green")))
        except Exception:
            pass
        if t['recruiter_id']:
            r = db.get_recruiter(t['recruiter_id'])
            if r:
                QTreeWidgetItem(root, [f"Recruiter: {r['id']}: {r['name']} ({r['email'] or '—'})"])

        # classes
        conn = db.get_conn(); cur = conn.cursor()
        cur.execute("SELECT c.* FROM class c JOIN trainee_class tc ON c.id = tc.class_id WHERE tc.trainee_id = ? ORDER BY c.start_date", (t['id'],))
        classes = cur.fetchall()
        if classes:
            cp = QTreeWidgetItem(root, ["Classes"]) 
            for c in classes:
                QTreeWidgetItem(cp, [f"{c['id']}: {c['name']} ({c['start_date'] or '—'} → {c['end_date'] or '—'})"])

        # exams
        cur.execute("SELECT * FROM exam WHERE trainee_id = ? ORDER BY exam_date DESC", (t['id'],))
        exams = cur.fetchall()
        if exams:
            ep = QTreeWidgetItem(root, ["Exams"]) 
            for e in exams:
                mod = f"[{e['module']}] " if 'module' in e.keys() and e['module'] else ''
                practice = "(practice) " if 'is_practice' in e.keys() and e['is_practice'] else ''
                passed = 'Pass' if ('passed' in e.keys() and e['passed'] == 1) else ('Fail' if ('passed' in e.keys() and e['passed'] == 0) else '—')
                reimb = ' [Reimb requested]' if ('reimbursement_requested' in e.keys() and e['reimbursement_requested']) else ''
                QTreeWidgetItem(ep, [f"{e['id']}: {e['exam_date'] or '—'} | {mod}{practice}Score: {e['score'] or '—'} | {passed}{reimb}"])

        # licenses
        cur.execute("SELECT * FROM license WHERE trainee_id = ? ORDER BY application_submitted_date DESC", (t['id'],))
        licenses = cur.fetchall()
        if licenses:
            lp = QTreeWidgetItem(root, ["Licenses"]) 
            for l in licenses:
                QTreeWidgetItem(lp, [f"{l['id']}: Applied: {l['application_submitted_date'] or '—'} | Approved: {l['approval_date'] or '—'} | Status: {l['status'] or '—'}"])

        conn.close()
        self.tr_details.addTopLevelItem(root)
        root.setExpanded(True)
        try:
            self._show_status(f"Selected trainee: {t['first_name']} {t['last_name']}")
        except Exception:
            pass

    # Global shortcut handlers
    def _global_add(self) -> None:
        idx = self.tabs.currentIndex()
        if idx == 0:
            self._add_recruiter()
        elif idx == 1:
            self._add_trainee()
        elif idx == 2:
            self._add_class()
        elif idx == 3:
            self._add_exam()
        elif idx == 4:
            self._add_license()

    def _global_edit(self) -> None:
        idx = self.tabs.currentIndex()
        if idx == 0:
            self._edit_recruiter()
        elif idx == 1:
            self._edit_trainee()
        elif idx == 2:
            self._edit_class()
        elif idx == 3:
            self._edit_exam()
        elif idx == 4:
            self._edit_license()

    def _global_delete(self) -> None:
        idx = self.tabs.currentIndex()
        if idx == 0:
            self._delete_recruiter()
        elif idx == 1:
            self._delete_trainee()
        elif idx == 2:
            self._delete_selected_class()
        elif idx == 3:
            self._delete_selected_exam()
        elif idx == 4:
            self._delete_license()

    # ---------- Classes ----------
    def _build_classes_tab(self) -> None:
        w = QWidget(); l = QHBoxLayout(w)
        # Use helper for form section
        self.class_name = QLineEdit(); self.class_start = QLineEdit(); self.class_end = QLineEdit()
        form_fields = [
            ("Class name", self.class_name),
            ("Start date (YYYY-MM-DD)", self.class_start),
            ("End date (YYYY-MM-DD)", self.class_end),
        ]
        left = create_form_section(form_fields)
        left.addWidget(QPushButton("Add Class", clicked=self._add_class))
        left.addWidget(create_section_header("Link trainee to class"))
        self.tc_trainee = QComboBox(); self.tc_class = QComboBox()
        left.addWidget(self.tc_trainee); left.addWidget(self.tc_class)
        left.addWidget(QPushButton("Link", clicked=self._link_trainee_class))
        l.addLayout(left, 1)

        right = QVBoxLayout()
        self.class_list = QListWidget(); self.class_list.itemSelectionChanged.connect(self._on_class_select)
        right.addWidget(create_section_header("Classes")); right.addWidget(self.class_list)
        class_btns = create_button_row([
            ("Edit", self._edit_class),
            ("Delete", self._delete_selected_class),
        ])
        right.addLayout(class_btns)
        self.class_details = QTreeWidget(); self.class_details.setHeaderHidden(True)
        right.addWidget(create_section_header("Details")); right.addWidget(self.class_details)
        l.addLayout(right, 2)
        self.tabs.addTab(w, "Classes")
        self._refresh_classes(); self._refresh_tc_dropdowns()

    def _add_class(self) -> None:
        name = self.class_name.text().strip()
        if not name:
            QMessageBox.warning(self, "Validation", "Class name required"); return
        start = self.class_start.text().strip() or None; end = self.class_end.text().strip() or None
        try:
            db.add_class(name, start, end)
        except Exception as exc:
            QMessageBox.critical(self, "Error", f"Failed to add class: {exc}"); return
        self.class_name.clear(); self.class_start.clear(); self.class_end.clear()
        self._refresh_classes(); self._refresh_tc_dropdowns()
        try:
            self._show_status(f"Added class: {name}")
        except Exception:
            pass

    def _refresh_classes(self) -> None:
        self.class_list.clear(); rows = db.list_classes(); self._class_rows = rows
        for c in rows:
            self.class_list.addItem(f"{c['id']}: {c['name']} ({c['start_date'] or '—'} → {c['end_date'] or '—'})")

    def _refresh_tc_dropdowns(self) -> None:
        trainees = [f"{t['id']}: {t['last_name']}, {t['first_name']}" for t in db.list_trainees()]
        classes = [f"{c['id']}: {c['name']}" for c in db.list_classes()]
        self.tc_trainee.clear(); self.tc_trainee.addItems(trainees)
        self.tc_class.clear(); self.tc_class.addItems(classes)

    def _link_trainee_class(self) -> None:
        t = self.tc_trainee.currentText(); c = self.tc_class.currentText()
        if not t or not c:
            QMessageBox.warning(self, "Validation", "Select trainee and class"); return
        tid = int(t.split(":",1)[0]); cid = int(c.split(":",1)[0])
        db.link_trainee_to_class(tid, cid); QMessageBox.information(self, "Linked", "Trainee linked to class")

    def _on_class_select(self) -> None:
        sel = self.class_list.currentRow(); self.class_details.clear()
        if sel < 0 or sel >= len(self._class_rows): return
        c = self._class_rows[sel]
        root = QTreeWidgetItem([f"Class: {c['name']} (ID: {c['id']})"])
        if c['start_date'] or c['end_date']:
            QTreeWidgetItem(root, [f"Dates: {c['start_date'] or '—'} → {c['end_date'] or '—'}"])
        # trainees linked
        conn = db.get_conn(); cur = conn.cursor()
        cur.execute("SELECT t.* FROM trainee t JOIN trainee_class tc ON t.id = tc.trainee_id WHERE tc.class_id = ? ORDER BY t.last_name, t.first_name", (c['id'],))
        trainees = cur.fetchall()
        if trainees:
            tp = QTreeWidgetItem(root, ["Trainees"]) 
            for t in trainees:
                tn = QTreeWidgetItem(tp, [f"{t['id']}: {t['last_name']}, {t['first_name']} (IBA Date: {t['dob'] or '—'})"])
                cur.execute("SELECT * FROM exam WHERE trainee_id = ? ORDER BY exam_date DESC", (t['id'],))
                exams = cur.fetchall()
                if exams:
                    ep = QTreeWidgetItem(tn, ["Exams"]) 
                    for e in exams:
                        QTreeWidgetItem(ep, [f"{e['id']}: {e['exam_date'] or '—'} | Score: {e['score'] or '—'}"])
                cur.execute("SELECT * FROM license WHERE trainee_id = ? ORDER BY application_submitted_date DESC", (t['id'],))
                licenses = cur.fetchall()
                if licenses:
                    lp = QTreeWidgetItem(tn, ["Licenses"]) 
                    for l in licenses:
                        QTreeWidgetItem(lp, [f"{l['id']}: Applied: {l['application_submitted_date'] or '—'} | Status: {l['status'] or '—'}"])
        conn.close()
        self.class_details.addTopLevelItem(root); root.setExpanded(True)

    def _delete_selected_class(self) -> None:
        sel = self.class_list.currentRow()
        if sel < 0 or sel >= len(self._class_rows): return
        text = self.class_list.item(sel).text(); cid = int(text.split(":",1)[0])
        if QMessageBox.question(self, 'Delete Class', f'Delete class {cid}? Trainee links will be removed.') != QMessageBox.StandardButton.Yes:
            return
        try:
            db.delete_class(cid)
        except Exception as e:
            QMessageBox.critical(self, 'Delete Class', f'Error deleting class: {e}'); return
        self._refresh_classes(); self._refresh_tc_dropdowns()
        try:
            self._show_status(f"Deleted class: {cid}")
        except Exception:
            pass

    def _edit_class(self) -> None:
        sel = self.class_list.currentRow()
        if sel < 0 or sel >= len(self._class_rows):
            QMessageBox.warning(self, "Edit Class", "Select a class to edit")
            return
        c = self._class_rows[sel]
        dlg = QDialog(self)
        dlg.setWindowTitle("Edit Class")
        form = QFormLayout(dlg)
        name = QLineEdit(c['name'] or "")
        start = QLineEdit(c['start_date'] or "")
        end = QLineEdit(c['end_date'] or "")
        form.addRow("Name", name); form.addRow("Start date", start); form.addRow("End date", end)
        btns = QHBoxLayout(); save = QPushButton("Save"); delete = QPushButton("Delete"); cancel = QPushButton("Cancel")
        btns.addWidget(save); btns.addWidget(delete); btns.addWidget(cancel); form.addRow(btns)
        save.clicked.connect(lambda: (db.update_class(c['id'], name.text().strip(), start.text().strip() or None, end.text().strip() or None), dlg.accept()))
        delete.clicked.connect(lambda: (db.delete_class(c['id']), dlg.accept()))
        cancel.clicked.connect(dlg.reject)
        if dlg.exec() == QDialog.Accepted:
            self._refresh_classes(); self._refresh_tc_dropdowns()

    # ---------- Exams ----------
    def _build_exams_tab(self) -> None:
        w = QWidget()
        l = QHBoxLayout(w)
        # Use helper for form section
        self.exam_trainee = QComboBox()
        self.exam_class = QComboBox()
        self.exam_module = QComboBox()
        self.exam_module.addItems(["", "Life", "A&S", "Seg Funds", "Ethics"])
        self.is_practice = QCheckBox("Practice exam")
        self.exam_date = QLineEdit()
        self.exam_result = QComboBox()
        self.exam_result.addItems(["Unknown", "Pass", "Fail"])
        self.exam_notes = QTextEdit()
        form_fields = [
            ("Trainee", self.exam_trainee),
            ("Class (optional)", self.exam_class),
            ("Module", self.exam_module),
            ("", self.is_practice),
            ("Exam date (YYYY-MM-DD)", self.exam_date),
            ("Result", self.exam_result),
            ("Notes", self.exam_notes),
        ]
        left = create_form_section(form_fields)
        left.addWidget(QPushButton("Add Exam", clicked=self._add_exam))
        l.addLayout(left, 1)

        right = QVBoxLayout()
        self.exam_list = QListWidget()
        right.addWidget(create_section_header("Exams"))
        right.addWidget(self.exam_list)
        exam_btns = create_button_row([
            ("Edit", self._edit_exam),
            ("Delete", self._delete_selected_exam),
        ])
        right.addLayout(exam_btns)
        right.addWidget(create_section_header("Provincial Exam Data for Selected Trainee"))
        self.prov_exam_info = QTreeWidget()
        self.prov_exam_info.setHeaderLabels(["Exam Date", "Module", "Score", "Result", "Notes"])
        self.prov_exam_info.setAlternatingRowColors(True)
        self.prov_exam_info.setRootIsDecorated(False)
        right.addWidget(self.prov_exam_info)
        l.addLayout(right, 2)
        self.tabs.addTab(w, "Exams")
        self._refresh_exam_dropdowns()
        self._refresh_exams()
        # Add practice exam status panel
        self._build_practice_exam_status_panel()

        # Connect trainee selection to info panel update (moved from _build)
        self.exam_trainee.currentIndexChanged.connect(self._update_prov_exam_info)
        self._update_prov_exam_info()

    def _build_practice_exam_status_panel(self):
        """Builds the practice exam status panel under the Exams tab."""
        panel = QWidget()
        layout = QVBoxLayout(panel)

        # Add a group box to visually separate the modules
        group_box = QGroupBox("Practice Exam Status")
        group_layout = QVBoxLayout(group_box)
        # horizontal rows: checkbox + icon label + date label

        self.practice_exam_checkboxes = {}
        self.practice_exam_date_labels = {}
        modules = ["Life", "A&S", "Seg Funds", "Ethics"]

        self.practice_exam_icon_labels = {}
        for module in modules:
            row = QWidget()
            row_l = QHBoxLayout(row)
            row_l.setContentsMargins(0, 0, 0, 0)
            checkbox = QCheckBox(module)
            icon_label = QLabel()
            icon_label.setFixedWidth(22)
            icon_label.setAlignment(Qt.AlignCenter)
            date_label = QLabel()
            date_label.setMinimumWidth(100)
            date_label.setAlignment(Qt.AlignLeft)

            # initialize state (will be updated when trainee is selected)
            checkbox.blockSignals(True)
            checkbox.setChecked(False)
            checkbox.blockSignals(False)

            # set initial icon and color using SVG icons
            icon = _load_icon('box')
            icon_label.setPixmap(icon.pixmap(18, 18))
            checkbox.setStyleSheet("margin-bottom: 6px; color: red;")

            # connect handler (user interaction will trigger stateChanged)
            checkbox.stateChanged.connect(lambda state, m=module: self._toggle_practice_exam_status(m, state))

            row_l.addWidget(checkbox)
            row_l.addStretch(1)
            row_l.addWidget(icon_label)
            row_l.addWidget(date_label)
            group_layout.addWidget(row)
            self.practice_exam_checkboxes[module] = checkbox
            self.practice_exam_icon_labels[module] = icon_label
            self.practice_exam_date_labels[module] = date_label

        group_box.setLayout(group_layout)
        layout.addWidget(group_box)
        # add Reset All button
        reset_btn = QPushButton("Reset All")
        reset_btn.clicked.connect(self._reset_practice_exam_statuses)
        layout.addWidget(group_box)
        layout.addWidget(reset_btn)
        self.tabs.widget(3).layout().addWidget(panel)

    def _toggle_practice_exam_status(self, module, state):
        """Handles toggling the practice exam status for a module for the current trainee. Updates icon, color, persists state and shows status."""
        # Get current trainee
        trainee_text = self.exam_trainee.currentText()
        if not trainee_text or ':' not in trainee_text:
            return
        
        try:
            trainee_id = int(trainee_text.split(":")[0])
        except (ValueError, IndexError):
            return
        
        # state is an int (0=Unchecked, 2=Checked)
        status = state == Qt.CheckState.Checked.value if hasattr(Qt.CheckState, 'Checked') else state == 2
        checkbox = self.practice_exam_checkboxes.get(module)
        icon_label = self.practice_exam_icon_labels.get(module)
        if checkbox is None or icon_label is None:
            return

        # update color
        checkbox.setStyleSheet("margin-bottom: 6px; color: green;" if status else "margin-bottom: 6px; color: red;")

        # update icon label
        icon = _load_icon('check' if status else 'box')
        icon_label.setPixmap(icon.pixmap(18, 18))

        # Persist the state in the database
        try:
            db.update_practice_exam_status(trainee_id, module, status)
            # user-visible transient status
            self._show_status(f"Module '{module}' set to: {'Completed' if status else 'Incomplete'}")
            # Refresh the trainee tab to show updated practice exam completion status
            self._refresh_recruiters()
        except Exception as e:
            # show non-blocking error in status bar
            self._show_status(f"Failed to update practice exam status for {module}: {e}")
            # Revert UI change on failure
            checkbox.blockSignals(True)
            checkbox.setChecked(not status)
            checkbox.blockSignals(False)

    def _reset_practice_exam_statuses(self) -> None:
        """Reset all practice exam statuses to incomplete for the current trainee and update UI."""
        # Get current trainee
        trainee_text = self.exam_trainee.currentText()
        if not trainee_text or ':' not in trainee_text:
            QMessageBox.warning(self, "Validation", "Select a trainee first")
            return
        
        try:
            trainee_id = int(trainee_text.split(":")[0])
        except (ValueError, IndexError):
            QMessageBox.warning(self, "Error", "Invalid trainee selection")
            return
        
        try:
            db.reset_practice_exam_statuses_for_trainee(trainee_id)
        except Exception as e:
            try:
                self._show_status(f"Failed to reset practice exam statuses: {e}")
            except Exception:
                pass
            return
        # update UI
        for module, cb in self.practice_exam_checkboxes.items():
            cb.blockSignals(True)
            cb.setChecked(False)
            cb.setStyleSheet("margin-bottom: 6px; color: red;")
            cb.blockSignals(False)
            icon_label = self.practice_exam_icon_labels.get(module)
            if icon_label is not None:
                icon = _load_icon('box')
                icon_label.setPixmap(icon.pixmap(18, 18))
        try:
            self._show_status("All practice exam statuses reset to Incomplete")
        except Exception:
            pass

    def _update_practice_exam_panel(self):
        """Update the practice exam status panel for the currently selected trainee."""
        trainee_text = self.exam_trainee.currentText()
        if not trainee_text or ':' not in trainee_text:
            # Reset all to unchecked if no trainee selected
            for module, cb in self.practice_exam_checkboxes.items():
                cb.blockSignals(True)
                cb.setChecked(False)
                cb.setStyleSheet("margin-bottom: 6px; color: red;")
                cb.blockSignals(False)
                icon_label = self.practice_exam_icon_labels.get(module)
                if icon_label is not None:
                    icon = _load_icon('box')
                    icon_label.setPixmap(icon.pixmap(18, 18))
                date_label = self.practice_exam_date_labels.get(module)
                if date_label is not None:
                    date_label.setText("")
            return
        
        try:
            trainee_id = int(trainee_text.split(":")[0])
            statuses = db.get_practice_exam_status_for_trainee(trainee_id)
            completion_dates = db.get_all_practice_module_completion_dates(trainee_id)
            
            for module, cb in self.practice_exam_checkboxes.items():
                completed = statuses.get(module, False)
                cb.blockSignals(True)
                cb.setChecked(completed)
                cb.setStyleSheet(f"margin-bottom: 6px; color: {'green' if completed else 'red'};")
                cb.blockSignals(False)
                icon_label = self.practice_exam_icon_labels.get(module)
                if icon_label is not None:
                    icon = _load_icon('check' if completed else 'box')
                    icon_label.setPixmap(icon.pixmap(18, 18))
                date_label = self.practice_exam_date_labels.get(module)
                if date_label is not None:
                    completion_date = completion_dates.get(module, "")
                    if completion_date:
                        # Format timestamp for display (extract date portion only)
                        date_str = completion_date.split('T')[0] if 'T' in completion_date else completion_date
                        date_label.setText(f"({date_str})")
                    else:
                        date_label.setText("")
        except Exception as e:
            pass

    def _add_exam(self) -> None:
        t = self.exam_trainee.currentText();
        if not t: QMessageBox.warning(self, "Validation", "Select a trainee"); return
        c = self.exam_class.currentText(); cid = int(c.split(":",1)[0]) if c else None
        exam_date = self.exam_date.text().strip() or None
        module = self.exam_module.currentText() or None
        is_practice = bool(self.is_practice.isChecked())
        res = self.exam_result.currentText(); passed = True if res == 'Pass' else (False if res == 'Fail' else None)
        reimbursement = False
        tid = int(t.split(":", 1)[0])
        if passed is False and not is_practice and db.practice_exams_complete(tid):
            if QMessageBox.question(self, "Reimbursement", "Trainee completed practice exams. Apply for reimbursement for rewrite?") == QMessageBox.StandardButton.Yes:
                reimbursement = True
        notes = self.exam_notes.toPlainText().strip() or None
        db.add_exam_v2(tid, cid, exam_date, module, is_practice, passed, notes, reimbursement_requested=reimbursement)
        self._refresh_exams()
        try:
            self._show_status(f"Added exam for trainee {tid}")
        except Exception:
            pass

    def _refresh_exam_dropdowns(self) -> None:
        trainees = [f"{t['id']}: {t['last_name']}, {t['first_name']}" for t in db.list_trainees()]
        classes = [f"{c['id']}: {c['name']}" for c in db.list_classes()]
        self.exam_trainee.clear(); self.exam_trainee.addItems(trainees)
        self.exam_class.clear(); self.exam_class.addItems([""] + classes)

    def _refresh_exams(self) -> None:
        self.exam_list.clear()
        for e in db.list_exams():
            mod = f"[{e['module']}] " if 'module' in e.keys() and e['module'] else ''
            practice = "(practice) " if 'is_practice' in e.keys() and e['is_practice'] else ''
            passed = 'Pass' if ('passed' in e.keys() and e['passed'] == 1) else ('Fail' if ('passed' in e.keys() and e['passed'] == 0) else '—')
            reimb = ' 💰' if ('reimbursement_requested' in e.keys() and e['reimbursement_requested']) else ''
            self.exam_list.addItem(f"{e['id']}: {e['exam_date'] or '—'} | {mod}{practice}{e['first_name']} {e['last_name']} | {passed}{reimb}")

    def _delete_selected_exam(self) -> None:
        sel = self.exam_list.currentRow();
        if sel < 0: return
        text = self.exam_list.item(sel).text(); eid = int(text.split(":",1)[0])
        if QMessageBox.question(self, 'Delete Exam', f'Delete exam {eid}?') != QMessageBox.StandardButton.Yes: return
        db.delete_exam(eid); self._refresh_exams()
        try:
            self._show_status(f"Deleted exam: {eid}")
        except Exception:
            pass

    def _edit_exam(self) -> None:
        sel = self.exam_list.currentRow()
        if sel < 0:
            QMessageBox.warning(self, "Edit Exam", "Select an exam to edit")
            return
        text = self.exam_list.item(sel).text(); eid = int(text.split(":",1)[0])
        # fetch exam
        conn = db.get_conn(); cur = conn.cursor()
        cur.execute("SELECT * FROM exam WHERE id = ?", (eid,))
        e = cur.fetchone()
        conn.close()
        if not e:
            QMessageBox.warning(self, "Edit Exam", "Exam not found")
            return
        dlg = QDialog(self); dlg.setWindowTitle("Edit Exam")
        form = QFormLayout(dlg)
        trainees = [f"{t['id']}: {t['last_name']}, {t['first_name']}" for t in db.list_trainees()]
        classes = [f"{c['id']}: {c['name']}" for c in db.list_classes()]
        trainee_cb = QComboBox(); trainee_cb.addItems(trainees)
        if e['trainee_id']:
            for i in range(trainee_cb.count()):
                if trainee_cb.itemText(i).startswith(str(e['trainee_id']) + ':'):
                    trainee_cb.setCurrentIndex(i); break
        class_cb = QComboBox(); class_cb.addItems([""] + classes)
        if e['class_id']:
            for i in range(class_cb.count()):
                if class_cb.itemText(i).startswith(str(e['class_id']) + ':'):
                    class_cb.setCurrentIndex(i); break
        module_cb = QComboBox(); module_cb.addItems(["", "Life", "A&S", "Seg Funds", "Ethics"]) ;
        module_cb.setCurrentText(e['module'] or "")
        practice_cb = QCheckBox("Practice exam"); practice_cb.setChecked(bool(e['is_practice']))
        date_e = QLineEdit(e['exam_date'] or "")
        result_cb = QComboBox(); result_cb.addItems(["Unknown","Pass","Fail"]); 
        if e['passed'] == 1: result_cb.setCurrentText('Pass')
        elif e['passed'] == 0: result_cb.setCurrentText('Fail')
        notes_e = QTextEdit(e['notes'] or "")
        reimb_cb = QCheckBox("Reimbursement requested"); reimb_cb.setChecked(bool(e['reimbursement_requested']))
        form.addRow("Trainee", trainee_cb); form.addRow("Class", class_cb); form.addRow("Module", module_cb); form.addRow(practice_cb)
        form.addRow("Exam date", date_e); form.addRow("Result", result_cb); form.addRow("Notes", notes_e); form.addRow(reimb_cb)
        btns = QHBoxLayout(); save = QPushButton("Save"); delete = QPushButton("Delete"); cancel = QPushButton("Cancel"); btns.addWidget(save); btns.addWidget(delete); btns.addWidget(cancel); form.addRow(btns)

        def do_save():
            tid = int(trainee_cb.currentText().split(":",1)[0])
            cid = None
            if class_cb.currentText():
                cid = int(class_cb.currentText().split(":",1)[0])
            exam_date = date_e.text().strip() or None
            module = module_cb.currentText() or None
            is_practice = bool(practice_cb.isChecked())
            res = result_cb.currentText(); passed = True if res == 'Pass' else (False if res == 'Fail' else None)
            notes = notes_e.toPlainText().strip() or None
            reimb = bool(reimb_cb.isChecked())
            try:
                db.update_exam(eid, tid, cid, exam_date, module, is_practice, passed, notes, reimbursement_requested=reimb)
            except Exception as exc:
                QMessageBox.critical(self, "Error", f"Failed to update exam: {exc}"); return
            dlg.accept()

        def do_delete():
            if QMessageBox.question(self, 'Delete Exam', f'Delete exam {eid}?') != QMessageBox.StandardButton.Yes:
                return
            db.delete_exam(eid); dlg.accept()

        save.clicked.connect(do_save); delete.clicked.connect(do_delete); cancel.clicked.connect(dlg.reject)
        if dlg.exec() == QDialog.Accepted:
            self._refresh_exams(); self._refresh_exam_dropdowns()

    # ---------- Licenses ----------
    def _build_licenses_tab(self) -> None:
        w = QWidget(); l = QHBoxLayout(w)
        # Use helper for form section
        self.lic_trainee = QComboBox()
        self.lic_trainee.currentIndexChanged.connect(self._update_license_trainee_info)
        self.lic_app = QLineEdit()
        self.lic_approval = QLineEdit()
        self.lic_number = QLineEdit()
        self.lic_status = QLineEdit()
        form_fields = [
            ("Trainee", self.lic_trainee),
            ("Application date", self.lic_app),
            ("Approval date", self.lic_approval),
            ("License number", self.lic_number),
            ("Status", self.lic_status),
        ]
        left = create_form_section(form_fields)
        left.addWidget(QPushButton("Add License", clicked=self._add_license))
        l.addLayout(left, 1)
        right = QVBoxLayout()
        self.lic_list = QListWidget()
        right.addWidget(create_section_header("Licenses"))
        right.addWidget(self.lic_list)
        lic_btns = create_button_row([
            ("Edit", self._edit_license),
            ("Delete", self._delete_license),
        ])
        right.addLayout(lic_btns)
        l.addLayout(right, 2)
        self.tabs.addTab(w, "Licenses")
        self._refresh_license_dropdowns(); self._refresh_licenses()

    def _add_license(self) -> None:
        t = self.lic_trainee.currentText();
        if not t: QMessageBox.warning(self, "Validation", "Select trainee"); return
        tid = int(t.split(":",1)[0])
        app_date = self.lic_app.text().strip() or None
        approval_date = self.lic_approval.text().strip() or None
        try:
            db.add_license(tid, app_date, approval_date, self.lic_number.text().strip() or None, self.lic_status.text().strip() or None, None)
        except Exception as exc:
            QMessageBox.critical(self, "Error", f"Failed to add license: {exc}"); return
        self._refresh_licenses()
        try:
            self._show_status(f"Added license for trainee {tid}")
        except Exception:
            pass

    def _refresh_license_dropdowns(self) -> None:
        trainees = [f"{t['id']}: {t['last_name']}, {t['first_name']}" for t in db.list_trainees()]
        self.lic_trainee.clear(); self.lic_trainee.addItems(trainees)

    def _refresh_licenses(self) -> None:
        self.lic_list.clear()
        for l in db.list_licenses():
            status = l['status'] or '—'
            self.lic_list.addItem(f"{l['id']}: {l['application_submitted_date'] or '—'} | {l['first_name']} {l['last_name']} | {status}")

    def _edit_license(self) -> None:
        sel = self.lic_list.currentRow()
        if sel < 0:
            QMessageBox.warning(self, "Edit License", "Select a license to edit")
            return
        text = self.lic_list.item(sel).text(); lid = int(text.split(":",1)[0])
        conn = db.get_conn(); cur = conn.cursor(); cur.execute("SELECT * FROM license WHERE id = ?", (lid,)); lic = cur.fetchone(); conn.close()
        if not lic:
            QMessageBox.warning(self, "Edit License", "License not found"); return
        dlg = QDialog(self); dlg.setWindowTitle("Edit License"); form = QFormLayout(dlg)
        trainees = [f"{t['id']}: {t['last_name']}, {t['first_name']}" for t in db.list_trainees()]
        trainee_cb = QComboBox(); trainee_cb.addItems(trainees)
        if lic['trainee_id']:
            for i in range(trainee_cb.count()):
                if trainee_cb.itemText(i).startswith(str(lic['trainee_id']) + ':'):
                    trainee_cb.setCurrentIndex(i); break
        app_e = QLineEdit(lic['application_submitted_date'] or ""); approval_e = QLineEdit(lic['approval_date'] or "")
        num_e = QLineEdit(lic['license_number'] or ""); status_e = QLineEdit(lic['status'] or "")
        form.addRow("Trainee", trainee_cb); form.addRow("Application date", app_e); form.addRow("Approval date", approval_e); form.addRow("License number", num_e); form.addRow("Status", status_e)
        btns = QHBoxLayout(); save = QPushButton("Save"); delete = QPushButton("Delete"); cancel = QPushButton("Cancel"); btns.addWidget(save); btns.addWidget(delete); btns.addWidget(cancel); form.addRow(btns)

        def do_save():
            tid = int(trainee_cb.currentText().split(":",1)[0])
            try:
                db.update_license(lid, tid, app_e.text().strip() or None, approval_e.text().strip() or None, num_e.text().strip() or None, status_e.text().strip() or None, None)
            except Exception as exc:
                QMessageBox.critical(self, "Error", f"Failed to update license: {exc}"); return
            dlg.accept()

        def do_delete():
            if QMessageBox.question(self, 'Delete License', f'Delete license {lid}?') != QMessageBox.StandardButton.Yes:
                return
                conn = db.get_conn(); cur = conn.cursor(); cur.execute("DELETE FROM license WHERE id = ?", (lid,)); conn.commit(); conn.close(); dlg.accept()

        save.clicked.connect(do_save); delete.clicked.connect(do_delete); cancel.clicked.connect(dlg.reject)
        if dlg.exec() == QDialog.Accepted:
            self._refresh_licenses(); self._refresh_license_dropdowns()

    def _delete_license(self) -> None:
        sel = self.lic_list.currentRow()
        if sel < 0: return
        text = self.lic_list.item(sel).text(); lid = int(text.split(":",1)[0])
        if QMessageBox.question(self, 'Delete License', f'Delete license {lid}?') != QMessageBox.StandardButton.Yes: return
        conn = db.get_conn(); cur = conn.cursor(); cur.execute("DELETE FROM license WHERE id = ?", (lid,)); conn.commit(); conn.close()
        self._refresh_licenses(); self._refresh_license_dropdowns()
        try:
            self._show_status(f"Deleted license: {lid}")
        except Exception:
            pass

    def _update_license_trainee_info(self):
        """Update license fields based on the selected trainee."""
        t = self.lic_trainee.currentText()
        if not t or ':' not in t:
            self.lic_app.clear()
            self.lic_approval.clear()
            self.lic_number.clear()
            self.lic_status.clear()
            return
        tid = int(t.split(":", 1)[0])
        license_info = db.get_license_info_for_trainee(tid)
        if not license_info:
            self.lic_app.clear()
            self.lic_approval.clear()
            self.lic_number.clear()
            self.lic_status.clear()
            return

        self.lic_app.setText(license_info.get("application_submitted_date", ""))
        self.lic_approval.setText(license_info.get("approval_date", ""))
        self.lic_number.setText(license_info.get("license_number", ""))
        self.lic_status.setText(license_info.get("status", ""))


def run_pyside_app() -> None:
    import os
    from PySide6.QtCore import QTimer

    app = QApplication(sys.argv)
    # apply stylesheet if present
    try:
        qss_path = pathlib.Path(__file__).with_name("style.qss")
        if qss_path.exists():
            app.setStyleSheet(qss_path.read_text(encoding="utf-8"))
    except Exception:
        pass

    w = MainWindow()
    w.resize(1100, 700)
    w.show()

    # For automated testing, support an env var `LS_AUTO_EXIT` (seconds)
    # If set, the app will quit after that many seconds. This is opt-in and
    # intended only to allow non-interactive smoke tests.
    try:
        v = os.environ.get("LS_AUTO_EXIT")
        if v:
            secs = float(v)
            QTimer.singleShot(int(secs * 1000), app.quit)
    except Exception:
        pass

    sys.exit(app.exec())


if __name__ == "__main__":
    run_pyside_app()
