"""
Django REST Framework ViewSets for TheStarFX models.
"""
import logging
import mimetypes
from pathlib import Path
from uuid import uuid4
from django.conf import settings
from django.contrib.auth.models import User
from django.core.files.storage import FileSystemStorage
from django.db import connection, transaction
from django.http import FileResponse, Http404
from django.utils import timezone
from rest_framework import viewsets, filters, generics, permissions, status
from rest_framework.pagination import PageNumberPagination
from rest_framework.throttling import ScopedRateThrottle
from rest_framework.response import Response
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.views import APIView
from rest_framework_simplejwt.exceptions import TokenError
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.views import TokenObtainPairView
from config import cfg
from django_filters.rest_framework import DjangoFilterBackend
from PIL import Image, UnidentifiedImageError
from .models import (
    Bot, ContentBlock, SiteSettings, NavigationItem, ForexPair, Lesson,
    Signal, News, Testimonial, LeaderboardEntry,
)
from .serializers import (
    BotSerializer, ForexPairSerializer, LessonSerializer,
    SignalSerializer, NewsSerializer, TestimonialSerializer, LeaderboardSerializer,
    RegisterSerializer, CustomTokenObtainPairSerializer, UserProfileSerializer,
    GoogleLoginSerializer, LogoutSerializer,
    ContentBlockSerializer,
    SiteSettingsSerializer, NavigationItemSerializer,
)
from .emails import send_registration_success_email
from .google_auth import GoogleAuthenticationError, verify_google_credential

logger = logging.getLogger(__name__)


@api_view(['GET'])
@permission_classes([permissions.AllowAny])
def health_view(request):
    """Lightweight production health check that also verifies the database."""
    with connection.cursor() as cursor:
        cursor.execute('SELECT 1')
        cursor.fetchone()
    return Response({'status': 'ok'})


class FlexiblePageNumberPagination(PageNumberPagination):
    """Keep public responses small while allowing the CMS to request larger pages."""

    page_size = 20
    page_size_query_param = 'page_size'
    max_page_size = 100


class IsAdminOrReadOnly(permissions.BasePermission):
    """Allow public reads but restrict every mutation to staff accounts."""

    message = 'Administrator access is required to change website content.'

    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        return bool(request.user and request.user.is_authenticated and request.user.is_staff)


# ---------------------------------------------------------------------------
# Auth Views
# ---------------------------------------------------------------------------

class RegisterView(generics.CreateAPIView):
    """POST /api/auth/register/ — create a new user account."""
    serializer_class = RegisterSerializer
    permission_classes = [permissions.AllowAny]

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        with transaction.atomic():
            user = serializer.save()

        email_sent = send_registration_success_email(user)
        detail = (
            'Account created successfully. Please check your email for a welcome message.'
            if email_sent
            else 'Account created successfully. You can now sign in.'
        )
        return Response(
            {'detail': detail, 'registration_email_sent': email_sent},
            status=status.HTTP_201_CREATED,
        )


class CustomTokenObtainPairView(TokenObtainPairView):
    """POST /api/auth/login/ — return access + refresh tokens with user info."""
    serializer_class = CustomTokenObtainPairSerializer


class GoogleLoginView(APIView):
    """Verify a Google credential and issue the project's normal JWT pair."""

    authentication_classes = []
    permission_classes = [permissions.AllowAny]

    def post(self, request):
        serializer = GoogleLoginSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        try:
            identity = verify_google_credential(serializer.validated_data['credential'])
        except GoogleAuthenticationError as exc:
            return Response({'detail': str(exc)}, status=status.HTTP_400_BAD_REQUEST)

        email = identity['email']
        with transaction.atomic():
            matching_users = list(
                User.objects.select_for_update().filter(email__iexact=email)[:2]
            )
            if len(matching_users) > 1:
                return Response(
                    {'detail': 'This email is associated with multiple accounts. Please contact support.'},
                    status=status.HTTP_409_CONFLICT,
                )

            if matching_users:
                user = matching_users[0]
                created = False
            else:
                username = email
                if User.objects.filter(username=username).exists():
                    username = f"google_{identity['subject']}"[:150]

                user = User.objects.create_user(
                    username=username,
                    email=email,
                    first_name=identity['first_name'],
                    last_name=identity['last_name'],
                )
                created = True

            if not user.is_active:
                return Response(
                    {'detail': 'This account is inactive.'},
                    status=status.HTTP_403_FORBIDDEN,
                )

        email_sent = send_registration_success_email(user) if created else False
        refresh = CustomTokenObtainPairSerializer.get_token(user)
        return Response({
            'refresh': str(refresh),
            'access': str(refresh.access_token),
            'is_new_user': created,
            'registration_email_sent': email_sent,
        })


