import json
import mimetypes
import os
import secrets
import logging
from pathlib import Path

from django.conf import settings
from django.db.models import F
from django.http import FileResponse, JsonResponse, Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.dateparse import parse_datetime
from django.views import View

from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.response import Response

from .models import FileUpload
from .serializers import FileUploadSerializer

logger = logging.getLogger(__name__)


def _get_upload_folder():
    folder = settings.FILE_UPLOADS_FOLDER
    Path(folder).mkdir(parents=True, exist_ok=True)
    return folder


def _save_uploaded_file(f):
    """Save an in-memory upload to FILE_UPLOADS_FOLDER and return a FileUpload instance."""
    folder = _get_upload_folder()
    ext = Path(f.name).suffix.lower()
    stored_name = f"{secrets.token_hex(16)}{ext}"
    dest_path = os.path.join(folder, stored_name)
    with open(dest_path, 'wb') as dst:
        for chunk in f.chunks():
            dst.write(chunk)
    mime_type = f.content_type or mimetypes.guess_type(f.name)[0] or 'application/octet-stream'
    return FileUpload.objects.create(
        name=f.name,
        stored_name=stored_name,
        mime_type=mime_type,
        size=f.size,
    )


# ── UI Views ──────────────────────────────────────────────────────────────────

class FileListView(View):
    def get(self, request):
        files = FileUpload.objects.all()
        return render(request, 'links/files/list.html', {'files': files})


class FileUploadView(View):
    def post(self, request):
        is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
        uploaded = request.FILES.getlist('files')
        if not uploaded:
            if is_ajax:
                return JsonResponse({'error': 'No files provided'}, status=400)
            return redirect('file-list')
        results = []
        for f in uploaded:
            record = _save_uploaded_file(f)
            results.append({'id': str(record.pk), 'name': record.name, 'size': record.size})
        if is_ajax:
            return JsonResponse({'uploaded': results})
        return redirect('file-list')


class FileDownloadView(View):
    def get(self, request, pk, filename=''):
        record = get_object_or_404(FileUpload, pk=pk)
        if not os.path.exists(record.file_path):
            raise Http404("File not found on disk")
        FileUpload.objects.filter(pk=pk).update(download_count=F('download_count') + 1)
        disposition = 'inline' if record.is_image else f'attachment; filename="{record.name}"'
        response = FileResponse(open(record.file_path, 'rb'), content_type=record.mime_type)
        response['Content-Disposition'] = disposition
        return response


class FileDeleteView(View):
    def post(self, request, pk):
        record = get_object_or_404(FileUpload, pk=pk)
        if os.path.exists(record.file_path):
            os.remove(record.file_path)
        record.delete()
        return redirect('file-list')


class FileTogglePublicView(View):
    def post(self, request, pk):
        record = get_object_or_404(FileUpload, pk=pk)
        if record.is_public:
            record.is_public = False
            record.save(update_fields=['is_public', 'updated_at'])
            return JsonResponse({'is_public': False, 'public_url': None})
        record.is_public = True
        record.save(update_fields=['is_public', 'updated_at'])
        return JsonResponse({
            'is_public': True,
            'public_url': record.public_url,
        })


class FileSetExpiryView(View):
    def post(self, request, pk):
        record = get_object_or_404(FileUpload, pk=pk)
        try:
            data = json.loads(request.body)
        except (json.JSONDecodeError, ValueError):
            return JsonResponse({'error': 'Invalid JSON'}, status=400)
        expires_at = data.get('expires_at')
        if expires_at:
            dt = parse_datetime(expires_at)
            if not dt:
                return JsonResponse({'error': 'Invalid datetime format. Use ISO 8601.'}, status=400)
            record.expires_at = dt
        else:
            record.expires_at = None
        record.save(update_fields=['expires_at', 'updated_at'])
        return JsonResponse({
            'expires_at': record.expires_at.isoformat() if record.expires_at else None,
            'is_expired': record.is_expired,
        })


class PublicFileView(View):
    def get(self, request, pk, filename=''):
        record = get_object_or_404(FileUpload, pk=pk, is_public=True)
        if record.is_expired:
            raise Http404("This public link has expired")
        if not os.path.exists(record.file_path):
            raise Http404("File not found")
        FileUpload.objects.filter(pk=record.pk).update(download_count=F('download_count') + 1)
        disposition = 'inline' if record.is_image else f'attachment; filename="{record.name}"'
        response = FileResponse(open(record.file_path, 'rb'), content_type=record.mime_type)
        response['Content-Disposition'] = disposition
        return response


# ── REST API ViewSet ──────────────────────────────────────────────────────────

