"""Resource-scope binding on geenforce fetch tokens.

A geenforce.fetch token MAY be pinned to specific manifest scopes
(ApiToken.resourcescopes). A bound token can only pull those scopes' manifests
and only the blobs those scopes reference; an unbound token (NULL) keeps the
original behavior. This is what lets a fleet-wide display key be handed out
without exposing every scope + every blob in the content store.
"""

import hashlib

from plugins.geenforce import service

DISPLAY = 'gea-shopfloor-display'
OTHER = 'gea-shopfloor-cmm'

KIOSK_BYTES = b'kiosk-installer-payload-bytes'
KIOSK_SHA = hashlib.sha256(KIOSK_BYTES).hexdigest()


def _publish_display_scope(app):
    """Publish a display scope whose one entry references KIOSK_SHA over http."""
    with app.app_context():
        service.store_blob(KIOSK_BYTES, 'kiosk.exe', 'application/octet-stream')
        manifest = {
            'Version': '2.0',
            'Applications': [
                {'Name': 'Kiosk', 'Type': 'EXE', 'Installer': 'apps/kiosk.exe',
                 'DetectionMethod': 'Always',
                 'PayloadSource': 'http', 'PayloadSha256': KIOSK_SHA},
            ],
        }
        service.replace_scope_draft(DISPLAY, 'runtime', manifest)
        service.publish_scope(DISPLAY, 'runtime', notes='display')
        # A second, unrelated scope + blob the display token must NOT reach.
        service.store_blob(b'secret-other-scope-bytes', 'other.exe')
        service.replace_scope_draft(OTHER, 'runtime', {
            'Version': '2.6',
            'Applications': [{'Name': 'Beta', 'Type': 'PS1',
                              'Script': 'scripts/beta.ps1',
                              'DetectionMethod': 'Always'}]})
        service.publish_scope(OTHER, 'runtime', notes='other')
        service.db.session.commit()


def _mint(client, auth_headers, resourcescopes=None):
    payload = {'name': 'display svc', 'scopes': ['geenforce.fetch']}
    if resourcescopes is not None:
        payload['resourcescopes'] = resourcescopes
    resp = client.post('/api/apitokens', json=payload, headers=auth_headers)
    assert resp.status_code == 201, resp.get_json()
    return resp.get_json()['data']


def _key(client, auth_headers, resourcescopes=None):
    return {'X-API-Key': _mint(client, auth_headers, resourcescopes)['secret']}


# -- manifest binding ---------------------------------------------------------

def test_bound_token_gets_its_own_scope(client, app, db, auth_headers):
    _publish_display_scope(app)
    headers = _key(client, auth_headers, resourcescopes=[DISPLAY])
    resp = client.get(f'/api/geenforce/manifest?pctype={DISPLAY}', headers=headers)
    assert resp.status_code == 200


def test_bound_token_forbidden_other_scope(client, app, db, auth_headers):
    _publish_display_scope(app)
    headers = _key(client, auth_headers, resourcescopes=[DISPLAY])
    resp = client.get(f'/api/geenforce/manifest?pctype={OTHER}', headers=headers)
    assert resp.status_code == 403


def test_unbound_token_reaches_any_scope(client, app, db, auth_headers):
    _publish_display_scope(app)
    headers = _key(client, auth_headers)  # no resourcescopes -> unrestricted
    assert client.get(f'/api/geenforce/manifest?pctype={DISPLAY}',
                      headers=headers).status_code == 200
    assert client.get(f'/api/geenforce/manifest?pctype={OTHER}',
                      headers=headers).status_code == 200


# -- payload binding ----------------------------------------------------------

def test_bound_token_gets_referenced_blob(client, app, db, auth_headers):
    _publish_display_scope(app)
    headers = _key(client, auth_headers, resourcescopes=[DISPLAY])
    resp = client.get(f'/api/geenforce/payload/{KIOSK_SHA}', headers=headers)
    assert resp.status_code == 200
    assert resp.data == KIOSK_BYTES


def test_bound_token_404_on_unreferenced_blob(client, app, db, auth_headers):
    _publish_display_scope(app)
    # A blob the display scope does NOT reference (belongs to OTHER).
    other_sha = hashlib.sha256(b'secret-other-scope-bytes').hexdigest()
    headers = _key(client, auth_headers, resourcescopes=[DISPLAY])
    resp = client.get(f'/api/geenforce/payload/{other_sha}', headers=headers)
    assert resp.status_code == 404


def test_unbound_token_gets_any_blob(client, app, db, auth_headers):
    _publish_display_scope(app)
    other_sha = hashlib.sha256(b'secret-other-scope-bytes').hexdigest()
    headers = _key(client, auth_headers)
    assert client.get(f'/api/geenforce/payload/{other_sha}',
                      headers=headers).status_code == 200


# -- token model / API --------------------------------------------------------

def test_create_persists_resourcescopes(client, app, db, auth_headers):
    data = _mint(client, auth_headers, resourcescopes=[DISPLAY])
    assert data['resourcescopes'] == [DISPLAY]


def test_resourcescopes_null_by_default(client, app, db, auth_headers):
    data = _mint(client, auth_headers)
    assert data['resourcescopes'] is None


def test_bad_resourcescopes_rejected(client, app, db, auth_headers):
    resp = client.post('/api/apitokens',
                       json={'name': 'x', 'scopes': ['geenforce.fetch'],
                             'resourcescopes': 'not-a-list'},
                       headers=auth_headers)
    assert resp.status_code == 400
