"""End-to-end test for Phase 6: Admin-Web-UI.

Scenarios:
  1. Bootstrap — unauthed admin session returns logged_in:false.
  2. Wrong password — 401.
  3. Correct login — 200 with cookie.
  4. List accounts (through cookie).
  5. Create account → new UID returned.
  6. Adjust tokens (+100).
  7. Change status (disable/enable).
  8. Get usage log.
  9. List packages — only_active=false so disabled ones also show.
 10. Update package price.
 11. Audit log contains expected entries.
 12. Logout — next call 401.
"""
import os, sys, shutil, pathlib, importlib, tempfile

# Windows: Konsole ist default cp1252 -> Emoji/Pfeile im Print killen den Test.
try:
    sys.stdout.reconfigure(encoding='utf-8', errors='replace')
    sys.stderr.reconfigure(encoding='utf-8', errors='replace')
except Exception:
    pass

APP_DIR = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(APP_DIR))

TEST_ROOT = pathlib.Path(tempfile.gettempdir()) / 'abrechnung_test_p6'
shutil.rmtree(str(TEST_ROOT), ignore_errors=True)
TEST_ROOT.mkdir(parents=True, exist_ok=True)
TEST_DB = str(TEST_ROOT / 'licensing.sqlite3')
os.environ['ABRECHNUNG_LICENSE_DB'] = TEST_DB

import session_manager
session_manager.TEMP_BASE = TEST_ROOT

import licensing
licensing.init_db()

# Seed an admin account directly in DB
licensing.create_admin('admin-test', 'supersecret123')

import app as app_module
flask_app = app_module.app
flask_app.testing = True

# Sanity: admin routes present?
admin_rules = [r for r in flask_app.url_map.iter_rules() if r.rule.startswith('/admin')]
print(f'admin rules: {len(admin_rules)}')
assert len(admin_rules) >= 10, f'Missing admin rules: {[r.rule for r in admin_rules]}'

# Scenario 1: bootstrap status
with flask_app.test_client() as c:
    rv = c.get('/admin/api/session')
    print(f'1. /admin/api/session (unauthed) → {rv.status_code} {rv.get_json()}')
    assert rv.status_code == 200
    assert rv.get_json()['logged_in'] is False

# Scenario 2: wrong password
with flask_app.test_client() as c:
    rv = c.post('/admin/api/login', json={'username': 'admin-test', 'password': 'wrong'})
    print(f'2. /admin/api/login (wrong pw) → {rv.status_code}')
    assert rv.status_code == 401