class FileUploadViewSet(viewsets.ModelViewSet):
    queryset = FileUpload.objects.all()
    serializer_class = FileUploadSerializer
    parser_classes = [MultiPartParser, FormParser, JSONParser]
    http_method_names = ['get', 'post', 'delete', 'head', 'options']

    def create(self, request, *args, **kwargs):
        uploaded = request.FILES.getlist('files')
        if not uploaded:
            return Response({'error': 'No files provided. Use files[] field.'}, status=status.HTTP_400_BAD_REQUEST)
        created = [_save_uploaded_file(f) for f in uploaded]
        serializer = self.get_serializer(created, many=True)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    def destroy(self, request, *args, **kwargs):
        record = self.get_object()
        if os.path.exists(record.file_path):
            os.remove(record.file_path)
        record.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

    @action(detail=True, methods=['post'], url_path='toggle-public')
    def toggle_public(self, request, pk=None):
        record = self.get_object()
        if record.is_public:
            record.is_public = False
            record.save(update_fields=['is_public', 'updated_at'])
            return Response({'is_public': False, 'public_url': None})
        record.is_public = True
        record.save(update_fields=['is_public', 'updated_at'])
        return Response({
            'is_public': True,
            'public_url': record.public_url,
        })

    @action(detail=True, methods=['post'], url_path='set-expiry')
    def set_expiry(self, request, pk=None):
        record = self.get_object()
        expires_at = request.data.get('expires_at')
        if expires_at:
            dt = parse_datetime(str(expires_at))
            if not dt:
                return Response({'error': 'Invalid datetime. Use ISO 8601.'}, status=status.HTTP_400_BAD_REQUEST)
            record.expires_at = dt
        else:
            record.expires_at = None
        record.save(update_fields=['expires_at', 'updated_at'])
        return Response(self.get_serializer(record).data)

    @action(detail=True, methods=['get'])
    def download(self, request, pk=None):
        record = self.get_object()
        if not os.path.exists(record.file_path):
            raise Http404("File not found")
        FileUpload.objects.filter(pk=pk).update(download_count=F('download_count') + 1)
        disposition = 'inline' if record.is_image else f'attachment; filename="{record.name}"'
        response = FileResponse(open(record.file_path, 'rb'), content_type=record.mime_type)
        response['Content-Disposition'] = disposition
        return response


def import_image_view(request, image_url):
    """Proxy-and-cache an external image via /import/images/<path:image_url>.

    The image_url path component has no scheme (e.g. 'example.com/path/img.jpg').
    On first request the view:
      1. Creates a FileUpload stub in the database (is_public=True, source_url set).
      2. Fires a background thread to download and save the file.
      3. Immediately redirects to the original URL so the image is visible right away.
    On subsequent requests, once the file is saved locally, the view redirects to
    the stored public URL instead — no more dependency on the original host.

    If the background download fails, the stub record is deleted automatically so no
    size=0 ghost appears in the file list. The next visit to the same URL will create
    a fresh stub and retry. A periodic APScheduler task (`retry_stuck_image_imports`)
    also reschedules any stubs left behind by killed threads.
    """
    import hashlib
    import posixpath
    from threading import Thread
    from .tasks import download_and_save_image

    # Build the canonical source URL, preserving query string.
    # Django's <path:> converter only captures the path component; query params
    # like ?format=jpg&name=900x900 end up in QUERY_STRING and must be re-attached.
    query_string = request.META.get('QUERY_STRING', '')
    source_url = f'https://{image_url}'
    if query_string:
        source_url = f'{source_url}?{query_string}'

    url_hash = hashlib.sha256(source_url.encode()).hexdigest()[:20]
    # Strip query string for filename derivation
    filename = posixpath.basename(image_url.split('?')[0]) or f'image_{url_hash}'

    # Derive extension from filename; fall back to .jpg for bare names
    _, ext = posixpath.splitext(filename)
    if not ext:
        ext = '.jpg'
        filename = f'{filename}{ext}'

    stored_name = f'import_{url_hash}{ext}'

    # Try to find an existing record for this URL (idempotent)
    record = FileUpload.objects.filter(source_url=source_url).first()

    if record is None:
        # Create the stub immediately so we have a stable public URL
        record = FileUpload.objects.create(
            name=filename,
            stored_name=stored_name,
            mime_type=f'image/{ext.lstrip(".") or "jpeg"}',
            size=0,
            is_public=True,
            source_url=source_url,
        )
        logger.info(f"import_image_view: created FileUpload {record.pk} for {source_url}")

    # If the file is already on disk, serve from local storage
    if os.path.exists(record.file_path):
        return redirect(record.public_url)

    # File not yet saved — kick off (or re-kick) the background download
    thread = Thread(target=download_and_save_image, args=(str(record.pk),), daemon=True)
    thread.start()

    # Redirect to the original URL as a temporary placeholder while download runs
    return redirect(source_url)
