"""What global search can reach, and what it must not return.

Two plugins own records that are NOT assets - USB devices and printed items -
so the generic asset search cannot see them, and nothing else did either: those
records were unreachable from search entirely while their list pages existed and
their labels were printed on physical bins and sticks.

The other half of this file is the retired-application rule. Hiding an article
about a decommissioned application from the plugin's own listing while global
search still returned it is not a rule at all - the article stayed two
keystrokes away, and the result printed the retired application as its subject.
A filter is only real if every path that reaches the row applies it.
"""

import pytest

from shopdb.core.models import Application


def search(client, query):
    """Global search rows for a query."""
    resp = client.get(f'/api/search?q={query}')
    assert resp.status_code == 200, resp.get_json()
    return resp.get_json()['data']['results']


def titles(client, query, resulttype=None):
    return [r['title'] for r in search(client, query)
            if resulttype is None or r['type'] == resulttype]


# --- USB devices (usb plugin, not an asset) ---------------------------------

@pytest.fixture
def usbdevices(db):
    """One live device and one deactivated one, distinctive enough that only the
    USB searcher can match them."""
    from plugins.usb.models import USBDevice
    live = USBDevice(serialnumber='ZZSERIAL1234', label='Loaner stick 4',
                     assetnumber='USB-0004', productname='Kingston DataTraveler',
                     isactive=True)
    retired = USBDevice(serialnumber='ZZSERIAL9999', label='Dead stick',
                        assetnumber='USB-0009', productname='Kingston DataTraveler',
                        isactive=False)
    db.session.add_all([live, retired])
    db.session.commit()
    return {'live': live.usbdeviceid, 'retired': retired.usbdeviceid}


def test_a_usb_device_is_found_by_serial(client, usbdevices):
    """The serial is what is printed on the stick in someone's hand."""
    rows = [r for r in search(client, 'ZZSERIAL1234') if r['type'] == 'usb_device']
    assert len(rows) == 1
    assert rows[0]['id'] == usbdevices['live']
    assert rows[0]['url'] == f"/usb/{usbdevices['live']}"
    # An exact serial is the strongest possible match for this domain.
    assert rows[0]['relevance'] == 100


def test_a_usb_device_is_found_by_label_and_asset_tag(client, usbdevices):
    assert 'Loaner stick 4' in titles(client, 'Loaner', 'usb_device')
    assert 'Loaner stick 4' in titles(client, 'USB-0004', 'usb_device')


def test_a_deactivated_usb_device_is_not_found(client, usbdevices):
    assert titles(client, 'ZZSERIAL9999', 'usb_device') == []
    # And it does not ride along on a query that matches both devices.
    assert 'Dead stick' not in titles(client, 'Kingston', 'usb_device')


def test_a_usb_holder_is_not_searchable_by_name(client, db):
    """currentusername records who holds a device. Making search a way to list
    what a named person checked out is a different feature, deliberately not
    built into this searcher."""
    from plugins.usb.models import USBDevice
    db.session.add(USBDevice(serialnumber='ZZSERIAL5555', label='Held stick',
                             currentusername='Distinctivesurname', isactive=True))
    db.session.commit()
    assert titles(client, 'Distinctivesurname', 'usb_device') == []


# --- Printed items (printedparts plugin, not an asset) ----------------------

@pytest.fixture
def printeditems(db):
    from plugins.printedparts.models import PrintedItem
    live = PrintedItem(itemcode='3DP-8001', gagelabtag='WJRP8001',
                       itemname='Fixture clamp', itemdescription='Holds a part',
                       binlocation='Bin 12', quantityonhand=4,
                       lowstockthreshold=2, isactive=True)
    retired = PrintedItem(itemcode='3DP-8009', gagelabtag='WJRP8009',
                          itemname='Obsolete clamp', quantityonhand=0,
                          lowstockthreshold=1, isactive=False)
    db.session.add_all([live, retired])
    db.session.commit()
    return {'live': live.printeditemid, 'retired': retired.printeditemid}


def test_a_printed_item_is_found_by_bin_code(client, printeditems):
    """itemcode is the bin label someone reads off the shelf."""
    rows = [r for r in search(client, '3DP-8001') if r['type'] == 'printed_item']
    assert len(rows) == 1
    assert rows[0]['id'] == printeditems['live']
    assert rows[0]['url'] == f"/printedparts/{printeditems['live']}"
    assert rows[0]['relevance'] == 100


