"""GE-Enforce dashboard card: what is broken on the floor right now.

The reports table has always held enforcement failures - the failing entry, its
exit code, the engine's message - and the only way to see any of it was to open
one PC's report modal, one PC at a time. A bay returned 500 to every collector
report for a day and a half before anyone looked. This card is the fix, so
these tests pin the behaviour that makes it useful rather than decorative.
"""

from datetime import datetime

from plugins.geenforce.models import (ManifestEnforcementReport,
                                      ManifestEnforcementResult)

URL = '/api/geenforce/dashboard/failures'


def _report(db, hostname, iscurrent=True, results=(), scopename='gea-shopfloor-cmm',
            receivedat=None):
    report = ManifestEnforcementReport(
        hostname=hostname, scopename=scopename, phase='runtime',
        status='failed', iscurrent=iscurrent,
        receivedat=receivedat or datetime(2026, 8, 11, 12, 0, 0))
    db.session.add(report)
    db.session.flush()
    for entryname, action, exitcode, message in results:
        db.session.add(ManifestEnforcementResult(
            reportid=report.reportid, entryname=entryname, action=action,
            exitcode=exitcode, message=message))
    db.session.commit()
    return report


def test_a_failed_entry_is_listed_with_what_a_person_needs(client, db, auth_headers):
    """Per ENTRY, not per report. "Three PCs failed" is a number; "Install
    OpenText failed with exit 1603 on WJSF1234" is something to act on."""
    _report(db, 'WJSF1234', results=[
        ('Install OpenText', 'failed', 1603, 'Fatal error during installation')])

    rows = client.get(URL, headers=auth_headers).get_json()['data']
    assert len(rows) == 1
    assert rows[0]['hostname'] == 'WJSF1234'
    assert rows[0]['entryname'] == 'Install OpenText'
    assert rows[0]['exitcode'] == 1603
    assert 'Fatal error' in rows[0]['message']


def test_only_failures_appear(client, db, auth_headers):
    """A cycle installs, skips and filters far more than it fails. Listing
    anything but failures is how a card becomes wallpaper."""
    _report(db, 'WJSF1234', results=[
        ('Install OpenText', 'failed', 1603, 'boom'),
        ('Install Acrobat', 'installed', 0, ''),
        ('Set FMS host', 'skipped', 0, ''),
        ('CMM settings', 'filtered', 0, 'PCTypes filter')])

    rows = client.get(URL, headers=auth_headers).get_json()['data']
    assert [r['entryname'] for r in rows] == ['Install OpenText']


def test_a_fixed_failure_disappears_on_its_own(client, db, auth_headers):
    """Superseded reports are not current. A failure that has since been fixed
    must clear itself rather than needing someone to dismiss it - otherwise the
    card accumulates history and stops meaning "right now"."""
    _report(db, 'WJSF1234', iscurrent=False, results=[
        ('Install OpenText', 'failed', 1603, 'boom')])
    _report(db, 'WJSF1234', iscurrent=True, results=[
        ('Install OpenText', 'installed', 0, '')])

    assert client.get(URL, headers=auth_headers).get_json()['data'] == []


def test_the_row_links_to_the_pc_when_shopdb_knows_it(client, db, auth_headers):
    """A card that names a PC without linking to it is a worse report."""
    from plugins.computers.models import Computer
    from shopdb.core.models import Asset, AssetType

    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='WJSF1234',
                  assettypeid=atype.assettypeid, statusid=1)
    db.session.add(asset)
    db.session.flush()
    comp = Computer(assetid=asset.assetid, hostname='WJSF1234')
    db.session.add(comp)
    db.session.commit()

    _report(db, 'WJSF1234', results=[('Install OpenText', 'failed', 1603, 'x')])
    rows = client.get(URL, headers=auth_headers).get_json()['data']
    assert rows[0]['computerid'] == comp.computerid


def test_a_pc_shopdb_does_not_know_still_reports_its_failure(client, db,
                                                             auth_headers):
    """The failure is real even when the inventory is behind. Dropping the row
    would hide exactly the bay most likely to be misconfigured."""
    _report(db, 'GHOSTPC', results=[('Install OpenText', 'failed', 1603, 'x')])
    rows = client.get(URL, headers=auth_headers).get_json()['data']
    assert len(rows) == 1
    assert rows[0]['computerid'] is None


def test_the_card_is_permission_gated(client, db, member_headers):
    """A dashboard card must not become a way around RBAC."""
    assert client.get(URL, headers=member_headers).status_code == 403


def test_the_widget_declares_data_and_a_renderer_not_a_component(app):
    """The old contract named a Vue component per widget, which cannot survive
    a lean build - which is why five plugins declared widgets pointing at
    components nobody wrote. A plugin declares data and shape; core renders."""
    from plugins.geenforce.plugin import GeEnforcePlugin

    widget = GeEnforcePlugin().get_dashboard_widgets()[0]
    assert 'component' not in widget
    assert widget['render'] == 'exceptions'
    assert widget['permission'] == 'geenforce.manage'
    # Empty cards must shrink, or the board becomes wallpaper.
    assert widget['empty'] == 'hide'
    assert widget['map']['link'] == '/pcs/{computerid}'
