from __future__ import annotations

import logging
from typing import Any

from django.db.models import QuerySet, Q
from django.http import FileResponse
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import parsers, permissions, status, viewsets
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from apps.books.models import Book, BookChapter, DownloadRecord, EBook, ReadingProgress
from apps.books.serializers import (
    BookChapterSerializer, BookDetailSerializer, BookListSerializer, BookReadingSettingsSerializer,
    BookSerializer, DownloadRecordSerializer, EBookContentSerializer, EBookDetailSerializer,
    EBookListSerializer, EBookTocSerializer, EBookUploadSerializer,
    ReadingProgressSerializer, StorageSummarySerializer,
)
from apps.reader.models import ReadingSettings
from apps.books.services.metadata import enrich_ebook_metadata

logger = logging.getLogger(__name__)


class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    permission_classes = [IsAuthenticated]
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = ["author", "genre", "reading_status"]
    search_fields = ["title", "author", "genre"]
    ordering_fields = ["title", "author", "genre", "created_at"]
    ordering = ["title"]

    def get_serializer_class(self):
        if self.action == "retrieve":
            return BookDetailSerializer
        if self.action == "list":
            return BookListSerializer
        return BookSerializer

    def get_queryset(self) -> QuerySet[Book]:
        qs = super().get_queryset()
        query = self.request.query_params.get("q", "").strip()
        if query:
            qs = qs.filter(Q(title__icontains=query) | Q(author__icontains=query) | Q(genre__icontains=query))
        return qs

    @action(detail=False, methods=["get"], permission_classes=[AllowAny])
    def genres(self, request: Request) -> Response:
        genre_list = Book.objects.values_list("genre", flat=True).distinct().order_by("genre")
        return Response([g for g in genre_list if g])

    @action(detail=False, methods=["get"], permission_classes=[AllowAny])
    def authors(self, request: Request) -> Response:
        author_list = Book.objects.values_list("author", flat=True).distinct().order_by("author")
        return Response([a for a in author_list if a])

    @action(detail=False, methods=["get"])
    def storage(self, request: Request) -> Response:
        """Return storage usage summary for the current user."""
        download_records = DownloadRecord.objects.filter(user=request.user).select_related("ebook")
        total_size = sum(r.file_size for r in download_records)
        ebook_list = [
            {"id": r.ebook.id, "title": r.ebook.title, "file_size": r.file_size}
            for r in download_records
        ]
        serializer = StorageSummarySerializer(data={
            "total_downloads": download_records.count(),
            "total_size_bytes": total_size,
            "ebooks": ebook_list,
        })
        serializer.is_valid(raise_exception=True)
        return Response(serializer.data)


class IsEBookOwner(permissions.BasePermission):
    def has_object_permission(self, request: Request, view: object, obj: EBook) -> bool:
        return obj.user == request.user


