"""Shared file-persistence helpers for UGC uploads.

Stored under MEDIA_ROOT/project_<pk>/... mirroring the avatar layout
(avatars/user_<id>/picture.jpeg). Draft uploads live under
MEDIA_ROOT/uploads/user_<id>/ until a project adopts them. All files are
indexed in FileIndex and served only through the gated file_request endpoint.
"""

import hashlib
import os
import uuid

from django.core.files.base import ContentFile
from django.core.files.storage import default_storage

from .models import FileIndex


def _compute_md5_size(uploaded):
    md5 = hashlib.md5()
    size = 0
    uploaded.seek(0)
    while True:
        chunk = uploaded.read(1024 * 1024)
        if not chunk:
            break
        md5.update(chunk)
        size += len(chunk)
    uploaded.seek(0)
    return md5.hexdigest(), size


def store_file(owner, kind, uploaded, project_pk, subdir='', original_filename=''):
    """Persist an uploaded file and index it.

    Returns the FileIndex row. ``subdir`` is e.g. 'versions' or 'gallery'.
    """
    name = getattr(uploaded, 'name', '') or original_filename or 'file'
    content_type = getattr(uploaded, 'content_type', '') or 'application/octet-stream'
    md5, size = _compute_md5_size(uploaded)

    ext = os.path.splitext(name)[1] or ''
    if subdir:
        stored_path = f'project_{project_pk}/{subdir}/{uuid.uuid4().hex}{ext}'
    else:
        stored_path = f'project_{project_pk}/{uuid.uuid4().hex}{ext}'

    actual_path = default_storage.save(stored_path, ContentFile(uploaded.read()))
    return FileIndex.objects.create(
        owner=owner,
        kind=kind,
        stored_path=actual_path,
        original_filename=name,
        content_type=content_type,
        size=size,
        md5=md5,
    )


def store_temp_file(owner, kind, uploaded):
    """Persist a draft upload under uploads/user_<id>/ and return its FileIndex."""
    name = getattr(uploaded, 'name', '') or 'file'
    content_type = getattr(uploaded, 'content_type', '') or 'application/octet-stream'
    md5, size = _compute_md5_size(uploaded)

    ext = os.path.splitext(name)[1] or ''
    stored_path = f'uploads/user_{owner.pk}/{uuid.uuid4().hex}{ext}'
    actual_path = default_storage.save(stored_path, ContentFile(uploaded.read()))
    return FileIndex.objects.create(
        owner=owner,
        kind=f'temp_{kind}',
        stored_path=actual_path,
        original_filename=name,
        content_type=content_type,
        size=size,
        md5=md5,
    )


def move_file_index(file_index, new_stored_path, kind=None):
    """Move an indexed file to a new stored path (chunked copy + delete old),
    updating the index. Used when a project adopts a temp upload."""
    if file_index.stored_path and file_index.stored_path != new_stored_path:
        with default_storage.open(file_index.stored_path, 'rb') as src:
            actual_path = default_storage.save(new_stored_path, src)
        if actual_path != file_index.stored_path and default_storage.exists(file_index.stored_path):
            default_storage.delete(file_index.stored_path)
        file_index.stored_path = actual_path
    if kind:
        file_index.kind = kind
    file_index.save(update_fields=['stored_path'] if not kind else ['stored_path', 'kind'])
    return file_index


def refresh_file_index(file_index):
    """Recompute size + md5 from the stored file. Needed after an in-place
    rewrite (e.g. injection of animationframework metadata) so downloads keep
    a correct Content-Length."""
    if not file_index.stored_path or not default_storage.exists(file_index.stored_path):
        return file_index
    with default_storage.open(file_index.stored_path, 'rb') as src:
        md5 = hashlib.md5()
        size = 0
        for chunk in iter(lambda: src.read(1024 * 1024), b''):
            md5.update(chunk)
            size += len(chunk)
    file_index.size = size
    file_index.md5 = md5.hexdigest()
    file_index.save(update_fields=['size', 'md5'])
    return file_index


def delete_file_index(file_index):
    """Remove an indexed file from disk and DB (safe no-op when None)."""
    if file_index is None:
        return
    if file_index.stored_path and default_storage.exists(file_index.stored_path):
        default_storage.delete(file_index.stored_path)
    file_index.delete()
