"""Views for the Bookmark feature — REST viewset + UI list view."""
from collections import OrderedDict

from django.views.generic import ListView, DetailView
from django.db import models
from django.utils import timezone
from rest_framework import viewsets, status, filters as drf_filters
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination

from .models import Bookmark
from .serializers import (
    BookmarkSerializer,
    BookmarkImportSerializer,
    compute_bookmark_content_hash,
)


class BookmarkPagination(PageNumberPagination):
    page_size = 24
    page_size_query_param = 'page_size'
    max_page_size = 200


class BookmarkViewSet(viewsets.ModelViewSet):
    """
    CRUD for archived bookmarks, plus bulk import + filterable listing.

    List filters (query params):
        q            full-text search on text/author/summary
        has_video    true/false
        has_photo    true/false
        has_card     true/false
        is_quote     true/false
        is_retweet   true/false
        author       exact screen name
    Bulk import:  POST /api/bookmarks/import/  body {"bookmarks": [ {...}, ... ]}
    Bulk upsert:  PUT /api/bookmarks/bulk/     body list of bookmark dicts (key: tweet_id)
    """
    queryset = Bookmark.objects.all()
    serializer_class = BookmarkSerializer
    pagination_class = BookmarkPagination
    filter_backends = [drf_filters.OrderingFilter]
    ordering_fields = ['tweet_created_at', 'bookmark_created_at',
                       'created_at', 'favorite_count', 'view_count']
    ordering = ['-tweet_created_at', '-created_at']

    def get_queryset(self):
        qs = Bookmark.objects.all()
        req = self.request
        q = req.query_params.get('q')
        if q:
            qs = qs.filter(
                models.Q(text__icontains=q)
                | models.Q(author_screen_name__icontains=q)
                | models.Q(author_name__icontains=q)
                | models.Q(summary__icontains=q)
            )
        for flag in ('has_video', 'has_photo', 'has_card',
                     'is_quote', 'is_retweet'):
            v = req.query_params.get(flag)
            if v is not None:
                qs = qs.filter(**{flag: _truthy(v)})
        author = req.query_params.get('author')
        if author:
            qs = qs.filter(author_screen_name__iexact=author)
        return qs

    @action(detail=False, methods=['post'], url_path='import')
    def import_bookmarks(self, request):
        """Bulk upsert bookmarks from a normalised export payload.

        Body: {"bookmarks": [ {normalised tweet obj}, ... ]} → matches the
        JSON files produced by x-bookmarks-exporter.
        Returns {created, updated, skipped, total}.
        """
        ser = BookmarkImportSerializer(data=request.data)
        ser.is_valid(raise_exception=True)
        result = ser.save() if hasattr(ser, 'save') else ser.create(ser.validated_data)
        result['total'] = Bookmark.objects.count()
        return Response(result, status=status.HTTP_200_OK)

    @action(detail=False, methods=['put', 'post'], url_path='bulk')
    def bulk_upsert(self, request):
        """Bulk create/update from a bare JSON array of bookmark dicts.

        Each item may be either a full normalised export object OR a flat
        Bookmark-shaped object (matching BookmarkSerializer fields).
        """
        items = request.data
        if not isinstance(items, list):
            return Response({'detail': 'expected a JSON array'},
                            status=status.HTTP_400_BAD_REQUEST)
        created = updated = unchanged = skipped = 0
        for raw in items:
            tid = str(raw.get('tweet_id') or raw.get('id') or '')
            if not tid:
                skipped += 1
                continue
            payload = _to_db_payload(raw)
            new_hash = payload.get('content_hash', '')
            obj = Bookmark.objects.filter(tweet_id=tid).first()
            if obj is None:
                BookmarkSerializer().create(payload)
                created += 1
            elif obj.content_hash == new_hash:
                # Content unchanged — only bump import timestamp.
                obj.last_imported_at = timezone.now()
                obj.save(update_fields=['last_imported_at'])
                unchanged += 1
            else:
                ser = BookmarkSerializer(obj, data=payload, partial=True)
                ser.is_valid(raise_exception=True)
                ser.save()
                updated += 1
        return Response({'created': created, 'updated': updated,
                         'unchanged': unchanged, 'skipped': skipped,
                         'total': Bookmark.objects.count()},
                        status=status.HTTP_200_OK)

    @action(detail=False, methods=['get'], url_path='stats')
    def stats(self, request):
        """Aggregate counts for the viewer header/filter chips."""
        qs = Bookmark.objects.all()
        return Response({
            'total': qs.count(),
            'has_video': qs.filter(has_video=True).count(),
            'has_photo': qs.filter(has_photo=True).count(),
            'has_card': qs.filter(has_card=True).count(),
            'is_quote': qs.filter(is_quote=True).count(),
            'is_retweet': qs.filter(is_retweet=True).count(),
        })


def _truthy(v):
    return str(v).lower() in ('1', 'true', 'yes', 'on')


