"""Tests for vendor-model photo upload, replace, delete, and serve.

Covers the core model-image endpoints: admin-gated upload/delete, the public
serve route, one-image-per-model replace semantics, and the guarantee that
deleting a model whose imageurl is an external URL clears the field without
touching the filesystem.
"""

import io
import os

from shopdb.core.models import Model
from shopdb.core.api.models import MODEL_IMAGE_URL_PREFIX


def _make_model(db, imageurl=None):
    m = Model(modelnumber='TESTMODEL-1', imageurl=imageurl)
    db.session.add(m)
    db.session.commit()
    return m


def test_upload_forbidden_for_non_admin(client, db, member_headers):
    """A role-less authenticated user cannot upload a model image."""
    m = _make_model(db)
    data = {'file': (io.BytesIO(b'<svg/>'), 'photo.svg')}
    resp = client.post(f'/api/models/{m.modelnumberid}/image', 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_imageurl_and_file_exists(client, db, auth_headers, app):
    """Admin upload writes the served URL and the file lands in the instance dir."""
    m = _make_model(db)
    payload = b'\x89PNG\r\n\x1a\nfake-png-bytes'
    data = {'file': (io.BytesIO(payload), 'photo.png')}
    resp = client.post(f'/api/models/{m.modelnumberid}/image', data=data,
                       content_type='multipart/form-data', headers=auth_headers)
    assert resp.status_code == 200, resp.get_json()

    expected_url = f'{MODEL_IMAGE_URL_PREFIX}model-{m.modelnumberid}.png'
    assert resp.get_json()['data']['imageurl'] == expected_url

    refreshed = db.session.get(Model, m.modelnumberid)
    assert refreshed.imageurl == expected_url

    path = os.path.join(app.instance_path, 'modelimages', f'model-{m.modelnumberid}.png')
    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."""
    m = _make_model(db)
    data = {'file': (io.BytesIO(b'MZ...'), 'photo.exe')}
    resp = client.post(f'/api/models/{m.modelnumberid}/image', 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_model_is_404(client, db, auth_headers):
    """Uploading to a nonexistent model id is a 404."""
    data = {'file': (io.BytesIO(b'<svg/>'), 'photo.svg')}
    resp = client.post('/api/models/999999/image', 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."""
    m = _make_model(db)
    first = {'file': (io.BytesIO(b'first'), 'photo.png')}
    resp = client.post(f'/api/models/{m.modelnumberid}/image', data=first,
                       content_type='multipart/form-data', headers=auth_headers)
    assert resp.status_code == 200
    oldpath = os.path.join(app.instance_path, 'modelimages', f'model-{m.modelnumberid}.png')
    assert os.path.exists(oldpath)

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

    # Old .png is gone; new .jpg exists and imageurl points at it.
    assert not os.path.exists(oldpath)
    newpath = os.path.join(app.instance_path, 'modelimages', f'model-{m.modelnumberid}.jpg')
    assert os.path.exists(newpath)
    refreshed = db.session.get(Model, m.modelnumberid)
    assert refreshed.imageurl == f'{MODEL_IMAGE_URL_PREFIX}model-{m.modelnumberid}.jpg'


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

    served = client.get(f'{MODEL_IMAGE_URL_PREFIX}model-{m.modelnumberid}.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 imageurl and removes the owned file."""
    m = _make_model(db)
    data = {'file': (io.BytesIO(b'bytes'), 'photo.png')}
    client.post(f'/api/models/{m.modelnumberid}/image', data=data,
                content_type='multipart/form-data', headers=auth_headers)
    path = os.path.join(app.instance_path, 'modelimages', f'model-{m.modelnumberid}.png')
    assert os.path.exists(path)

    resp = client.delete(f'/api/models/{m.modelnumberid}/image', headers=auth_headers)
    assert resp.status_code == 200
    assert resp.get_json()['data']['imageurl'] is None
    assert not os.path.exists(path)

    refreshed = db.session.get(Model, m.modelnumberid)
    assert refreshed.imageurl is None


def test_delete_external_url_clears_field_without_filesystem_error(client, db, auth_headers):
    """Delete on a model whose imageurl is an external URL just clears the field."""
    m = _make_model(db, imageurl='https://example.com/product.png')
    resp = client.delete(f'/api/models/{m.modelnumberid}/image', headers=auth_headers)
    assert resp.status_code == 200
    assert resp.get_json()['data']['imageurl'] is None

    refreshed = db.session.get(Model, m.modelnumberid)
    assert refreshed.imageurl is None


def test_delete_legacy_path_clears_field_without_filesystem_error(client, db, auth_headers):
    """Delete on a shipped /images/models/* path clears the field, touches no disk."""
    m = _make_model(db, imageurl='/images/models/machines/legacy.png')
    resp = client.delete(f'/api/models/{m.modelnumberid}/image', headers=auth_headers)
    assert resp.status_code == 200
    assert resp.get_json()['data']['imageurl'] is None


def test_delete_forbidden_for_non_admin(client, db, member_headers):
    """A role-less authenticated user cannot delete a model image."""
    m = _make_model(db, imageurl='https://example.com/product.png')
    resp = client.delete(f'/api/models/{m.modelnumberid}/image', headers=member_headers)
    assert resp.status_code == 403