def test_a_printed_item_is_found_by_gage_lab_tag(client, printeditems):
    rows = [r for r in search(client, 'WJRP8001') if r['type'] == 'printed_item']
    assert [r['title'] for r in rows] == ['Fixture clamp']


def test_a_printed_item_is_found_by_name(client, printeditems):
    assert 'Fixture clamp' in titles(client, 'Fixture clamp', 'printed_item')


def test_a_printed_item_subtitle_locates_it(client, printeditems):
    """A hit is only useful if it says where to go and get the part."""
    rows = [r for r in search(client, '3DP-8001') if r['type'] == 'printed_item']
    assert 'Bin 12' in rows[0]['subtitle']


def test_a_deactivated_printed_item_is_not_found(client, printeditems):
    assert titles(client, '3DP-8009', 'printed_item') == []
    assert 'Obsolete clamp' not in titles(client, 'clamp', 'printed_item')


# --- The retired-application rule reaches global search too ----------------

@pytest.fixture
def kbarticles(db):
    from plugins.knowledgebase.models import KnowledgeBase
    live = Application(appname='Livesearchapp', isactive=True)
    retired = Application(appname='Retiredsearchapp', isactive=False)
    db.session.add_all([live, retired])
    db.session.flush()
    db.session.add_all([
        KnowledgeBase(shortdescription='Zzrunbook for the live one', linkurl='u',
                      keywords='zzkeyword', appid=live.appid, isactive=True),
        KnowledgeBase(shortdescription='Zzrunbook for the retired one', linkurl='u',
                      keywords='zzkeyword', appid=retired.appid, isactive=True),
        KnowledgeBase(shortdescription='Zzrunbook with no topic', linkurl='u',
                      keywords='zzkeyword', appid=None, isactive=True),
    ])
    db.session.commit()


def test_global_search_hides_an_article_about_a_retired_application(client, kbarticles):
    found = titles(client, 'zzkeyword', 'knowledgebase')
    assert 'Zzrunbook for the retired one' not in found
    assert 'Zzrunbook for the live one' in found


def test_global_search_keeps_an_article_with_no_topic(client, kbarticles):
    """A null topic is not a retired one."""
    assert 'Zzrunbook with no topic' in titles(client, 'zzkeyword', 'knowledgebase')


def test_global_search_hides_a_retired_application_itself(client, kbarticles):
    """The application domain has always filtered isactive; pinned here so the
    two rules stay together and cannot drift apart again."""
    assert titles(client, 'Retiredsearchapp', 'application') == []
    assert titles(client, 'Livesearchapp', 'application') == ['Livesearchapp']


# --- The optional asset identifiers (ADR-001) -------------------------------

@pytest.fixture
def taggedmachine(client, db, auth_headers):
    """A machine carrying both optional identifiers.

    A MACHINE on purpose, not a measuring tool: gaugelabreference was searchable
    only through the measuring-tools searcher, so the same tag on a machine
    matched nothing even though Settings offers the identifier for machines.
    """
    from shopdb.core.models import AssetType, Asset
    if not AssetType.query.filter_by(assettype='machine').first():
        db.session.add(AssetType(assettype='machine', pluginname='machines',
                                 tablename='machines', description='m'))
        db.session.commit()
    resp = client.post('/api/machines', json={'assetnumber': 'ZZMACH01'},
                       headers=auth_headers)
    assert resp.status_code == 201, resp.get_json()
    asset = Asset.query.filter_by(assetnumber='ZZMACH01').first()
    asset.gaugelabreference = 'ZZGAUGE777'
    asset.maintenancereference = 'ZZMAINT888'
    db.session.commit()
    return asset.assetid


def test_a_machine_is_found_by_its_gauge_lab_reference(client, taggedmachine):
    rows = [r for r in search(client, 'ZZGAUGE777') if r['id'] == taggedmachine
            or r['title'] == 'ZZMACH01']
    assert rows, 'a gauge-lab tag on a machine matched nothing'


def test_a_machine_is_found_by_its_maintenance_reference(client, taggedmachine):
    """maintenancereference was searched by nothing at all, for any asset
    type - a field the UI collects and search could not find."""
    rows = [r for r in search(client, 'ZZMAINT888') if r['title'] == 'ZZMACH01']
    assert rows, 'a maintenance reference matched nothing'