def _to_db_payload(raw):
    """Accept either an export object ({'id','author':{'screen_name'},'media',...})
    or a flat Bookmark-shaped dict and return a serializer-compatible dict."""
    if 'author' in raw or 'media' in raw or 'created_at' in raw \
            and not any(k in raw for k in ('tweet_created_at',)):
        # Looks like a normalised export object → re-map it.
        author = raw.get('author') or {}
        media = raw.get('media') or []
        from datetime import datetime
        tca = raw.get('created_at')
        tweet_at = None
        if isinstance(tca, str) and tca:
            try:
                tweet_at = datetime.strptime(tca, '%a %b %d %H:%M:%S %z %Y')
            except ValueError:
                tweet_at = None
        return {
            'tweet_id': str(raw.get('id') or raw.get('tweet_id')),
            'url': raw.get('url') or '',
            'author_screen_name': author.get('screen_name') or '',
            'author_name': author.get('name') or '',
            'author_profile_image_url': author.get('profile_image_url') or '',
            'text': raw.get('text') or '',
            'summary': raw.get('summary') or '',
            'tweet_created_at': tweet_at,
            'bookmark_created_at': raw.get('bookmark_created_at'),
            'view_count': raw.get('view_count') or 0,
            'favorite_count': raw.get('favorite_count') or 0,
            'retweet_count': raw.get('retweet_count') or 0,
            'reply_count': raw.get('reply_count') or 0,
            'bookmark_count': raw.get('bookmark_count') or 0,
            'quote_count': raw.get('quote_count') or 0,
            'has_video': any(m.get('type') in ('video', 'animated_gif') for m in media),
            'has_photo': any(m.get('type') == 'photo' for m in media),
            'has_card': bool(raw.get('card')),
            'is_quote': bool(raw.get('quoted')),
            'is_retweet': bool(raw.get('retweet')),
            'media': media,
            'card_data': raw.get('card'),
            'quoted_data': raw.get('quoted'),
            'data': raw,
            'content_hash': compute_bookmark_content_hash(raw),
            'last_imported_at': timezone.now(),
        }
    # Already flat/serializer-shaped — still stamp content_hash + last_imported_at
    # so the dedup comparison in bulk_upsert works for both formats.
    out = dict(raw)
    if 'content_hash' not in out or not out['content_hash']:
        out['content_hash'] = _compute_flat_content_hash(raw)
    if 'last_imported_at' not in out or not out.get('last_imported_at'):
        out['last_imported_at'] = timezone.now()
    return out


def _compute_flat_content_hash(flat: dict) -> str:
    """Compute a SHA-256 hash from a flat/normalized bookmark dict for dedup."""
    import hashlib, json
    content = {
        'text': flat.get('text', ''),
        'url': flat.get('url', ''),
        'author_screen_name': flat.get('author_screen_name', ''),
        'author_name': flat.get('author_name', ''),
        'media': flat.get('media', []),
        'card_data': flat.get('card_data'),
        'quoted_data': flat.get('quoted_data'),
        'has_video': flat.get('has_video', False),
        'has_photo': flat.get('has_photo', False),
        'has_card': flat.get('has_card', False),
        'is_quote': flat.get('is_quote', False),
        'is_retweet': flat.get('is_retweet', False),
        'view_count': flat.get('view_count', 0),
        'favorite_count': flat.get('favorite_count', 0),
        'retweet_count': flat.get('retweet_count', 0),
        'reply_count': flat.get('reply_count', 0),
        'bookmark_count': flat.get('bookmark_count', 0),
        'quote_count': flat.get('quote_count', 0),
    }
    canonical = json.dumps(content, sort_keys=True, ensure_ascii=False, default=str)
    return hashlib.sha256(canonical.encode()).hexdigest()


# ---------- UI views ----------

class BookmarkListView(ListView):
    """Render the full bookmark archive page.

    All bookmarks are rendered server-side in one response (with
    `content-visibility:auto` cards so off-screen cards are cheap), and
    client-side JS handles instant search + filtering — matching the UX of the
    standalone x-bookmarks-exporter viewer. Server-side query params still work
    for deep-linking / no-JS.
    """
    model = Bookmark
    template_name = 'links/bookmark_list.html'
    context_object_name = 'bookmarks'
    paginate_by = None  # render all; performance via content-visibility + lazy imgs

    def get_queryset(self):
        # Server-side filtering only honours q + flag params for deep-links.
        # The interactive page filters client-side for instant UX.
        qs = Bookmark.objects.all().order_by('-tweet_created_at', '-created_at')
        q = self.request.GET.get('q')
        if q:
            qs = qs.filter(
                models.Q(text__icontains=q)
                | models.Q(author_screen_name__icontains=q)
                | models.Q(author_name__icontains=q)
                | models.Q(summary__icontains=q)
            )
        for flag in ('has_video', 'has_photo', 'has_card', 'is_quote', 'is_retweet'):
            v = self.request.GET.get(flag)
            if v is not None and _truthy(v):
                qs = qs.filter(**{flag: True})
        author = self.request.GET.get('author')
        if author:
            qs = qs.filter(author_screen_name__iexact=author)
        return qs

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        qs = Bookmark.objects.all()
        ctx['stats'] = {
            'total': qs.count(),
            'has_video': qs.filter(has_video=True).count(),
            'has_photo': qs.filter(has_photo=True).count(),
            'has_card': qs.filter(has_card=True).count(),
            'is_quote': qs.filter(is_quote=True).count(),
            'is_retweet': qs.filter(is_retweet=True).count(),
        }
        return ctx


class BookmarkDetailView(DetailView):
    model = Bookmark
    template_name = 'links/bookmark_detail.html'
    context_object_name = 'bookmark'