class LogoutView(APIView):
    """Blacklist the submitted refresh token so it cannot be reused."""

    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        serializer = LogoutSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        try:
            refresh = RefreshToken(serializer.validated_data['refresh'])
        except TokenError:
            return Response(
                {'detail': 'The refresh token is invalid or expired.'},
                status=status.HTTP_400_BAD_REQUEST,
            )
        user_id_claim = settings.SIMPLE_JWT.get('USER_ID_CLAIM', 'user_id')
        if str(refresh.payload.get(user_id_claim)) != str(request.user.pk):
            return Response(
                {'detail': 'The refresh token does not belong to this account.'},
                status=status.HTTP_400_BAD_REQUEST,
            )
        refresh.blacklist()
        return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(['GET'])
@permission_classes([permissions.IsAuthenticated])
def me_view(request):
    """GET /api/auth/me/ — return the authenticated user's profile."""
    serializer = UserProfileSerializer(request.user)
    return Response(serializer.data)


@api_view(['GET'])
@permission_classes([permissions.IsAdminUser])
def cms_summary(request):
    """High-level CMS metrics for the staff dashboard."""

    return Response({
        'content_blocks': ContentBlock.objects.count(),
        'published_blocks': ContentBlock.objects.filter(is_published=True).count(),
        'navigation_items': NavigationItem.objects.count(),
        'site_settings': SiteSettings.objects.count(),
        'bots': Bot.objects.count(),
        'forex': ForexPair.objects.count(),
        'lessons': Lesson.objects.count(),
        'signals': Signal.objects.count(),
        'news': News.objects.count(),
        'testimonials': Testimonial.objects.count(),
        'leaderboard': LeaderboardEntry.objects.count(),
        'users': User.objects.count(),
        'last_updated': ContentBlock.objects.order_by('-updated_at')
            .values_list('updated_at', flat=True).first(),
    })


@api_view(['GET'])
@permission_classes([permissions.AllowAny])
def cms_logo_asset(request, filename):
    """Serve a validated CMS logo immediately from its canonical directory."""

    if Path(filename).name != filename or not filename.startswith('site-logo-'):
        raise Http404
    logo_path = Path(settings.CMS_LOGO_ROOT) / filename
    if not logo_path.is_file():
        raise Http404

    content_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
    response = FileResponse(logo_path.open('rb'), content_type=content_type)
    response['Cache-Control'] = 'public, max-age=31536000, immutable'
    return response


