"""Tests for the computers collector PC->printer relationship sync.

A collector payload carrying defaultprinter/printers creates AssetRelationship
rows linking the PC asset to the resolved printer assets ('defaultprinter' for
the default, 'connectedto' for the rest). Stale collector links archive on
re-report; manual links survive; unresolved identifiers warn without failing.
"""

import pytest

from shopdb.core.models import (
    Asset, AssetType, AssetRelationship, RelationshipType, CommunicationType,
    Communication)
from plugins.printers.models import Printer

KEY = 'testcollectorkey'


@pytest.fixture
def collector_key(app):
    old = app.config.get('COLLECTOR_API_KEY')
    app.config['COLLECTOR_API_KEY'] = KEY
    yield KEY
    app.config['COLLECTOR_API_KEY'] = old


@pytest.fixture
def scene(db):
    """Computer + printer asset types, the two relationship types, and three
    printer assets identified by windowsname / hostname / IP."""
    pc_type = AssetType(assettype='computer', pluginname='computers',
                        tablename='computers')
    pr_type = AssetType(assettype='printer', pluginname='printers',
                        tablename='printers')
    dp_type = RelationshipType(relationshiptype='defaultprinter',
                               description='PC to default printer',
                               isdirectional=True)
    ct_type = RelationshipType(relationshiptype='connectedto',
                               description='Network link', isdirectional=False)
    ip_comtype = CommunicationType(comtype='IP')
    db.session.add_all([pc_type, pr_type, dp_type, ct_type, ip_comtype])
    db.session.flush()

    # Printer A: matched by windowsname. Printer B: by hostname. Printer C: by IP.
    a_asset = Asset(assetnumber='PRN-A', name='Materials HP',
                    assettypeid=pr_type.assettypeid, isactive=True)
    b_asset = Asset(assetnumber='PRN-B', name='Front Office',
                    assettypeid=pr_type.assettypeid, isactive=True)
    c_asset = Asset(assetnumber='PRN-C', name='Shipping Label',
                    assettypeid=pr_type.assettypeid, isactive=True)
    db.session.add_all([a_asset, b_asset, c_asset])
    db.session.flush()

    db.session.add_all([
        Printer(assetid=a_asset.assetid, windowsname='HP-Materials'),
        Printer(assetid=b_asset.assetid, hostname='prn-front.wjs.local'),
        Printer(assetid=c_asset.assetid, windowsname='Zebra-Ship'),
    ])
    db.session.add(Communication(assetid=c_asset.assetid,
                                 comtypeid=ip_comtype.comtypeid,
                                 ipaddress='10.1.2.3', isprimary=True))
    db.session.commit()

    return {'pc_type': pc_type, 'dp_type': dp_type, 'ct_type': ct_type,
            'a': a_asset, 'b': b_asset, 'c': c_asset}


def _post(client, payload):
    return client.post('/api/collector/computers', json=payload,
                       headers={'X-API-Key': KEY})


def _pc_asset(db):
    from plugins.computers.models import Computer
    comp = Computer.query.first()
    return comp.asset


def _active_links(db, pcid):
    return AssetRelationship.query.filter_by(
        sourceassetid=pcid, isactive=True).all()


def test_creates_default_and_connected_links(client, db, collector_key, scene):
    """Default -> defaultprinter, others -> connectedto, resolved by three id forms."""
    resp = _post(client, {
        'hostname': 'WJPC-P1',
        'defaultprinter': 'HP-Materials',          # windowsname
        'printers': ['prn-front.wjs.local', '10.1.2.3'],  # hostname, IP
    })
    assert resp.status_code == 200, resp.get_json()
    data = resp.get_json()['data']
    assert data['printerlinkcount'] == 3
    assert not data['warnings']

    pc = _pc_asset(db)
    links = {(r.targetassetid, r.relationshiptype.relationshiptype)
             for r in _active_links(db, pc.assetid)}
    assert (scene['a'].assetid, 'defaultprinter') in links
    assert (scene['b'].assetid, 'connectedto') in links
    assert (scene['c'].assetid, 'connectedto') in links