class EBookViewSet(viewsets.ModelViewSet):
    parser_classes = [parsers.MultiPartParser, parsers.FormParser, parsers.JSONParser]
    permission_classes = [IsAuthenticated, IsEBookOwner]

    def get_serializer_class(self):
        if self.action == "create":
            return EBookUploadSerializer
        if self.action in ("list",):
            return EBookListSerializer
        if self.action in ("toc",):
            return BookChapterSerializer
        return EBookDetailSerializer

    def get_queryset(self):
        return EBook.objects.filter(user=self.request.user).select_related("reading_progress", "user")

    @action(detail=True, methods=["get", "patch"])
    def progress(self, request: Request, pk: int | None = None) -> Response:
        ebook = self.get_object()
        progress_obj, _created = ReadingProgress.objects.get_or_create(user=request.user, ebook=ebook)
        if request.method == "GET":
            serializer = ReadingProgressSerializer(progress_obj)
            return Response(serializer.data)
        serializer = ReadingProgressSerializer(progress_obj, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

    @action(detail=True, methods=["post"], url_path="enrich-metadata")
    def enrich_metadata(self, request: Request, pk: int | None = None) -> Response:
        """Re-fetch Open Library metadata and cover for this ebook."""
        ebook = self.get_object()
        try:
            enrich_ebook_metadata(ebook)
        except Exception:
            logger.exception("Manual metadata enrichment failed for ebook %s", ebook.id)
            return Response(
                {"error": "Metadata enrichment failed."},
                status=status.HTTP_502_BAD_GATEWAY,
            )
        ebook.refresh_from_db()
        serializer = EBookDetailSerializer(ebook, context={"request": request})
        return Response(serializer.data)

    @action(detail=True, methods=["post"])
    def process(self, request: Request, pk: int | None = None) -> Response:
        """Trigger e-book processing: metadata extraction, TOC building, page counting."""
        ebook = self.get_object()
        if not ebook.file:
            return Response({"error": "No file found for this e-book."}, status=status.HTTP_400_BAD_REQUEST)

        try:
            from apps.books.services.process_ebook import apply_processing_to_ebook

            result = apply_processing_to_ebook(ebook)
            return Response(result)
        except Exception as exc:
            logger.exception("Failed to process ebook %s", ebook.id)
            return Response({"error": f"Processing failed: {exc}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    @action(detail=True, methods=["get"])
    def file(self, request: Request, pk: int | None = None) -> FileResponse | Response:
        """Stream the EPUB file for authenticated in-browser reading."""
        ebook = self.get_object()
        if ebook.format != "epub":
            return Response(
                {"error": "Reader supports EPUB only."},
                status=status.HTTP_400_BAD_REQUEST,
            )
        if not ebook.file:
            return Response({"error": "No file found for this e-book."}, status=status.HTTP_400_BAD_REQUEST)
        return FileResponse(
            ebook.file.open("rb"),
            content_type="application/epub+zip",
            filename=ebook.filename(),
        )

    @action(detail=True, methods=["get"])
    def toc(self, request: Request, pk: int | None = None) -> Response:
        """Return hierarchical table of contents."""
        ebook = self.get_object()
        chapters = BookChapter.objects.filter(ebook=ebook).order_by("index").select_related("ebook")
        serializer = BookChapterSerializer(chapters, many=True)
        return Response({
            "chapters": serializer.data,
            "format": ebook.format,
            "page_count": ebook.page_count,
        })

    @action(detail=True, methods=["get"])
    def content(self, request: Request, pk: int | None = None) -> Response:
        """Return paginated content for a given page number.

        Query params:
            page (int): page/chapter index to fetch (1-indexed, default: 1)
        """
        ebook = self.get_object()
        page = max(1, int(request.query_params.get("page", 1)))

        chapters = list(BookChapter.objects.filter(ebook=ebook).order_by("index").select_related("ebook"))
        total_pages = len(chapters) or ebook.page_count or 1

        chapter: BookChapter | None = None
        chapter_title = ""
        content_html = ""

        if chapters and 0 <= page - 1 < len(chapters):
            ch = chapters[page - 1]
            chapter_title = ch.title
            content_html = _fetch_chapter_content(ebook, ch)

        serializer = EBookContentSerializer(data={
            "page": page,
            "total_pages": total_pages,
            "content": content_html,
            "chapter_title": chapter_title,
            "format": ebook.format,
        })
        serializer.is_valid(raise_exception=True)
        return Response(serializer.data)

    @action(detail=True, methods=["post"])
    def download(self, request: Request, pk: int | None = None) -> Response:
        """Track download of an e-book. Creates a DownloadRecord and returns file info."""
        ebook = self.get_object()
        if not ebook.file:
            return Response({"error": "No file found for this e-book."}, status=status.HTTP_400_BAD_REQUEST)

        download, created = DownloadRecord.objects.get_or_create(
            user=request.user,
            ebook=ebook,
            defaults={"file_size": ebook.file.size if ebook.file else 0},
        )
        if not created:
            download.file_size = ebook.file.size if ebook.file else 0
            download.save(update_fields=["file_size"])

        serializer = DownloadRecordSerializer(download, context={"request": request})
        return Response(serializer.data, status=status.HTTP_200_OK)

    @action(detail=False, methods=["get"])
    def downloads(self, request: Request) -> Response:
        """List all e-books the current user has downloaded."""
        records = DownloadRecord.objects.filter(user=request.user).select_related(
            "ebook", "ebook__reading_progress"
        ).prefetch_related("ebook__chapters")
        page = self.paginate_queryset(records)
        if page is not None:
            serializer = DownloadRecordSerializer(page, many=True, context={"request": request})
            return self.get_paginated_response(serializer.data)
        serializer = DownloadRecordSerializer(records, many=True, context={"request": request})
        return Response(serializer.data)

    @action(detail=False, methods=["delete"], url_path="downloads/(?P<download_pk>[^/.]+)")
    def delete_download(self, request: Request, download_pk: str | None = None) -> Response:
        """Delete a download record."""
        try:
            download = DownloadRecord.objects.get(pk=download_pk, user=request.user)
        except DownloadRecord.DoesNotExist:
            return Response({"error": "Download record not found."}, status=status.HTTP_404_NOT_FOUND)
        download.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


def _fetch_chapter_content(ebook: EBook, chapter: BookChapter) -> str:
    """Fetch HTML content for a chapter from the e-book file."""
    if ebook.format == "epub":
        return _fetch_epub_chapter_content(ebook.file.path, chapter)
    return ""


def _fetch_epub_chapter_content(file_path: str, chapter: BookChapter) -> str:
    """Extract HTML content of a specific EPUB chapter by href."""
    try:
        from ebooklib import epub
        from bs4 import BeautifulSoup
    except ImportError:
        return ""

    try:
        book = epub.read_epub(file_path)
        href = chapter.href or ""
        # Find the item by href
        for item in book.get_items():
            item_name = item.get_name() or ""
            if href and (item_name.endswith(href) or href.endswith(item_name)):
                content = item.get_content()
                soup = BeautifulSoup(content, "html.parser")
                # Clean up — remove body/html/head wrappers, keep inner content
                body = soup.find("body")
                if body:
                    return str(body)
                return str(soup)
        return ""
    except Exception:
        logger.exception("Failed to fetch EPUB chapter content for %s", chapter.href)
        return ""


@api_view(["GET", "PATCH"])
@permission_classes([IsAuthenticated])
def book_reading_settings_view(request: Request) -> Response:
    """Get or update reading settings via the books API contract."""
    settings, _created = ReadingSettings.objects.get_or_create(user=request.user)

    if request.method == "GET":
        serializer = BookReadingSettingsSerializer(settings)
        return Response(serializer.data)

    serializer = BookReadingSettingsSerializer(settings, data=request.data, partial=True)
    serializer.is_valid(raise_exception=True)
    serializer.save()
    return Response(serializer.data)