# ---------------------------------------------------------------------------
# Live News Proxy — fetches from Marketaux and falls back to internal DB
# ---------------------------------------------------------------------------
@api_view(['GET'])
@permission_classes([permissions.AllowAny])
def live_news_proxy(request):
    """
    Proxy endpoint for live financial news from Marketaux API.

    Query params (forwarded to Marketaux):
      category  — 'forex' | 'crypto' | 'commodities' | 'general' (default: all)
      page_size — number of articles (default: 12, max: 50)
      page      — page number (default: 1)

    Falls back to internal News model if:
      - MARKETAUX_API_KEY is not set in environment
      - Marketaux request fails for any reason

    Response shape (normalised):
      {
        "source": "marketaux" | "internal",
        "results": [
          {
            "id":          str,
            "headline":    str,
            "summary":     str,
            "url":         str,
            "image_url":   str,
            "source":      str,
            "category":    str,
            "impact":      "high" | "medium" | "low",
            "sentiment":   float | null,   # -1.0 to 1.0
            "tickers":     list[str],
            "published_at": str (ISO 8601),
            "read_time":   int (minutes)
          }
        ],
        "total": int
      }
    """
    import httpx

    api_key   = cfg.MARKETAUX_API_KEY
    category  = request.query_params.get('category', '')
    page_size = min(int(request.query_params.get('page_size', cfg.MARKETAUX_DEFAULT_LIMIT)), cfg.MARKETAUX_MAX_LIMIT)
    page      = int(request.query_params.get('page', 1))

    # ── Try Marketaux ──────────────────────────────────────────────────────
    if api_key:
        try:
            # Map our category names to Marketaux entity types / search terms
            CATEGORY_QUERIES = {
                'forex':       'forex OR "currency market" OR "exchange rate"',
                'crypto':      'cryptocurrency OR bitcoin OR ethereum OR crypto',
                'commodities': 'gold OR oil OR commodities OR "crude oil"',
                'general':     'forex OR crypto OR "stock market" OR trading',
            }
            search_q = CATEGORY_QUERIES.get(category, 'forex OR crypto OR trading OR "financial markets"')

            params = {
                'api_token':    api_key,
                'language':     'en',
                'search':       search_q,
                'limit':        page_size,
                'page':         page,
                'sort':         'published_on',
                'filter_entities': 'true',
            }

            resp = httpx.get(
                'https://api.marketaux.com/v1/news/all',
                params=params,
                timeout=8.0,
            )
            resp.raise_for_status()
            data = resp.json()

            articles = data.get('data', [])
            meta     = data.get('meta', {})

            def _impact(sentiment_score):
                if sentiment_score is None:
                    return 'medium'
                if sentiment_score >= 0.3 or sentiment_score <= -0.3:
                    return 'high'
                if abs(sentiment_score) >= 0.1:
                    return 'medium'
                return 'low'

            def _read_time(text):
                return max(1, round(len(text or '') / 800))

            def _category(article):
                # Use our category param if set, otherwise infer from entities
                if category:
                    return category.capitalize()
                entities = article.get('entities', [])
                types = {e.get('type', '').lower() for e in entities}
                if 'cryptocurrency' in types:
                    return 'Crypto'
                if 'currency' in types or 'forex' in types:
                    return 'Forex'
                return 'Markets'

            normalised = []
            for a in articles:
                # Average sentiment across all entities in the article
                entities = a.get('entities', [])
                sentiments = [
                    e.get('sentiment_score')
                    for e in entities
                    if e.get('sentiment_score') is not None
                ]
                avg_sentiment = (sum(sentiments) / len(sentiments)) if sentiments else None

                normalised.append({
                    'id':           str(a.get('uuid', '')),
                    'headline':     a.get('title', ''),
                    'summary':      a.get('description', '') or a.get('snippet', ''),
                    'url':          a.get('url', ''),
                    'image_url':    a.get('image_url', ''),
                    'source':       a.get('source', {}).get('name', 'Unknown') if isinstance(a.get('source'), dict) else str(a.get('source', '')),
                    'category':     _category(a),
                    'impact':       _impact(avg_sentiment),
                    'sentiment':    round(avg_sentiment, 4) if avg_sentiment is not None else None,
                    'tickers':      [e.get('symbol', '') for e in entities if e.get('symbol')],
                    'published_at': a.get('published_at', ''),
                    'read_time':    _read_time(a.get('description', '')),
                })

            return Response({
                'source':  'marketaux',
                'results': normalised,
                'total':   meta.get('found', len(normalised)),
            })

        except Exception as exc:
            logger.warning(f'[LiveNews] Marketaux request failed: {exc} — falling back to internal DB')

    # ── Fallback: internal News model ─────────────────────────────────────
    qs = News.objects.all()
    if category:
        qs = qs.filter(category__iexact=category)
    qs = qs.order_by('-published_at')[:page_size]

    results = []
    for n in qs:
        results.append({
            'id':           str(n.id),
            'headline':     n.title,
            'summary':      n.content[:300] if n.content else '',
            'url':          '',
            'image_url':    n.image_url or '',
            'source':       n.source,
            'category':     n.category,
            'impact':       n.impact,
            'sentiment':    None,
            'tickers':      n.related_pairs or [],
            'published_at': n.published_at.isoformat() if n.published_at else '',
            'read_time':    max(1, round(len(n.content or '') / 800)),
        })

    return Response({
        'source':  'internal',
        'results': results,
        'total':   len(results),
    })


