"""GE-Enforce observed-state reporting: PCs POST enforcement results.

Covers the report ingest (applied version + per-entry self-heal outcomes), the
latest-per-host upsert, received-latest derivation (did the PC pick up the newest
published manifest?), status derivation (ok / selfhealed / failed), and the admin
fleet-compliance views.
"""

import json

from plugins.geenforce import service


SCOPE = {
    'Version': '2.6',
    'Applications': [
        {'Name': 'Alpha', 'Type': 'MSI', 'Installer': 'apps/alpha.msi'},
        {'Name': 'Beta', 'Type': 'PS1', 'Script': 'scripts/beta.ps1'},
    ],
}


def _seed_and_publish(app, scopename='gea-shopfloor-cmm'):
    with app.app_context():
        service.replace_scope_draft(scopename, 'runtime', SCOPE)
        version = service.publish_scope(scopename, 'runtime', notes='v')
        service.db.session.commit()
    return version


def _token(client, auth_headers, scopes):
    resp = client.post('/api/apitokens',
                       json={'name': 'svc', 'scopes': scopes},
                       headers=auth_headers)
    assert resp.status_code == 201, resp.get_json()
    return resp.get_json()['data']['secret']


def test_report_malformed_counts_is_400(client, db, app, auth_headers):
    """A wrong JSON shape (counts as a string, results as a string) must be a
    clean 400, not a 500 from a later .get()/iteration."""
    _seed_and_publish(app)
    secret = _token(client, auth_headers, ['geenforce.report'])
    for bad in ({'counts': 'nope'}, {'results': 'nope'}):
        body = {'hostname': 'WJCMM01', 'scopename': 'gea-shopfloor-cmm'}
        body.update(bad)
        resp = client.post('/api/geenforce/report', json=body,
                           headers={'X-API-Key': secret})
        assert resp.status_code == 400, resp.get_json()


def test_report_recorded_and_listed(client, db, app, auth_headers):
    _seed_and_publish(app)
    secret = _token(client, auth_headers, ['geenforce.report'])
    resp = client.post('/api/geenforce/report', json={
        'hostname': 'WJCMM01', 'scopename': 'gea-shopfloor-cmm',
        'appliedversion': 1, 'enforcerversion': '2.6',
        'counts': {'installed': 1, 'skipped': 1, 'failed': 0, 'filtered': 0},
        'results': [
            {'name': 'Alpha', 'action': 'installed', 'selfhealed': True},
            {'name': 'Beta', 'action': 'skipped'},
        ],
    }, headers={'X-API-Key': secret})
    assert resp.status_code == 200, resp.get_json()
    assert resp.get_json()['data']['status'] == 'selfhealed'

    listing = client.get('/api/geenforce/reports', headers=auth_headers)
    rows = listing.get_json()['data']
    assert len(rows) == 1
    row = rows[0]
    assert row['hostname'] == 'WJCMM01'
    assert row['appliedversion'] == 1
    assert row['latestversion'] == 1
    assert row['receivedlatest'] is True
    assert row['installed'] == 1 and row['skipped'] == 1


def test_received_latest_flips_when_new_version_published(client, db, app,
                                                          auth_headers):
    _seed_and_publish(app)
    secret = _token(client, auth_headers, ['geenforce.report'])
    client.post('/api/geenforce/report',
                json={'hostname': 'WJCMM02', 'scopename': 'gea-shopfloor-cmm',
                      'appliedversion': 1, 'counts': {}},
                headers={'X-API-Key': secret})
    # Publish a newer version; the PC is now behind.
    with app.app_context():
        service.publish_scope('gea-shopfloor-cmm', 'runtime', notes='v2')
        service.db.session.commit()

    row = client.get('/api/geenforce/reports?hostname=WJCMM02',
                     headers=auth_headers).get_json()['data'][0]
    assert row['appliedversion'] == 1
    assert row['latestversion'] == 2
    assert row['receivedlatest'] is False


