"""Tests for self-hosted employee photo upload, replace, delete, serve, and the
shared photo-URL resolver used by both EmployeeDetail and the kiosk cards.

Covers: admin-gated upload/delete, the public serve route, one-photo-per-person
replace semantics, external-mode 409s, and resolve_employee_photo_url in both
directory modes. The wjf_employees HR DB is not available under test, so external
resolution is exercised by passing the Picture value directly (no query)."""

import io
import os

from shopdb.core.models import Setting
from plugins.employees.models import DirectoryEmployee
from plugins.employees.api.routes import (
    resolve_employee_photo_url,
    EMPLOYEE_PHOTO_URL_PREFIX,
    EMPLOYEE_PHOTO_STATIC_PREFIX,
)


def _set_mode(db, mode):
    row = Setting.query.filter_by(key='employee_directory_mode').first()
    if row:
        row.value = mode
    else:
        db.session.add(Setting(key='employee_directory_mode', value=mode,
                               valuetype='string', category='site'))
    db.session.commit()


def _make_employee(db, sso=210000001):
    emp = DirectoryEmployee(sso=sso, firstname='Test', lastname='Person')
    db.session.add(emp)
    db.session.commit()
    return emp


# --- upload / replace / delete (self-hosted) --------------------------------

def test_upload_forbidden_for_non_admin(client, db, member_headers):
    """A role-less authenticated user cannot upload an employee photo."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    data = {'file': (io.BytesIO(b'bytes'), 'p.png')}
    resp = client.post(f'/api/employees/{emp.sso}/photo', data=data,
                       content_type='multipart/form-data', headers=member_headers)
    assert resp.status_code == 403
    assert resp.get_json()['data']['error']['code'] == 'FORBIDDEN'


def test_upload_sets_photofilename_and_file_exists(client, db, auth_headers, app):
    """Admin upload writes photofilename + photourl and lands the file on disk."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    payload = b'\x89PNG\r\n\x1a\nfake-png'
    data = {'file': (io.BytesIO(payload), 'p.png')}
    resp = client.post(f'/api/employees/{emp.sso}/photo', data=data,
                       content_type='multipart/form-data', headers=auth_headers)
    assert resp.status_code == 200, resp.get_json()

    filename = f'photo-{emp.sso}.png'
    body = resp.get_json()['data']
    assert body['photofilename'] == filename
    assert body['photourl'] == f'{EMPLOYEE_PHOTO_URL_PREFIX}{filename}'

    refreshed = db.session.get(DirectoryEmployee, emp.sso)
    assert refreshed.photofilename == filename

    path = os.path.join(app.instance_path, 'employeephotos', filename)
    assert os.path.exists(path)
    with open(path, 'rb') as handle:
        assert handle.read() == payload


def test_upload_rejects_invalid_extension(client, db, auth_headers):
    """A disallowed file extension is rejected."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    data = {'file': (io.BytesIO(b'MZ'), 'p.exe')}
    resp = client.post(f'/api/employees/{emp.sso}/photo', data=data,
                       content_type='multipart/form-data', headers=auth_headers)
    assert resp.status_code == 400
    assert resp.get_json()['data']['error']['code'] == 'VALIDATION_ERROR'


def test_upload_missing_employee_is_404(client, db, auth_headers):
    """Uploading to a nonexistent SSO is a 404."""
    _set_mode(db, 'selfhosted')
    data = {'file': (io.BytesIO(b'x'), 'p.png')}
    resp = client.post('/api/employees/999999/photo', data=data,
                       content_type='multipart/form-data', headers=auth_headers)
    assert resp.status_code == 404


def test_reupload_replaces_and_removes_old_extension(client, db, auth_headers, app):
    """Re-upload with a different extension deletes the prior file."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    first = {'file': (io.BytesIO(b'first'), 'p.png')}
    client.post(f'/api/employees/{emp.sso}/photo', data=first,
                content_type='multipart/form-data', headers=auth_headers)
    oldpath = os.path.join(app.instance_path, 'employeephotos', f'photo-{emp.sso}.png')
    assert os.path.exists(oldpath)

    second = {'file': (io.BytesIO(b'second'), 'p.jpg')}
    resp = client.post(f'/api/employees/{emp.sso}/photo', data=second,
                       content_type='multipart/form-data', headers=auth_headers)
    assert resp.status_code == 200

    assert not os.path.exists(oldpath)
    newpath = os.path.join(app.instance_path, 'employeephotos', f'photo-{emp.sso}.jpg')
    assert os.path.exists(newpath)
    refreshed = db.session.get(DirectoryEmployee, emp.sso)
    assert refreshed.photofilename == f'photo-{emp.sso}.jpg'