class BotViewSet(viewsets.ModelViewSet):
    """
    API endpoint for Trading Bots.
    
    Supports filtering by category, status, and plan.
    Supports searching by name, description, and tags.
    Supports ordering by price, win_rate, monthly_return, created_at.
    """
    queryset = Bot.objects.all()
    serializer_class = BotSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    
    # Filtering
    filterset_fields = ['category', 'status', 'plan']
    
    # Search
    search_fields = ['name', 'description', 'tags']
    
    # Ordering
    ordering_fields = ['price', 'win_rate', 'monthly_return', 'created_at', 'sharpe_ratio']
    ordering = ['-created_at']


class ForexPairViewSet(viewsets.ModelViewSet):
    """
    API endpoint for Forex Pairs (Read-only).
    
    Supports filtering by category, trend, and sentiment.
    Supports searching by pair, base, quote.
    Supports ordering by pair, price, sentiment.
    """
    queryset = ForexPair.objects.all()
    serializer_class = ForexPairSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    throttle_classes = [ScopedRateThrottle]
    throttle_scope = 'market_data'
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    
    # Filtering
    filterset_fields = ['category', 'trend']
    
    # Search
    search_fields = ['pair', 'base', 'quote']
    
    # Ordering
    ordering_fields = ['pair', 'price', 'sentiment', 'change_pct']
    ordering = ['pair']


class LessonViewSet(viewsets.ModelViewSet):
    """
    API endpoint for Educational Lessons (Read-only).
    
    Supports filtering by category and difficulty.
    Supports searching by title, description, and tags.
    Supports ordering by order, difficulty, created_at.
    """
    queryset = Lesson.objects.all()
    serializer_class = LessonSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    
    # Filtering
    filterset_fields = ['category', 'difficulty']
    
    # Search
    search_fields = ['title', 'description', 'tags']
    
    # Ordering
    ordering_fields = ['order', 'difficulty', 'duration_minutes', 'created_at']
    ordering = ['order']


class SignalViewSet(viewsets.ModelViewSet):
    """
    API endpoint for Trading Signals (Read-only).
    
    Supports filtering by pair, signal_type.
    Supports ordering by confidence, created_at, expires_at.
    """
    serializer_class = SignalSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    
    # Filtering
    filterset_fields = ['pair', 'signal_type']
    
    # Ordering
    ordering_fields = ['confidence', 'created_at', 'expires_at']
    ordering = ['-confidence', '-created_at']
    
    def get_queryset(self):
        """Only return signals that haven't expired."""
        if self.request.user.is_authenticated and self.request.user.is_staff:
            return Signal.objects.all()
        return Signal.objects.filter(expires_at__gt=timezone.now())


class NewsViewSet(viewsets.ModelViewSet):
    """
    API endpoint for Market News (Read-only).
    
    Supports filtering by category and impact.
    Supports searching by title, content.
    Supports ordering by published_at, impact.
    """
    queryset = News.objects.all()
    serializer_class = NewsSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    
    # Filtering
    filterset_fields = ['category', 'impact']
    
    # Search
    search_fields = ['title', 'content', 'source']
    
    # Ordering
    ordering_fields = ['published_at', 'created_at', 'impact']
    ordering = ['-published_at']


class TestimonialViewSet(viewsets.ModelViewSet):
    """
    API endpoint for Testimonials (Read-only).
    
    Supports filtering by rating.
    Supports ordering by rating, created_at.
    """
    queryset = Testimonial.objects.all()
    serializer_class = TestimonialSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    
    # Filtering
    filterset_fields = ['rating']
    
    # Ordering
    ordering_fields = ['rating', 'created_at']
    ordering = ['-rating', '-created_at']


class LeaderboardViewSet(viewsets.ModelViewSet):
    """
    API endpoint for Leaderboard (Read-only).
    
    Supports ordering by rank, monthly_return, win_rate, profit.
    """
    queryset = LeaderboardEntry.objects.all()
    serializer_class = LeaderboardSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    filter_backends = [filters.OrderingFilter]
    
    # Ordering
    ordering_fields = ['rank', 'monthly_return', 'win_rate', 'profit', 'total_trades']
    ordering = ['rank']