def test_latest_report_upserts_per_host(client, db, app, auth_headers):
    _seed_and_publish(app)
    secret = _token(client, auth_headers, ['geenforce.report'])
    for installed in (0, 1):
        client.post('/api/geenforce/report',
                    json={'hostname': 'WJCMM03', 'scopename': 'gea-shopfloor-cmm',
                          'appliedversion': 1,
                          'counts': {'installed': installed}},
                    headers={'X-API-Key': secret})
    rows = client.get('/api/geenforce/reports?hostname=WJCMM03',
                      headers=auth_headers).get_json()['data']
    assert len(rows) == 1  # only the latest is current
    assert rows[0]['installed'] == 1


def test_hostname_case_variation_keeps_single_current(client, db, app, auth_headers):
    """A PC reporting its hostname in different casing must not leave two
    iscurrent rows (the demote is case-insensitive, matching the ilike read)."""
    _seed_and_publish(app)
    secret = _token(client, auth_headers, ['geenforce.report'])
    for host in ('WJCMM09', 'wjcmm09'):
        client.post('/api/geenforce/report',
                    json={'hostname': host, 'scopename': 'gea-shopfloor-cmm',
                          'appliedversion': 1, 'counts': {'installed': 1}},
                    headers={'X-API-Key': secret})
    from plugins.geenforce.models import ManifestEnforcementReport
    with app.app_context():
        current = ManifestEnforcementReport.query.filter(
            ManifestEnforcementReport.hostname.ilike('WJCMM09'),
            ManifestEnforcementReport.iscurrent == True).all()
        assert len(current) == 1


def test_status_failed_when_failures(client, db, app, auth_headers):
    _seed_and_publish(app)
    secret = _token(client, auth_headers, ['geenforce.report'])
    resp = client.post('/api/geenforce/report',
                       json={'hostname': 'WJCMM04', 'scopename': 'gea-shopfloor-cmm',
                             'counts': {'failed': 1},
                             'results': [{'name': 'Alpha', 'action': 'failed',
                                          'exitcode': 1603,
                                          'message': 'MSI 1603'}]},
                       headers={'X-API-Key': secret})
    assert resp.get_json()['data']['status'] == 'failed'


def test_report_detail_shows_per_entry_results(client, db, app, auth_headers):
    _seed_and_publish(app)
    secret = _token(client, auth_headers, ['geenforce.report'])
    post = client.post('/api/geenforce/report',
                       json={'hostname': 'WJCMM05', 'scopename': 'gea-shopfloor-cmm',
                             'results': [
                                 {'name': 'Alpha', 'action': 'installed',
                                  'selfhealed': True},
                                 {'name': 'Beta', 'action': 'failed',
                                  'exitcode': 1, 'message': 'boom'}]},
                       headers={'X-API-Key': secret})
    reportid = post.get_json()['data']['reportid']
    detail = client.get(f'/api/geenforce/reports/{reportid}',
                        headers=auth_headers).get_json()['data']
    results = {r['entryname']: r for r in detail['results']}
    assert results['Alpha']['selfhealed'] is True
    assert results['Beta']['action'] == 'failed'
    assert results['Beta']['message'] == 'boom'


def test_report_requires_report_scope(client, db, app, auth_headers):
    _seed_and_publish(app)
    # A fetch-only token cannot report.
    secret = _token(client, auth_headers, ['geenforce.fetch'])
    resp = client.post('/api/geenforce/report',
                       json={'hostname': 'WJCMM06'},
                       headers={'X-API-Key': secret})
    assert resp.status_code == 401


def test_report_unauthenticated_rejected(client, db, app):
    resp = client.post('/api/geenforce/report', json={'hostname': 'WJCMM07'})
    assert resp.status_code == 401


def test_report_missing_hostname_rejected(client, db, app, auth_headers):
    secret = _token(client, auth_headers, ['geenforce.report'])
    resp = client.post('/api/geenforce/report', json={'scopename': 'x'},
                       headers={'X-API-Key': secret})
    assert resp.status_code == 400
