"""Export the reference CATALOG from a live instance to a portable JSON file.

A new site starts with almost nothing: `flask seed reference-data` writes a
dozen generic model types and no vendors or models at all. So the first thing
anyone does at a new facility is retype a vendor and model list that another
facility already spent a year building. This lets that catalog travel.

CATALOG ONLY. Vendors, models and the type vocabularies are descriptions of
equipment that exists in the world, and are the same at every site. Assets,
locations, employees, business units and anything with a serial number are that
site's own records and are deliberately NOT exported - nobody wants West
Jefferson's machines appearing at another plant.

Vendor contact details are also skipped: a rep's name and phone number belong to
whoever has the relationship, not to the catalog.

    python scripts/export_catalog.py                     # to shopdb/data/catalog.json
    python scripts/export_catalog.py --out mine.json
    python scripts/export_catalog.py --pretty            # readable diffs in git

Load it on another instance with:  flask seed catalog
"""

import argparse
import json
import os
import sys
from collections import OrderedDict

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

DEFAULT_OUT = os.path.join('shopdb', 'data', 'catalog.json')


def export(connection):
    from sqlalchemy import inspect, text

    inspector = inspect(connection)
    tables = set(inspector.get_table_names())
    data = OrderedDict()

    def rows(sql):
        return [dict(r._mapping) for r in connection.execute(text(sql))]

    def has(table):
        return table in tables

    # --- type vocabularies -------------------------------------------------
    # Each is a small controlled list, and a site with none of them cannot even
    # classify what it owns.
    if has('modeltypes'):
        data['modeltypes'] = rows("""
            SELECT modeltype, category, description, icon
            FROM modeltypes ORDER BY modeltype""")
    for table, column in (('machinetypes', 'machinetype'),
                          ('computertypes', 'computertype'),
                          ('printertypes', 'printertype'),
                          ('networkdevicetypes', 'networkdevicetype'),
                          ('locationtypes', 'locationtype')):
        if has(table):
            extra = ', color' if 'color' in {c['name'] for c in inspector.get_columns(table)} else ''
            data[table] = rows('SELECT %s, description%s FROM %s ORDER BY %s'
                               % (column, extra, table, column))

    if has('operatingsystems'):
        columns = {c['name'] for c in inspector.get_columns('operatingsystems')}
        wanted = [c for c in ('osname', 'osversion', 'osfamily', 'description') if c in columns]
        data['operatingsystems'] = rows('SELECT %s FROM operatingsystems ORDER BY osname'
                                        % ', '.join(wanted))

    # --- vendors -----------------------------------------------------------
    # Name and website only. Contact person, phone and address are a site's own
    # relationship with that vendor, not a property of the vendor.
    if has('vendors'):
        columns = {c['name'] for c in inspector.get_columns('vendors')}
        wanted = [c for c in ('vendor', 'website', 'description') if c in columns]
        data['vendors'] = rows('SELECT %s FROM vendors ORDER BY vendor' % ', '.join(wanted))

    # --- models ------------------------------------------------------------
    # Referenced by NAME, not id: ids differ between databases, and a catalog
    # keyed on them would silently attach models to the wrong vendor.
    if has('models'):
        data['models'] = rows("""
            SELECT m.modelnumber          AS modelnumber,
                   v.vendor               AS vendor,
                   mt.modeltype           AS modeltype,
                   m.description          AS description,
                   m.documentationurl     AS documentationurl,
                   m.imageurl             AS imageurl
            FROM models m
            LEFT JOIN vendors v     ON m.vendorid = v.vendorid
            LEFT JOIN modeltypes mt ON m.modeltypeid = mt.modeltypeid
            ORDER BY v.vendor, m.modelnumber""")

    # --- printer supplies --------------------------------------------------
    # The single most useful thing to inherit after the models themselves: a new
    # site with printers gets every toner, drum and maintenance-kit part number
    # already matched to the right model, instead of somebody reading them off
    # cartridges. Keyed by the model's natural key for the same reason models
    # are keyed by vendor name.
    if has('modelsupplies') and has('models'):
        data['modelsupplies'] = rows("""
            SELECT mo.modelnumber   AS modelnumber,
                   v.vendor         AS vendor,
                   s.supplytype     AS supplytype,
                   s.color          AS color,
                   s.capacitytier   AS capacitytier,
                   s.partnumber     AS partnumber,
                   s.marketingname  AS marketingname,
                   s.pageyield      AS pageyield,
                   s.notes          AS notes
            FROM modelsupplies s
            JOIN models mo      ON s.modelnumberid = mo.modelnumberid
            LEFT JOIN vendors v ON mo.vendorid = v.vendorid
            ORDER BY v.vendor, mo.modelnumber, s.partnumber""")

    # --- small generic vocabularies ---------------------------------------
    # Each is a controlled list that means the same thing at any site.
    if has('measuringtooltypes'):
        data['measuringtooltypes'] = rows(
            'SELECT name, description, color FROM measuringtooltypes ORDER BY name')
    if has('notificationtypes'):
        data['notificationtypes'] = rows(
            'SELECT typename, typedescription, typecolor FROM notificationtypes ORDER BY typename')
    if has('accessprotocols'):
        columns = {c['name'] for c in inspector.get_columns('accessprotocols')}
        wanted = [c for c in ('name', 'scheme', 'defaultport', 'linktemplate') if c in columns]
        data['accessprotocols'] = rows('SELECT %s FROM accessprotocols ORDER BY name'
                                       % ', '.join(wanted))

    # Drop empty optional values so the file stays readable and a NULL does not
    # arrive as the string "None".
    for key, records in data.items():
        cleaned = []
        for record in records:
            cleaned.append({k: v for k, v in record.items() if v not in (None, '')})
        data[key] = cleaned
    return data


def main():
    parser = argparse.ArgumentParser(description=__doc__,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('--out', default=DEFAULT_OUT, help='output file')
    parser.add_argument('--pretty', action='store_true',
                        help='indent the JSON so git diffs are readable')
    args = parser.parse_args()

    from shopdb import create_app
    from shopdb.extensions import db

    app = create_app()
    with app.app_context():
        data = export(db.session.connection())

    directory = os.path.dirname(os.path.abspath(args.out))
    if directory:
        os.makedirs(directory, exist_ok=True)
    with open(args.out, 'w', encoding='utf-8') as handle:
        json.dump(data, handle, indent=2 if args.pretty else None,
                  ensure_ascii=False, sort_keys=False)
        handle.write('\n')

    print('Wrote %s' % args.out)
    for key, records in data.items():
        print('  %-22s %d' % (key, len(records)))
    print('')
    print('Load it on another instance with:  flask seed catalog')
    return 0


if __name__ == '__main__':
    sys.exit(main())