class ContentBlockViewSet(viewsets.ModelViewSet):
    """Public published page copy with staff-only CMS editing."""

    serializer_class = ContentBlockSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['key', 'is_published']
    search_fields = ['key', 'page', 'section', 'title', 'body']
    ordering_fields = ['page', 'display_order', 'section', 'updated_at']
    ordering = ['page', 'display_order', 'section']

    def get_queryset(self):
        queryset = ContentBlock.objects.all()
        if not (self.request.user.is_authenticated and self.request.user.is_staff):
            queryset = queryset.filter(is_published=True)
        page_name = self.request.query_params.get('page_name')
        if page_name:
            queryset = queryset.filter(page__iexact=page_name)
        return queryset


class SiteSettingsViewSet(viewsets.ModelViewSet):
    """Global header, footer and brand settings."""

    queryset = SiteSettings.objects.all().order_by('id')
    serializer_class = SiteSettingsSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination

    @action(
        detail=True,
        methods=['post', 'delete'],
        url_path='logo',
        parser_classes=[MultiPartParser, FormParser],
        permission_classes=[permissions.IsAdminUser],
    )
    def logo(self, request, pk=None):
        """Upload or remove the canonical public/CMS brand logo."""

        site_settings = self.get_object()
        storage = FileSystemStorage(
            location=settings.CMS_LOGO_ROOT,
            base_url=settings.CMS_LOGO_URL,
        )

        def delete_current_logo():
            if not site_settings.logo_url:
                return
            current_name = Path(site_settings.logo_url.split('?', 1)[0]).name
            if current_name.startswith('site-logo-') and storage.exists(current_name):
                storage.delete(current_name)

        if request.method == 'DELETE':
            delete_current_logo()
            site_settings.logo_url = ''
            site_settings.save(update_fields=['logo_url', 'updated_at'])
            return Response(self.get_serializer(site_settings).data)

        logo_file = request.FILES.get('logo')
        if not logo_file:
            return Response(
                {'logo': ['Choose an image to upload.']},
                status=status.HTTP_400_BAD_REQUEST,
            )
        if logo_file.size > 5 * 1024 * 1024:
            return Response(
                {'logo': ['The logo must be 5 MB or smaller.']},
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            image = Image.open(logo_file)
            image_format = (image.format or '').upper()
            width, height = image.size
            image.verify()
        except (UnidentifiedImageError, OSError, ValueError):
            return Response(
                {'logo': ['Upload a valid PNG, JPEG, or WebP image.']},
                status=status.HTTP_400_BAD_REQUEST,
            )

        extensions = {'PNG': 'png', 'JPEG': 'jpg', 'WEBP': 'webp'}
        if image_format not in extensions:
            return Response(
                {'logo': ['Only PNG, JPEG, and WebP logos are supported.']},
                status=status.HTTP_400_BAD_REQUEST,
            )
        if width > 4096 or height > 4096:
            return Response(
                {'logo': ['Logo dimensions cannot exceed 4096 × 4096 pixels.']},
                status=status.HTTP_400_BAD_REQUEST,
            )

        logo_file.seek(0)
        filename = f"site-logo-{uuid4().hex[:12]}.{extensions[image_format]}"
        saved_name = storage.save(filename, logo_file)
        delete_current_logo()

        site_settings.logo_url = f"{settings.CMS_LOGO_URL}{saved_name}"
        site_settings.save(update_fields=['logo_url', 'updated_at'])
        return Response(self.get_serializer(site_settings).data)


class NavigationItemViewSet(viewsets.ModelViewSet):
    """Ordered public navigation links with staff-only editing."""

    serializer_class = NavigationItemSerializer
    permission_classes = [IsAdminOrReadOnly]
    pagination_class = FlexiblePageNumberPagination
    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
    filterset_fields = ['location', 'is_active']
    search_fields = ['label', 'url', 'description']
    ordering_fields = ['location', 'order', 'updated_at']
    ordering = ['location', 'order', 'id']

    def get_queryset(self):
        queryset = NavigationItem.objects.all()
        if not (self.request.user.is_authenticated and self.request.user.is_staff):
            queryset = queryset.filter(is_active=True)
        return queryset