def test_unresolved_identifier_warns_not_fails(client, db, collector_key, scene):
    """An unknown printer identifier is a warning, the rest still link."""
    resp = _post(client, {
        'hostname': 'WJPC-P2',
        'defaultprinter': 'HP-Materials',
        'printers': ['does-not-exist'],
    })
    assert resp.status_code == 200
    data = resp.get_json()['data']
    assert any('unresolved printer: does-not-exist' in w
               for w in data['warnings'])
    pc = _pc_asset(db)
    assert data['printerlinkcount'] == 1
    assert len(_active_links(db, pc.assetid)) == 1


def test_idempotent_no_duplicates(client, db, collector_key, scene):
    """Re-report the same set: no new rows, still one link per printer."""
    payload = {'hostname': 'WJPC-P3', 'defaultprinter': 'HP-Materials',
               'printers': ['Zebra-Ship']}
    assert _post(client, payload).status_code == 200
    assert _post(client, payload).status_code == 200

    pc = _pc_asset(db)
    all_rows = AssetRelationship.query.filter_by(sourceassetid=pc.assetid).all()
    assert len(all_rows) == 2  # one default + one connected, no dupes
    assert all(r.isactive for r in all_rows)


def test_dropped_printer_is_archived(client, db, collector_key, scene):
    """A printer that stops being reported loses its collector link."""
    _post(client, {'hostname': 'WJPC-P4', 'defaultprinter': 'HP-Materials',
                   'printers': ['Zebra-Ship']})
    pc = _pc_asset(db)
    assert len(_active_links(db, pc.assetid)) == 2

    # Re-report without Zebra-Ship.
    _post(client, {'hostname': 'WJPC-P4', 'defaultprinter': 'HP-Materials',
                   'printers': []})
    active = _active_links(db, pc.assetid)
    assert len(active) == 1
    assert active[0].targetassetid == scene['a'].assetid
    # the dropped row still exists but is inactive
    zebra = AssetRelationship.query.filter_by(
        sourceassetid=pc.assetid, targetassetid=scene['c'].assetid).first()
    assert zebra is not None and zebra.isactive is False


def test_default_change_archives_old_default(client, db, collector_key, scene):
    """Changing the default printer archives the previous defaultprinter link."""
    _post(client, {'hostname': 'WJPC-P5', 'defaultprinter': 'HP-Materials'})
    pc = _pc_asset(db)
    _post(client, {'hostname': 'WJPC-P5', 'defaultprinter': 'prn-front.wjs.local'})

    active = _active_links(db, pc.assetid)
    assert len(active) == 1
    assert active[0].targetassetid == scene['b'].assetid
    assert active[0].relationshiptype.relationshiptype == 'defaultprinter'


def test_manual_link_survives_collector_sync(client, db, collector_key, scene):
    """A manually-created link (no collector label) is never archived."""
    # PC created first via a bare report.
    _post(client, {'hostname': 'WJPC-P6', 'defaultprinter': 'HP-Materials',
                   'printers': ['Zebra-Ship']})
    pc = _pc_asset(db)

    # Manual connectedto link PC -> printer B (label stays NULL).
    manual = AssetRelationship(
        sourceassetid=pc.assetid, targetassetid=scene['b'].assetid,
        relationshiptypeid=scene['ct_type'].relationshiptypeid, notes='by hand')
    db.session.add(manual)
    db.session.commit()
    manual_id = manual.relationshipid

    # Re-report dropping Zebra-Ship and never mentioning printer B.
    _post(client, {'hostname': 'WJPC-P6', 'defaultprinter': 'HP-Materials',
                   'printers': []})

    surviving = db.session.get(AssetRelationship, manual_id)
    assert surviving.isactive is True   # manual link untouched
    # Zebra (collector) archived.
    zebra = AssetRelationship.query.filter_by(
        sourceassetid=pc.assetid, targetassetid=scene['c'].assetid).first()
    assert zebra.isactive is False


def test_no_printer_keys_leaves_links_untouched(client, db, collector_key, scene):
    """A report with no printer keys does not archive existing links."""
    _post(client, {'hostname': 'WJPC-P7', 'defaultprinter': 'HP-Materials'})
    pc = _pc_asset(db)
    assert len(_active_links(db, pc.assetid)) == 1

    # A plain update (no defaultprinter, no printers key) must not archive.
    _post(client, {'hostname': 'WJPC-P7', 'currentuser': 'alice'})
    assert len(_active_links(db, pc.assetid)) == 1
