"""GE-Enforce compliance: fleet-install coverage per app-linked entry.

Compliance pairs each app-linked manifest entry with collected PC data
(ComputerInstalledApp) to answer 'how many PCs have this app, and how many on
the expected version'. Only entries carrying a curated appid appear; unlinked
entries are skipped. FileVersion detection supplies the expected-version target;
every other detection method yields a null target (no meaningful match). When
the optional computers plugin is absent, counts degrade to null gracefully.
"""

from shopdb.core.models import Asset, AssetType, Application

from plugins.geenforce import service


def _seed_app(db, appname):
    app_row = Application(appname=appname)
    db.session.add(app_row)
    db.session.commit()
    return app_row.appid


def _seed_pc_with_app(db, hostname, appid, installedversion, isactive=True):
    """Create a Computer + one ComputerInstalledApp row for it."""
    from plugins.computers.models import Computer, ComputerInstalledApp
    atype = AssetType.query.filter_by(assettype='computer').first()
    if not atype:
        atype = AssetType(assettype='computer')
        db.session.add(atype)
        db.session.flush()
    asset = Asset(assetnumber=f'PC-{hostname}', assettypeid=atype.assettypeid)
    db.session.add(asset)
    db.session.flush()
    comp = Computer(assetid=asset.assetid, hostname=hostname)
    db.session.add(comp)
    db.session.flush()
    link = ComputerInstalledApp(computerid=comp.computerid, appid=appid,
                                installedversion=installedversion,
                                isactive=isactive)
    db.session.add(link)
    db.session.commit()
    return comp


def _create_scope(client, auth_headers, name='gea-shopfloor-cmm'):
    resp = client.post('/api/geenforce/scopes',
                       json={'scopename': name, 'phase': 'runtime'},
                       headers=auth_headers)
    assert resp.status_code == 201, resp.get_json()
    return resp.get_json()['data']['scopeid']


def _add_entry(client, auth_headers, scopeid, entry):
    resp = client.post(f'/api/geenforce/scopes/{scopeid}/entries',
                       json=entry, headers=auth_headers)
    assert resp.status_code == 201, resp.get_json()
    return resp.get_json()['data']['entryid']


def test_compliance_only_lists_app_linked_entries(client, db, auth_headers):
    appid = _seed_app(db, 'PC-DMIS')
    scopeid = _create_scope(client, auth_headers)
    _add_entry(client, auth_headers, scopeid,
               {'Name': 'PC-DMIS 2019', 'Type': 'MSI', 'appid': appid,
                'DetectionMethod': 'FileVersion', 'DetectionPath': 'C:\\p.exe',
                'DetectionValue': '2019 R1'})
    # An unlinked entry (no appid) must NOT appear.
    _add_entry(client, auth_headers, scopeid,
               {'Name': 'Unlinked', 'Type': 'MSI', 'Installer': 'apps/x.msi'})

    data = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
                      headers=auth_headers).get_json()['data']
    assert data['computersplugin'] is True
    assert [r['entryname'] for r in data['rows']] == ['PC-DMIS 2019']
    row = data['rows'][0]
    assert row['appid'] == appid
    assert row['appname'] == 'PC-DMIS'
    assert row['expectedversion'] == '2019 R1'


def test_compliance_counts_active_installs_and_version_match(client, db,
                                                             auth_headers):
    appid = _seed_app(db, 'PC-DMIS')
    # Two PCs on the expected version, one on a different version, one inactive.
    _seed_pc_with_app(db, 'CMM01', appid, '2019 R1')
    _seed_pc_with_app(db, 'CMM02', appid, '2019 R1')
    _seed_pc_with_app(db, 'CMM03', appid, '2016 R2')
    _seed_pc_with_app(db, 'CMM04', appid, '2019 R1', isactive=False)

    scopeid = _create_scope(client, auth_headers)
    _add_entry(client, auth_headers, scopeid,
               {'Name': 'PC-DMIS 2019', 'Type': 'MSI', 'appid': appid,
                'DetectionMethod': 'FileVersion', 'DetectionPath': 'C:\\p.exe',
                'DetectionValue': '2019 R1'})

    row = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
                     headers=auth_headers).get_json()['data']['rows'][0]
    # Inactive row excluded: 3 active installs, 2 on the expected version.
    assert row['installedcount'] == 3
    assert row['versionmatchcount'] == 2
    assert '2 of 3' in row['coveragenote']


def test_compliance_non_fileversion_has_null_version_target(client, db,
                                                            auth_headers):
    appid = _seed_app(db, 'eDNC')
    _seed_pc_with_app(db, 'DNC01', appid, '5.0')
    scopeid = _create_scope(client, auth_headers)
    # Registry detection is not a version string -> no expected-version target.
    _add_entry(client, auth_headers, scopeid,
               {'Name': 'eDNC', 'Type': 'MSI', 'appid': appid,
                'DetectionMethod': 'Registry',
                'DetectionPath': 'HKLM\\Software\\eDNC'})

    row = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
                     headers=auth_headers).get_json()['data']['rows'][0]
    assert row['expectedversion'] is None
    assert row['installedcount'] == 1
    assert row['versionmatchcount'] is None
    assert 'no version target' in row['coveragenote']


def test_compliance_not_installed_anywhere(client, db, auth_headers):
    appid = _seed_app(db, 'Orphan')
    scopeid = _create_scope(client, auth_headers)
    _add_entry(client, auth_headers, scopeid,
               {'Name': 'Orphan', 'Type': 'MSI', 'appid': appid,
                'DetectionMethod': 'FileVersion', 'DetectionPath': 'C:\\o.exe',
                'DetectionValue': '1.0'})

    row = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
                     headers=auth_headers).get_json()['data']['rows'][0]
    assert row['installedcount'] == 0
    assert row['versionmatchcount'] == 0
    assert row['coveragenote'] == 'not installed on any collected PC'


def test_compliance_scope_not_found(client, db, auth_headers):
    resp = client.get('/api/geenforce/scopes/999999/compliance',
                      headers=auth_headers)
    assert resp.status_code == 404


def test_compliance_requires_manage_permission(client, db, member_headers):
    resp = client.get('/api/geenforce/scopes/1/compliance',
                      headers=member_headers)
    assert resp.status_code == 403


def test_compliance_graceful_without_computers_plugin(client, db, auth_headers,
                                                      monkeypatch):
    appid = _seed_app(db, 'PC-DMIS')
    scopeid = _create_scope(client, auth_headers)
    _add_entry(client, auth_headers, scopeid,
               {'Name': 'PC-DMIS 2019', 'Type': 'MSI', 'appid': appid,
                'DetectionMethod': 'FileVersion', 'DetectionPath': 'C:\\p.exe',
                'DetectionValue': '2019 R1'})
    monkeypatch.setattr(service, '_installed_app_model', lambda: None)

    data = client.get(f'/api/geenforce/scopes/{scopeid}/compliance',
                      headers=auth_headers).get_json()['data']
    assert data['computersplugin'] is False
    row = data['rows'][0]
    assert row['installedcount'] is None
    assert row['versionmatchcount'] is None
    assert 'computers plugin not installed' in row['coveragenote']