def test_serve_returns_bytes(client, db, auth_headers):
    """The public serve route returns the uploaded bytes without auth."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    payload = b'\x89PNG\r\n\x1a\nserved'
    data = {'file': (io.BytesIO(payload), 'p.png')}
    client.post(f'/api/employees/{emp.sso}/photo', data=data,
                content_type='multipart/form-data', headers=auth_headers)

    served = client.get(f'{EMPLOYEE_PHOTO_URL_PREFIX}photo-{emp.sso}.png')
    assert served.status_code == 200
    assert served.get_data() == payload


def test_delete_clears_field_and_removes_file(client, db, auth_headers, app):
    """Delete clears photofilename and removes the uploaded file."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    data = {'file': (io.BytesIO(b'bytes'), 'p.png')}
    client.post(f'/api/employees/{emp.sso}/photo', data=data,
                content_type='multipart/form-data', headers=auth_headers)
    path = os.path.join(app.instance_path, 'employeephotos', f'photo-{emp.sso}.png')
    assert os.path.exists(path)

    resp = client.delete(f'/api/employees/{emp.sso}/photo', headers=auth_headers)
    assert resp.status_code == 200
    assert resp.get_json()['data']['photourl'] is None
    assert not os.path.exists(path)

    refreshed = db.session.get(DirectoryEmployee, emp.sso)
    assert refreshed.photofilename is None


def test_delete_forbidden_for_non_admin(client, db, member_headers):
    """A role-less authenticated user cannot delete an employee photo."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    resp = client.delete(f'/api/employees/{emp.sso}/photo', headers=member_headers)
    assert resp.status_code == 403


# --- external-mode 409 ------------------------------------------------------

def test_upload_conflict_in_external_mode(client, db, auth_headers):
    """Upload is a 409 when the directory is external (HR owns the photo)."""
    _set_mode(db, 'external')
    data = {'file': (io.BytesIO(b'x'), 'p.png')}
    resp = client.post('/api/employees/210000001/photo', data=data,
                       content_type='multipart/form-data', headers=auth_headers)
    assert resp.status_code == 409
    assert resp.get_json()['data']['error']['code'] == 'CONFLICT'


def test_delete_conflict_in_external_mode(client, db, auth_headers):
    """Delete is a 409 when the directory is external."""
    _set_mode(db, 'external')
    resp = client.delete('/api/employees/210000001/photo', headers=auth_headers)
    assert resp.status_code == 409


# --- resolver ---------------------------------------------------------------

def test_resolver_selfhosted_returns_upload_url_or_none(client, db, auth_headers):
    """Self-hosted: served upload URL when a photo exists, else None."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    assert resolve_employee_photo_url(emp.sso) is None

    data = {'file': (io.BytesIO(b'x'), 'p.png')}
    client.post(f'/api/employees/{emp.sso}/photo', data=data,
                content_type='multipart/form-data', headers=auth_headers)
    assert resolve_employee_photo_url(emp.sso) == f'{EMPLOYEE_PHOTO_URL_PREFIX}photo-{emp.sso}.png'


def test_resolver_external_prefixes_relative_and_passes_urls(db):
    """External: relative HR paths get the static prefix; absolute URLs pass through."""
    _set_mode(db, 'external')
    assert resolve_employee_photo_url(210000001, 'Support/210000001.png') == \
        f'{EMPLOYEE_PHOTO_STATIC_PREFIX}Support/210000001.png'
    assert resolve_employee_photo_url(210000001, 'https://hr.example.net/p.png') == \
        'https://hr.example.net/p.png'
    assert resolve_employee_photo_url(210000001, None) is None
    assert resolve_employee_photo_url(None) is None


def test_lookup_includes_photourl(client, db, auth_headers):
    """The lookup serializer carries the resolved photourl (self-hosted)."""
    _set_mode(db, 'selfhosted')
    emp = _make_employee(db)
    data = {'file': (io.BytesIO(b'x'), 'p.png')}
    client.post(f'/api/employees/{emp.sso}/photo', data=data,
                content_type='multipart/form-data', headers=auth_headers)

    resp = client.get(f'/api/employees/lookup/{emp.sso}')
    assert resp.status_code == 200
    assert resp.get_json()['data']['photourl'] == f'{EMPLOYEE_PHOTO_URL_PREFIX}photo-{emp.sso}.png'