# Scenario 3+: full happy path through a single client (cookie persists)
with flask_app.test_client() as c:
    # Login
    rv = c.post('/admin/api/login', json={'username': 'admin-test', 'password': 'supersecret123'})
    print(f'3. /admin/api/login → {rv.status_code} {rv.get_json()}')
    assert rv.status_code == 200
    assert rv.get_json()['username'] == 'admin-test'

    # 4. List accounts — empty initially
    rv = c.get('/admin/api/accounts')
    print(f'4. list accounts (empty) → {rv.status_code} count={len(rv.get_json()["accounts"])}')
    assert rv.status_code == 200
    assert rv.get_json()['accounts'] == []

    # 5. Create account
    rv = c.post('/admin/api/accounts', json={
        'customer_name': 'Testkunde GmbH', 'customer_email': 't@example.com',
        'device_name': 'Büro-PC', 'initial_tokens': 500, 'notes': 'Testkonto'
    })
    print(f'5. create account → {rv.status_code} {rv.get_json()}')
    assert rv.status_code == 200
    new_uid = rv.get_json()['uid']
    assert new_uid.startswith('EEGA-')

    # 5b. Relist
    rv = c.get('/admin/api/accounts')
    accts = rv.get_json()['accounts']
    print(f'   Nach Anlage: {len(accts)} Konto(en), erstes UID {accts[0]["uid"]}')
    assert len(accts) == 1
    assert accts[0]['remaining_tokens'] == 500

    # 6. Adjust tokens (+100)
    rv = c.post(f'/admin/api/accounts/{new_uid}/tokens', json={'delta': 100, 'reason': 'Nachkauf'})
    print(f'6. adjust +100 → {rv.status_code} {rv.get_json()}')
    assert rv.status_code == 200
    assert rv.get_json()['new_balance'] == 600

    # 6b. Adjust -1000 → would go negative → 400
    rv = c.post(f'/admin/api/accounts/{new_uid}/tokens', json={'delta': -1000, 'reason': 'test'})
    print(f'6b. adjust -1000 (too much) → {rv.status_code}')
    assert rv.status_code == 400

    # 7. Disable
    rv = c.post(f'/admin/api/accounts/{new_uid}/status', json={'status': 'disabled'})
    print(f'7. disable → {rv.status_code}')
    assert rv.status_code == 200

    # 7b. Re-enable
    rv = c.post(f'/admin/api/accounts/{new_uid}/status', json={'status': 'active'})
    print(f'7b. re-enable → {rv.status_code}')
    assert rv.status_code == 200

    # 7c. Invalid status
    rv = c.post(f'/admin/api/accounts/{new_uid}/status', json={'status': 'banned'})
    print(f'7c. invalid status → {rv.status_code}')
    assert rv.status_code == 400

    # 8. Usage log (via a real consume so there's an entry)
    #    Simulate a token consumption
    licensing.adjust_tokens(new_uid, -10, 'test_consume', admin_user='admin-test')
    rv = c.get(f'/admin/api/accounts/{new_uid}/usage')
    print(f'8. usage log → {rv.status_code} entries={len(rv.get_json()["entries"])}')
    assert rv.status_code == 200
    assert len(rv.get_json()['entries']) >= 1

    # 9. Packages
    rv = c.get('/admin/api/packages')
    print(f'9. packages → {rv.status_code} count={len(rv.get_json()["packages"])}')
    assert rv.status_code == 200
    pkgs = rv.get_json()['packages']
    assert len(pkgs) >= 4  # pdfs/sepa/kassabuch/report
    pdfs_before = next(p for p in pkgs if p['key'] == 'pdfs')
    print(f'   pdfs cost before: {pdfs_before["cost"]}')

    # 10. Update package
    rv = c.post('/admin/api/packages/pdfs', json={'cost': 20, 'is_active': True})
    print(f'10. update package → {rv.status_code} {rv.get_json()}')
    assert rv.status_code == 200
    assert rv.get_json()['cost'] == 20

    rv = c.get('/admin/api/packages')
    pdfs_after = next(p for p in rv.get_json()['packages'] if p['key'] == 'pdfs')
    print(f'   pdfs cost after: {pdfs_after["cost"]}')
    assert pdfs_after['cost'] == 20

    # 10b. Invalid cost
    rv = c.post('/admin/api/packages/pdfs', json={'cost': -5})
    print(f'10b. negative cost → {rv.status_code}')
    assert rv.status_code == 400

    # 10c. Unknown package
    rv = c.post('/admin/api/packages/does_not_exist', json={'cost': 5})
    print(f'10c. unknown package → {rv.status_code}')
    assert rv.status_code == 400

    # 11. Audit log
    rv = c.get('/admin/api/audit')
    print(f'11. audit log → {rv.status_code} entries={len(rv.get_json()["entries"])}')
    assert rv.status_code == 200
    entries = rv.get_json()['entries']
    actions = {e['action'] for e in entries}
    print(f'    actions: {actions}')
    assert 'create_account' in actions
    assert 'adjust_tokens' in actions
    assert 'update_package' in actions

    # 12. Logout + replay
    rv = c.post('/admin/api/logout')
    print(f'12. logout → {rv.status_code}')
    assert rv.status_code == 200

    rv = c.get('/admin/api/accounts')
    print(f'12b. after logout: /admin/api/accounts → {rv.status_code}')
    assert rv.status_code == 401

# 13. Admin-HTML renders (no auth check in route handler)
with flask_app.test_client() as c:
    rv = c.get('/admin')
    print(f'13. GET /admin → {rv.status_code} (len={len(rv.data)})')
    assert rv.status_code == 200
    # Some key markers present?
    html = rv.data.decode('utf-8')
    assert 'Admin-Login' in html
    assert '/admin/api/login' in html
    assert '/admin/api/accounts' in html

# 14. Rate-limit (pummel with bad passwords)
print('\n14. Rate-limit check (10 bad logins)')
with flask_app.test_client() as c:
    hit_limit = False
    for i in range(12):
        rv = c.post('/admin/api/login', json={'username': 'admin-test', 'password': 'x'})
        if rv.status_code == 429:
            print(f'   Hit rate-limit at attempt {i+1}')
            hit_limit = True
            break
    if not hit_limit:
        print('   ⚠ Kein Rate-Limit nach 12 Versuchen — ist das erwartet?')

print('\n=== ALL PHASE 6 TESTS PASSED ✅ ===')
