"""PXE/GE-Enforce pc-type -> ComputerType mapping (computers-plugin domain).

The imaging pipeline writes C:\\Enrollment\\pc-type.txt (the gea-shopfloor-*
taxonomy). The collector maps that to a flask ComputerType. Defaults live here;
per-site overrides live in core Settings (pctypemap_<pxetype>, category
'pctypemapping') and are editable in the Settings UI. This indirection means
the imaging taxonomy can change without code edits.
"""

from shopdb.api import Setting

# pxe pc-type -> flask ComputerType name. Defaults; overridable via settings.
DEFAULT_PCTYPE_MAP = {
    'gea-shopfloor-collections': 'Shopfloor PC',
    'gea-shopfloor-nocollections': 'Shopfloor PC',
    'gea-shopfloor-common': 'Shopfloor PC',
    'gea-shopfloor-keyence': 'Shopfloor PC',
    'gea-shopfloor-cmm': 'CMM PC',
    'gea-shopfloor-genspect': 'Shopfloor PC',
    'gea-shopfloor-heattreat': 'Shopfloor PC',
    'gea-shopfloor-waxtrace': 'Shopfloor PC',
    'gea-shopfloor-display': 'Kiosk',
    'gea-shopfloor-partmarker': 'Shopfloor PC',
}

# DEPRECATED settings surface: the "Collector PC Types" settings page is retired
# (GE-Enforce scope computertypeid supersedes it, ADR-012). Collector still reads
# this map, so keep the code; only the editor UI card is gone.
_SETTING_PREFIX = 'pctypemap_'
_SETTING_CATEGORY = 'pctypemapping'

# SUBORDINATE DEVICES: a PC that drives a device which is its own asset.
#
# Two cases arrived separately and were written twice. A metrology PC drives a
# CMM or a Keyence unit, which is a measuring_tool asset. A part-marker PC
# drives a Telesis marker, which is a machine asset filed under the operation it
# serves. Both mint a device, link the PC to it with `controls`, and archive the
# link if the PC is re-imaged - the same mechanism with different nouns.
#
# Declared here rather than coded per case so the NEXT one is configuration. It
# will happen: several markers already share operation numbers 0613, 0615 and
# WJPRT, and any site with two marking lasers or two wax-trace units on one
# number needs the same treatment. A site adds or retargets an entry through the
# subordinatedevice_<pctype> settings (category 'pctypemapping'), per ADR-015.
#
# Fields:
#   assettype   the core AssetType. 'measuring_tool' or 'machine' today; the
#               extension row and type vocabulary follow from it.
#   typename    the device type within that vocabulary, created on demand.
#   description used only when the type is created.
#   suffix      appended to the PC's asset number to name the device.
#   partof      True files the device `partof` the reported operation AND stops
#               the PC claiming that operation directly. Set it when several
#               devices can share one machine number: `controls` propagates
#               along `partof`, so control still follows, without two devices
#               contesting a link only one can hold.
#   label       relationship origin marker. UNCHANGED per case on purpose -
#               these values are in the production database and only rows
#               carrying them are archived by a collector push.
SUBORDINATE_DEVICE_MAP = {
    'gea-shopfloor-cmm': {
        'assettype': 'measuring_tool', 'typename': 'CMM',
        'description': 'Coordinate measuring machine',
        'suffix': 'CMM', 'partof': False, 'label': 'collector:measuringtool',
    },
    'gea-shopfloor-keyence': {
        'assettype': 'measuring_tool', 'typename': 'Vision System',
        'description': 'Optical / vision measurement system (Keyence)',
        'suffix': 'KEYENCE', 'partof': False, 'label': 'collector:measuringtool',
    },
    'gea-shopfloor-genspect': {
        'assettype': 'measuring_tool', 'typename': 'Genspect',
        'description': 'Genspect visual / borescope inspection system',
        'suffix': 'GENSPECT', 'partof': False, 'label': 'collector:measuringtool',
    },
    'gea-shopfloor-waxtrace': {
        'assettype': 'measuring_tool', 'typename': 'Form Tracer',
        'description': 'Surface / form tracing system (wax and trace)',
        'suffix': 'WAXTRACE', 'partof': False, 'label': 'collector:measuringtool',
    },
    'gea-shopfloor-partmarker': {
        'assettype': 'machine', 'typename': 'Part Marker',
        'description': 'Telesis part marker',
        'suffix': 'PARTMARKER', 'partof': True, 'label': 'collector:partmarker',
    },
}

_DEVICE_SETTING_PREFIX = 'subordinatedevice_'


def subordinate_device_for(pctype):
    """Device spec for this imaging pc-type, or None.

    Site overrides live in settings named subordinatedevice_<pctype> holding a
    JSON object with the same fields; a site can retarget an existing entry or
    add a pc-type of its own without a code change. A malformed override is
    ignored in favour of the default rather than failing the whole collector
    push - a bad setting must not stop a bay reporting its inventory.
    """
    key = (pctype or '').strip()
    if not key:
        return None
    spec = SUBORDINATE_DEVICE_MAP.get(key)
    setting = Setting.query.filter_by(
        key='{}{}'.format(_DEVICE_SETTING_PREFIX, key)).first()
    if setting and (setting.value or '').strip():
        import json
        try:
            override = json.loads(setting.value)
            if isinstance(override, dict):
                spec = dict(spec or {}, **override)
        except (ValueError, TypeError):
            pass
    if not spec or not spec.get('assettype') or not spec.get('typename'):
        return None
    return spec


def metrology_tool_for(pctype):
    """Back-compat shim: (typename, description) for a measuring-tool pc-type.

    Kept because the parity harness and the older tests read it. New code asks
    subordinate_device_for, which covers machine-typed devices too.
    """
    spec = SUBORDINATE_DEVICE_MAP.get((pctype or '').strip())
    if not spec or spec['assettype'] != 'measuring_tool':
        return None
    return (spec['typename'], spec['description'])


def pctype_mapping():
    """Return the live pctype -> ComputerType-name map (settings over defaults)."""
    mapping = dict(DEFAULT_PCTYPE_MAP)
    for setting in Setting.query.filter_by(category=_SETTING_CATEGORY).all():
        if setting.key.startswith(_SETTING_PREFIX):
            mapping[setting.key[len(_SETTING_PREFIX):]] = setting.get_typed_value()
    return mapping


def seed_pctype_settings():
    """Seed the pctypemap_<pxetype> settings from defaults (idempotent)."""
    for pxetype, computertype in DEFAULT_PCTYPE_MAP.items():
        key = f'{_SETTING_PREFIX}{pxetype}'
        if not Setting.query.filter_by(key=key).first():
            Setting.set(key, computertype, valuetype='string',
                        category=_SETTING_CATEGORY,
                        description=f'Collector maps pc-type {pxetype} to this ComputerType')
