"""Badge resolution for the printedparts plugin.

Same input contract as the USB plugin (deliberately copied, not imported -
cross-plugin imports break the shopdb.api-only contract):

- all digits            -> an SSO typed or scanned directly
- 0<digits>BZ           -> a physical badge wrapping a PayNo (keyboard-wedge
                           scanners emit this shape)
- anything else         -> unresolvable

Name lookup honors the site's employee directory mode (the same setting the
employees/usb plugins use):

- selfhosted: the employees plugin's directoryemployees table, by SSO. The
  table has no PayNo column, so a PayNo badge only resolves when the wrapped
  digits are themselves the SSO.
- external: the HR directory via employee_connection() - SSO badges by SSO,
  PayNo badges by PayNo (which also recovers the real SSO to record).

Policy (Setting printedparts_unknown_badge): 'deny' (default) rejects a badge
with no directory match; 'allow' records the SSO with an empty name.
"""

import logging
import re

from shopdb.api import Setting, employee_connection

logger = logging.getLogger(__name__)

_PAYNO_BADGE = re.compile(r'^0(\d+)BZ$', re.IGNORECASE)


class BadgeError(ValueError):
    """Raised when a badge cannot be accepted under the site policy."""


def _parse_badge(badge):
    """Return ('sso'|'payno', digits) or raise BadgeError on unknown shape."""
    badge = (badge or '').strip()
    if not badge:
        raise BadgeError('Scan or enter a badge')
    if badge.isdigit():
        return 'sso', badge
    match = _PAYNO_BADGE.match(badge)
    if match:
        return 'payno', match.group(1)
    raise BadgeError('Unrecognized badge format')


def _selfhosted_lookup(digits):
    """(sso, name) from the employees plugin directory, or None."""
    try:
        from plugins.employees.models import DirectoryEmployee
        from shopdb.api import db
        employee = db.session.get(DirectoryEmployee, int(digits))
        if employee:
            return digits, f'{employee.firstname} {employee.lastname}'.strip()
    except Exception:
        logger.exception('Self-hosted directory lookup failed for %s', digits)
    return None


def _external_lookup(kind, digits):
    """(sso, name) from the HR directory, or None. PayNo badges resolve to
    the employee's real SSO."""
    try:
        conn = employee_connection()
    except Exception:
        logger.exception('HR directory connection failed')
        return None
    try:
        with conn.cursor() as cursor:
            column = 'SSO' if kind == 'sso' else 'PayNo'
            cursor.execute(
                f'SELECT SSO, First_Name, Last_Name FROM employees '
                f'WHERE {column} = %s', (digits,))
            row = cursor.fetchone()
        if row:
            sso = str(row[0] if not isinstance(row, dict) else row['SSO'])
            first = row[1] if not isinstance(row, dict) else row['First_Name']
            last = row[2] if not isinstance(row, dict) else row['Last_Name']
            return sso, f'{(first or "").strip()} {(last or "").strip()}'.strip()
    except Exception:
        logger.exception('HR directory lookup failed for %s %s', kind, digits)
    finally:
        try:
            conn.close()
        except Exception:
            pass
    return None


def resolve_badge(badge):
    """Return (sso, name) for a scanned badge, enforcing the site policy.

    Raises BadgeError with a kiosk-displayable message when the badge shape is
    unrecognized or the policy denies an unmatched badge.
    """
    kind, digits = _parse_badge(badge)

    mode = (Setting.get('employee_directory_mode') or 'selfhosted').lower()
    if mode == 'external':
        resolved = _external_lookup(kind, digits)
    else:
        resolved = _selfhosted_lookup(digits)

    if resolved is None:
        policy = (Setting.get('printedparts_unknown_badge') or 'deny').lower()
        if policy != 'allow':
            raise BadgeError('Badge not recognized - see the parts team')
        return digits, ''
    return resolved
