from django.shortcuts import render, redirect, get_object_or_404
from django.db.models import Count, Q
from django.contrib.auth.views import PasswordChangeView, PasswordResetView
from django.urls import reverse_lazy
from rest_framework.views import APIView
from rest_framework.response import Response
from django.utils import timezone
import json
from django.core.files.storage import FileSystemStorage
from django.http import HttpResponse
from .models import Year, Category, MyModel
from django.contrib.auth import authenticate, login
from django.contrib import messages
from .forms import CustomAuthenticationForm
from django.conf import settings
import logging
from datetime import timedelta, datetime
from django.contrib.auth.forms import AuthenticationForm, SetPasswordForm, PasswordChangeForm
from django.urls import reverse
import base64
from io import BytesIO
from django.core.files.base import ContentFile
from PIL import Image
from django.contrib.auth.mixins import LoginRequiredMixin as DjangoLoginRequiredMixin
from django.contrib.auth import get_user_model
from django.contrib.auth import authenticate, login, login as auth_login, logout
from django.contrib.auth.decorators import login_required
from .models import MyModel, Year, MyModelVisits, SearchQuery, SearchTermQuery, Subscriber, Category, Term, Bookmark, UserProfile, Popularity
from django.views.generic import ListView, DetailView
from .serializers import MyModelSerializer
from cryptography.fernet import Fernet, InvalidToken
from django.shortcuts import get_object_or_404
from .models import Term
from django.utils.decorators import method_decorator
from django.core.exceptions import PermissionDenied
from .forms import RecaptchaForm
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.http import JsonResponse
from .forms import SubscribeForm
from .forms import UserSignupForm, UserProfileForm
from django.contrib.auth import update_session_auth_hash
from django.utils.timezone import now
from django.utils.html import escape
from django_user_agents.utils import get_user_agent
from .forms import ContactForm
from django.core.mail import send_mail
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes
from django.contrib.auth.models import User
import re
from rest_framework import viewsets
from .models import Bookmark
from .serializers import BookmarkSerializer
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

from .models import MyModel, Popularity, Bookmark, MyModelVisits
from regulations.utils import get_client_ip
from django_ratelimit.decorators import ratelimit
from regulations.utils.device import detect_device
from django.http import HttpResponseForbidden
from .models import Report
from .forms import ReportForm
from collections import defaultdict
from django.core.cache import cache
from django.utils.text import slugify
import requests

logger = logging.getLogger(__name__)

# Inisialisasi cipher_suite dengan kunci dari settings
cipher_suite = Fernet(settings.ENCRYPTION_KEY)

RECAPTCHA_REQUIRED_SCORE = 0.5
COMMON_WORDS = {"uu", "undang-undang", "peraturan", "putusan"}


# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        return x_forwarded_for.split(',')[0]
    return request.META.get('REMOTE_ADDR')


def detect_device(user_agent):
    if user_agent.is_mobile:
        return 'Mobile'
    elif user_agent.is_tablet:
        return 'Tablet'
    return 'Desktop'


def is_googlebot(request):
    user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
    return "googlebot" in user_agent


def decrypt_data(encrypted_data):
    if not encrypted_data:
        return ""
    try:
        return cipher_suite.decrypt(encrypted_data.encode()).decode()
    except (InvalidToken, AttributeError, TypeError) as e:
        logging.error(f"Dekripsi gagal: {e}")
        return "Invalid Data"


def encrypt_data(plain_data):
    if not plain_data:
        return ""
    try:
        return cipher_suite.encrypt(plain_data.encode()).decode()
    except Exception as e:
        logging.error(f"Enkripsi gagal: {e}")
        return ""


def clean_search_query(query):
    words = query.lower().split()
    return " ".join(w for w in words if w not in COMMON_WORDS)


def count_occurrences(data, query):
    """Recursively count occurrences of query in nested JSON data."""
    if isinstance(data, dict):
        return sum(count_occurrences(value, query) for value in data.values())
    elif isinstance(data, list):
        return sum(count_occurrences(item, query) for item in data)
    elif isinstance(data, str):
        return data.lower().count(query.lower())
    return 0


def verify_recaptcha(request):
    """Verify reCAPTCHA v2 response. Returns True on success."""
    recaptcha_response = request.POST.get('g-recaptcha-response')
    secret_key = settings.RECAPTCHA_PRIVATE_KEY

    if not recaptcha_response:
        return False

    try:
        result = requests.post(
            'https://www.google.com/recaptcha/api/siteverify',
            data={'secret': secret_key, 'response': recaptcha_response},
            timeout=5,
        ).json()
    except requests.RequestException as e:
        logger.error(f"reCAPTCHA request failed: {e}")
        return False

    return result.get('success', False)


# ──────────────────────────────────────────────────────────────────────────────
# Reports
# ──────────────────────────────────────────────────────────────────────────────

@csrf_exempt
def report_error(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        url = data.get('url')
        description = data.get('description')

        if Report.is_report_limit_reached(url, description):
            return JsonResponse({
                'status': 'error',
                'message': 'Anda telah mencapai batas maksimal laporan untuk URL atau keterangan ini.'
            }, status=400)

        Report.objects.create(
            url=url,
            description=description,
            ip_address=request.META.get('REMOTE_ADDR'),
            user=request.user if request.user.is_authenticated else None,
        )
        return JsonResponse({'status': 'success'}, status=200)
    return JsonResponse({'status': 'error'}, status=400)


# ──────────────────────────────────────────────────────────────────────────────
# Password management
# ──────────────────────────────────────────────────────────────────────────────

@login_required
def password_change_success(request):
    return render(request, 'password_change_success.html')


@login_required
def change_password(request):
    if request.method == 'POST':
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)
            send_mail(
                subject='Password Anda Berhasil Diganti',
                message='Password Anda telah berhasil diganti. Jika ini bukan Anda, harap hubungi kami segera.',
                from_email='noreply@artifilaw.com',
                recipient_list=[user.email],
                fail_silently=False,
            )
            messages.success(request, 'Password berhasil diganti. Kami juga telah mengirimkan notifikasi ke email Anda.')
            return redirect('password_change_done')
    else:
        form = PasswordChangeForm(request.user)
    return render(request, 'registration/password_change_form.html', {'form': form})


class CustomPasswordResetView(PasswordResetView):
    email_template_name = 'registration/password_reset_email.html'
    token_generator = default_token_generator

    def form_valid(self, form):
        email = form.cleaned_data["email"]
        users = self.get_users(email)

        if not users.exists():
            messages.error(self.request, "No user is associated with this email.")
            return self.form_invalid(form)

        for user in users:
            token = self.token_generator.make_token(user)
            uid = urlsafe_base64_encode(force_bytes(user.pk))
            reset_url = self.request.build_absolute_uri(
                reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token})
            )
            self.send_reset_email(user.email, reset_url)

        messages.success(self.request, "Password reset email has been sent.")
        return super().form_valid(form)

    def get_users(self, email):
        UserModel = get_user_model()
        return UserModel.objects.filter(email=email)

    def send_reset_email(self, email, reset_url):
        send_mail(
            "Reset Your Password",
            f"Click the link below to reset your password:\n{reset_url}",
            'noreply@artifilaw.com',
            [email],
            fail_silently=False,
        )


@login_required
def password_settings_view(request):
    if request.method == 'POST':
        form = PasswordChangeForm(user=request.user, data=request.POST)
        if form.is_valid():
            if not request.user.check_password(form.cleaned_data['old_password']):
                form.add_error('old_password', 'Password lama yang Anda masukkan salah.')
            else:
                request.session['old_password'] = request.user.password
                user = form.save()
                update_session_auth_hash(request, user)
                send_password_change_email(request, user)
                messages.success(request, 'Password Anda telah berhasil diperbarui!')
                return redirect('password_settings')
        else:
            messages.error(request, 'Ada kesalahan dalam formulir. Silakan periksa kembali.')
    else:
        form = PasswordChangeForm(user=request.user)
    return render(request, 'password_settings.html', {'form': form})


def send_password_change_email(request, user):
    token = default_token_generator.make_token(user)
    uid = urlsafe_base64_encode(force_bytes(user.pk))
    reset_url = request.build_absolute_uri(
        reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token})
    )
    subject = "Reset Your Password"
    message = f"""
    Hallo {user.username},

    Kami melihat bahwa Anda mungkin tidak melakukan perubahan password. Jika Anda merasa ini adalah tindakan yang tidak diinginkan, silakan reset password Anda dengan mengklik tautan di bawah ini:

    {reset_url}

    Jika Anda tidak ingin melakukan apa pun, abaikan email ini.

    Terima kasih,
    Tim Artifilaw
    """
    send_mail(subject, message, 'noreply@artifilaw.com', [user.email], fail_silently=False)


def password_change_confirm(request, user_id):
    try:
        user = get_user_model().objects.get(id=user_id)

        if 'old_password' not in request.session:
            request.session['old_password'] = user.password

        if request.method == 'POST':
            old_password = request.POST.get('old_password')
            new_password1 = request.POST.get('new_password1')
            new_password2 = request.POST.get('new_password2')

            if not user.check_password(old_password) and user.password != request.session.get('old_password'):
                messages.error(request, "Password lama Anda salah.")
                return redirect('password_change_confirm', user_id=user.id)

            if new_password1 != new_password2:
                messages.error(request, "Konfirmasi password baru tidak cocok.")
                return redirect('password_change_confirm', user_id=user.id)

            user.set_password(new_password1)
            user.save()
            del request.session['old_password']
            messages.success(request, "Password Anda berhasil diperbarui, silahkan login.")
            return redirect('home')
        else:
            return render(request, 'password_change_confirm.html', {'user': user})

    except get_user_model().DoesNotExist:
        return HttpResponse('Pengguna tidak ditemukan', status=404)


# ──────────────────────────────────────────────────────────────────────────────
# Auth
# ──────────────────────────────────────────────────────────────────────────────

def signup_view(request):
    if request.method == 'POST':
        user_form = UserSignupForm(request.POST)
        profile_form = UserProfileForm(request.POST, user_instance=None)

        if not verify_recaptcha(request):
            messages.error(request, "Invalid reCAPTCHA. Please try again.")
            return render(request, 'signup.html', {
                'user_form': user_form,
                'profile_form': profile_form,
                'RECAPTCHA_PUBLIC_KEY': settings.RECAPTCHA_PUBLIC_KEY
            })

        if user_form.is_valid() and profile_form.is_valid():
            user = user_form.save(commit=False)
            user.set_password(user_form.cleaned_data['password'])
            user.is_active = False
            user.save()

            profile = profile_form.save(commit=False)
            profile.user = user
            profile.save()

            send_verification_email(request, user)
            messages.success(request, "Registration successful! Please check your email for the verification token.")
            return redirect(reverse('verify_email', kwargs={'user_id': user.pk}))
        else:
            print(user_form.errors)
            print(profile_form.errors)
    else:
        user_form = UserSignupForm()
        profile_form = UserProfileForm(user_instance=None)

    return render(request, 'signup.html', {
        'user_form': user_form,
        'profile_form': profile_form,
        'RECAPTCHA_PUBLIC_KEY': settings.RECAPTCHA_PUBLIC_KEY
    })


def send_verification_email(request, user):
    token = default_token_generator.make_token(user)
    prefix = token[:6]

    cache.set(f"email_verification_{user.pk}_prefix", prefix, timeout=900)
    cache.set(f"email_verification_{user.pk}_full", token, timeout=900)

    verification_url = request.build_absolute_uri(
        reverse('verify_email', kwargs={'user_id': user.pk})
    )
    send_mail(
        "Verify Your Email Address",
        (
            f"Hi {user.username},\n\n"
            f"Here is your verification code: {prefix}\n\n"
            f"Click the link below to complete the verification:\n\n{verification_url}"
        ),
        'noreply@artifilaw.com',
        [user.email],
        fail_silently=False,
    )


def verify_email_view(request, user_id):
    if request.method == 'POST':
        prefix_input = request.POST.get('prefix')
        try:
            user = User.objects.get(pk=user_id)
            prefix_stored = cache.get(f"email_verification_{user.pk}_prefix")
            full_token_stored = cache.get(f"email_verification_{user.pk}_full")

            if prefix_input == prefix_stored and full_token_stored:
                user.is_active = True
                user.save()
                cache.delete(f"email_verification_{user.pk}_prefix")
                cache.delete(f"email_verification_{user.pk}_full")
                messages.success(request, "Email verified successfully, login!")
                return redirect('login')
            else:
                messages.error(request, "Invalid token.")
        except User.DoesNotExist:
            messages.error(request, "Invalid user.")

    return render(request, 'verify_email.html', {'user_id': user_id})


@csrf_exempt
def resend_verification_email(request, user_id):
    try:
        user = User.objects.get(pk=user_id)
        cooldown_key = f"resend_cooldown_{user.pk}"
        last_request_time = cache.get(cooldown_key)

        if last_request_time:
            remaining_time = (last_request_time + timedelta(seconds=60)) - now()
            if remaining_time.total_seconds() > 0:
                messages.error(request, f"Please wait {int(remaining_time.total_seconds())} seconds before requesting again.")
                return redirect('verify_email', user_id=user_id)

        if not user.is_active:
            send_verification_email(request, user)
            cache.set(cooldown_key, now(), timeout=60)
            messages.success(request, "A new verification email has been sent.")
        else:
            messages.info(request, "Your email is already verified.")
    except User.DoesNotExist:
        messages.error(request, "User does not exist.")

    return redirect('verify_email', user_id=user_id)


def custom_login_view(request):
    next_url = request.POST.get('next', request.GET.get('next', '/'))

    if request.method == 'POST':
        form = CustomAuthenticationForm(request, data=request.POST)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            user = authenticate(request, username=username, password=password)

            if user is None:
                messages.error(request, 'Nama pengguna atau kata sandi salah.')
            elif not user.is_active:
                messages.warning(request, 'Akun Anda tidak aktif. Hubungi administrator untuk bantuan.')
            else:
                login(request, user)
                return redirect(next_url)
    else:
        form = CustomAuthenticationForm(request)

    return render(request, 'login.html', {
        'form': form,
        'next': next_url,
        'RECAPTCHA_PUBLIC_KEY': settings.RECAPTCHA_PUBLIC_KEY
    })


def logout_view(request):
    logout(request)
    return redirect('home')


# ──────────────────────────────────────────────────────────────────────────────
# Bookmarks
# ──────────────────────────────────────────────────────────────────────────────

class BookmarkViewSet(viewsets.ModelViewSet):
    queryset = Bookmark.objects.all()
    serializer_class = BookmarkSerializer

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()


@login_required
def toggle_bookmark(request, pk):
    my_model = get_object_or_404(MyModel, pk=pk)

    bookmark, created = Bookmark.objects.get_or_create(
        user=request.user,
        regulation=my_model,
        defaults={'url': request.build_absolute_uri(request.get_full_path())}
    )

    if not created:
        # Bookmark sudah ada → hapus
        bookmark.delete()
        is_bookmarked = False
    else:
        # Bookmark baru dibuat → simpan posisi scroll jika dikirim
        is_bookmarked = True
        if request.method == 'POST':
            try:
                data = json.loads(request.body)
                scroll_position = data.get('scroll_position')
                if scroll_position is not None:
                    # ✅ nama field sesuai model: last_read_position
                    bookmark.last_read_position = float(scroll_position)
                    bookmark.save()
            except (json.JSONDecodeError, ValueError, AttributeError):
                pass

    if request.headers.get('x-requested-with') == 'XMLHttpRequest':
        return JsonResponse({'is_bookmarked': is_bookmarked})
    return redirect('my_model_detail', pk=pk)


@login_required
def delete_bookmark(request, pk):
    bookmark = get_object_or_404(Bookmark, pk=pk, user=request.user)
    bookmark.delete()
    return redirect('profile')

from django.views.decorators.csrf import csrf_exempt

@login_required
@csrf_exempt  # ✅ aman karena sudah ada @login_required (harus login dulu)
def update_reading_position(request, pk):
    if request.method != 'POST':
        return JsonResponse({'error': 'Method not allowed'}, status=405)
    try:
        data     = json.loads(request.body)
        position = data.get('position')
        if position is None:
            return JsonResponse({'error': 'position required'}, status=400)

        bookmark = Bookmark.objects.filter(
            user=request.user,
            regulation_id=pk
        ).first()

        if bookmark:
            bookmark.last_read_position = float(position)
            bookmark.save(update_fields=['last_read_position'])

        return JsonResponse({'saved': True, 'position': float(position)})
    except (json.JSONDecodeError, ValueError):
        return JsonResponse({'error': 'Invalid data'}, status=400)

from .models import ArticleSuggestion
 
@login_required
def submit_suggestion(request, pk):
    """Terima saran perbaikan pasal dari user."""
    if request.method != 'POST':
        return JsonResponse({'error': 'Method not allowed'}, status=405)
    try:
        data          = json.loads(request.body)
        article_key   = data.get('article_key', '').strip()
        article_path  = data.get('article_path', '').strip()
        original      = data.get('original_text', '').strip()
        suggested     = data.get('suggested_text', '').strip()
        reason        = data.get('reason', '').strip()
 
        if not article_key or not original or not suggested:
            return JsonResponse({'error': 'Data tidak lengkap.'}, status=400)
 
        regulation = get_object_or_404(MyModel, pk=pk)
        ArticleSuggestion.objects.create(
            regulation    = regulation,
            user          = request.user,
            article_key   = article_key,
            article_path  = article_path,
            original_text = original,
            suggested_text= suggested,
            reason        = reason,
        )
        return JsonResponse({'success': True, 'message': 'Saran berhasil dikirim!'})
    except (json.JSONDecodeError, ValueError):
        return JsonResponse({'error': 'Data tidak valid.'}, status=400)
# ──────────────────────────────────────────────────────────────────────────────
# Subscribe
# ──────────────────────────────────────────────────────────────────────────────

def subscribe(request):
    if request.method == 'POST':
        email = request.POST.get('email')
        if email:
            try:
                Subscriber.objects.create(email=email)
                messages.success(request, 'You have successfully subscribed!')
            except Exception:
                messages.error(request, 'Subscription failed. Email might already exist.')
        else:
            messages.error(request, 'Email is required.')
    return redirect('home')


# ──────────────────────────────────────────────────────────────────────────────
# Profile & Settings
# ──────────────────────────────────────────────────────────────────────────────

@login_required
def profile_settings_view(request):
    profile, created = UserProfile.objects.get_or_create(user=request.user)

    if request.method == 'POST':
        form = UserProfileForm(request.POST, request.FILES, instance=profile, user_instance=request.user)
        delete_picture = request.POST.get('delete_picture') == 'true'
        current_picture = request.POST.get('current_picture')

        if form.is_valid():
            if delete_picture:
                profile.profile_picture = None
            elif 'profile_picture' in request.FILES:
                img = request.FILES['profile_picture']
                image_data = img.read()
                profile.profile_picture = base64.b64encode(image_data).decode('utf-8')
            elif current_picture:
                profile.profile_picture = current_picture

            form.save()
            messages.success(request, 'Your profile has been updated successfully!')
            return redirect('profile_settings')
        else:
            messages.error(request, 'Please correct the errors below.')
    else:
        form = UserProfileForm(instance=profile, user_instance=request.user)

    return render(request, 'profile_settings.html', {'form': form, 'profile': profile})


@login_required
def settings_view(request):
    try:
        profile = UserProfile.objects.get(user=request.user)
    except UserProfile.DoesNotExist:
        profile = UserProfile(user=request.user)

    if request.method == 'POST':
        form = UserProfileForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():
            form.save()
            messages.success(request, 'Your profile has been updated successfully!')
            return redirect('settings')
    else:
        form = UserProfileForm(instance=profile)

    return render(request, 'settings.html', {'form': form})


def profile_view(request):
    user = request.user
    bookmarks = Bookmark.objects.filter(user=user)

    try:
        user_profile = UserProfile.objects.get(user=user)
        user_profile.first_name = decrypt_data(user_profile.first_name)
        user_profile.last_name = decrypt_data(user_profile.last_name)
        user_profile.job = decrypt_data(user_profile.job)
        user_profile.province = decrypt_data(user_profile.province)
        logging.info(f"Profile data setelah dekripsi: {user_profile.__dict__}")
    except UserProfile.DoesNotExist:
        user_profile = None

    recent_activities = MyModelVisits.objects.filter(user=user).order_by('-visited_at')[:5]
    total_bookmarks = bookmarks.count()
    total_visited = MyModelVisits.objects.filter(user=user).count()

    settings_active = request.path.startswith('/settings/')
    profile_url = reverse('profile_settings')
    password_url = reverse('password_settings')

    context = {
        'bookmarks': bookmarks,
        'user_profile': user_profile,
        'recent_activities': recent_activities,
        'total_bookmarks': total_bookmarks,
        'total_visited': total_visited,
        'settings_active': settings_active,
        'profile_url': profile_url,
        'password_url': password_url,
        'profile_active': request.path == profile_url,
        'password_active': request.path == password_url,
    }
    return render(request, 'profile.html', context)


# ──────────────────────────────────────────────────────────────────────────────
# Regulation detail views
# ──────────────────────────────────────────────────────────────────────────────

@ratelimit(key='ip', rate='10/m', method='GET', block=False)
def my_model_detail(request, uuid):
    if is_googlebot(request):
        return my_model_detail_unlimited(request, uuid)

    was_limited = getattr(request, 'limited', False)
    if was_limited and not request.session.get('recaptcha_passed', False):
        if request.method == "POST":
            form = RecaptchaForm(request.POST)
            if form.is_valid():
                request.session['recaptcha_passed'] = True
                return redirect(request.path)
        else:
            return render(request, 'captcha_page.html', {'form': RecaptchaForm()})

    return my_model_detail_unlimited(request, uuid)


def my_model_detail_unlimited(request, uuid):
    my_model = get_object_or_404(MyModel.objects.select_related('category'), uuid=uuid)

    if my_model.category:
        if my_model.category.name == "Konstitusi":
            return redirect('uud1945_detail', uuid=my_model.uuid)
        elif my_model.category.name == "Qanun":
            return redirect('qanun_detail', uuid=my_model.uuid)

    user_agent = request.user_agent
    device_type = detect_device(user_agent)
    platform = user_agent.os.family
    browser = user_agent.browser.family

    MyModelVisits.objects.create(
        uu=my_model,
        user_ip=get_client_ip(request),
        user=request.user if request.user.is_authenticated else None,
        device_type=device_type,
        platform_type=platform,
        browser_type=browser
    )

    popularity, created = Popularity.objects.get_or_create(uu=my_model)
    popularity.view_count += 1
    popularity.save()

    recent_laws = request.COOKIES.get('recent_laws', '')
    recent_laws_ids = set(recent_laws.split(',') if recent_laws else [])
    recent_laws_ids.add(str(my_model.pk))

    is_bookmarked = False
    if request.user.is_authenticated:
        is_bookmarked = Bookmark.objects.filter(user=request.user, regulation=my_model).exists()

    context = {
        'my_model':      my_model,
        'is_bookmarked': is_bookmarked,
        'popularity':    popularity.view_count,
        # ✅ json.dumps agar bisa dikirim ke JS sebagai LAW_DATA di body.html
        'alinea_data':   json.dumps(my_model.data, ensure_ascii=False),
    }
    response = render(request, 'mymodel_detail.html', context)
    response.set_cookie('recent_laws', ','.join(recent_laws_ids), max_age=30*24*60*60)
    return response


def qanun_detail(request, uuid):
    my_model = get_object_or_404(MyModel.objects.select_related('category'), uuid=uuid)

    user_agent = request.user_agent
    device_type = detect_device(user_agent)
    platform = user_agent.os.family
    browser = user_agent.browser.family

    MyModelVisits.objects.create(
        uu=my_model,
        user_ip=get_client_ip(request),
        user=request.user if request.user.is_authenticated else None,
        device_type=device_type,
        platform_type=platform,
        browser_type=browser
    )

    popularity, created = Popularity.objects.get_or_create(uu=my_model)
    popularity.view_count += 1
    popularity.save()

    recent_laws = request.COOKIES.get('recent_laws', '')
    recent_laws_ids = set(recent_laws.split(',') if recent_laws else [])
    recent_laws_ids.add(str(my_model.pk))

    is_bookmarked = False
    if request.user.is_authenticated:
        is_bookmarked = Bookmark.objects.filter(user=request.user, regulation=my_model).exists()

    context = {
        'my_model': my_model,
        'is_bookmarked': is_bookmarked,
        'popularity': popularity.view_count,
        'alinea_data': my_model.data,
        'is_qanun': True,
    }
    response = render(request, 'regulations/qanun_detail.html', context)
    response.set_cookie('recent_laws', ','.join(recent_laws_ids), max_age=30*24*60*60)
    return response

def reading_mode(request, uuid):
    my_model = get_object_or_404(MyModel.objects.select_related('category', 'year'), uuid=uuid)
    is_bookmarked = (
        request.user.is_authenticated and
        Bookmark.objects.filter(user=request.user, regulation=my_model).exists()
    )
    context = {
        'my_model'    : my_model,
        'is_bookmarked': is_bookmarked,
        'alinea_data' : json.dumps(my_model.data, ensure_ascii=False),
    }
    return render(request, 'regulations/reading_mode.html', context)


def pma_detail(request, uuid):
    try:
        my_model = get_object_or_404(
            MyModel.objects.select_related('category', 'year'),
            uuid=uuid
        )

        user_agent = request.user_agent
        MyModelVisits.objects.create(
            uu=my_model,
            user_ip=get_client_ip(request),
            user=request.user if request.user.is_authenticated else None,
            device_type=detect_device(user_agent),
            platform_type=user_agent.os.family,
            browser_type=user_agent.browser.family,
            visited_at=timezone.now()
        )

        popularity, _ = Popularity.objects.get_or_create(uu=my_model)
        popularity.view_count += 1
        popularity.save()

        recent_laws = request.COOKIES.get('recent_laws', '')
        recent_laws_ids = {x for x in recent_laws.split(',') if x}
        recent_laws_ids.add(str(my_model.pk))

        is_bookmarked = (
            request.user.is_authenticated and
            Bookmark.objects.filter(user=request.user, regulation=my_model).exists()
        )

        context = {
            'my_model': my_model,
            'is_bookmarked': is_bookmarked,
            'popularity': popularity.view_count,
            'pma_data': my_model.data,
            'is_pma': True,
        }
        response = render(request, 'regulations/pma_detail.html', context)
        response.set_cookie(
            'recent_laws', ','.join(recent_laws_ids),
            max_age=30*24*60*60, httponly=True, secure=request.is_secure()
        )
        return response

    except Exception as e:
        logger.error(f"Error in PMA detail view: {str(e)}", exc_info=True,
                     extra={'uuid': uuid, 'user': request.user})
        return render(request, 'errors/500.html', status=500)


def uud1945_detail(request, uuid):
    my_model = get_object_or_404(MyModel, uuid=uuid)

    alinea_data = {}
    for i in range(1, 5):
        key = f"Alinea_{i}"
        alinea_data[key] = my_model.data.get(key, "")

    context = {
        "my_model": my_model,
        "alinea_data": alinea_data,
        "aturan_peralihan": my_model.data.get("aturan_peralihan", {}),
        "aturan_tambahan": my_model.data.get("aturan_tambahan", {}),
    }
    return render(request, "regulations/uud1945_detail.html", context)


# ──────────────────────────────────────────────────────────────────────────────
# Search & List
# ──────────────────────────────────────────────────────────────────────────────

from django.shortcuts import render, redirect, get_object_or_404
from django.db.models import Count, Q
from django.contrib.auth.views import PasswordChangeView, PasswordResetView
from django.urls import reverse_lazy
from rest_framework.views import APIView
from rest_framework.response import Response
from django.utils import timezone
import json
from django.core.files.storage import FileSystemStorage
from django.http import HttpResponse
from .models import Year, Category, MyModel
from django.contrib.auth import authenticate, login
from django.contrib import messages
from .forms import CustomAuthenticationForm
from django.conf import settings
import logging
from datetime import timedelta, datetime
from django.contrib.auth.forms import AuthenticationForm, SetPasswordForm, PasswordChangeForm
from django.urls import reverse
import base64
from io import BytesIO
from django.core.files.base import ContentFile
from PIL import Image
from django.contrib.auth.mixins import LoginRequiredMixin as DjangoLoginRequiredMixin
from django.contrib.auth import get_user_model
from django.contrib.auth import authenticate, login, login as auth_login, logout
from django.contrib.auth.decorators import login_required
from .models import MyModel, Year, MyModelVisits, SearchQuery, SearchTermQuery, Subscriber, Category, Term, Bookmark, UserProfile, Popularity
from django.views.generic import ListView, DetailView
from .serializers import MyModelSerializer
from cryptography.fernet import Fernet, InvalidToken
from django.shortcuts import get_object_or_404
from .models import Term
from django.utils.decorators import method_decorator
from django.core.exceptions import PermissionDenied
from .forms import RecaptchaForm
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.http import JsonResponse
from .forms import SubscribeForm
from .forms import UserSignupForm, UserProfileForm
from django.contrib.auth import update_session_auth_hash
from django.utils.timezone import now
from django.utils.html import escape
from django_user_agents.utils import get_user_agent
from .forms import ContactForm
from django.core.mail import send_mail
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes
from django.contrib.auth.models import User
import re
from rest_framework import viewsets
from .models import Bookmark
from .serializers import BookmarkSerializer
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

from .models import MyModel, Popularity, Bookmark, MyModelVisits
from regulations.utils import get_client_ip
from django_ratelimit.decorators import ratelimit
from regulations.utils.device import detect_device
from django.http import HttpResponseForbidden
from .models import Report
from .forms import ReportForm
from collections import defaultdict
from django.core.cache import cache
from django.utils.text import slugify
import requests

logger = logging.getLogger(__name__)

# Inisialisasi cipher_suite dengan kunci dari settings
cipher_suite = Fernet(settings.ENCRYPTION_KEY)

RECAPTCHA_REQUIRED_SCORE = 0.5
COMMON_WORDS = {"uu", "undang-undang", "peraturan", "putusan"}


# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────

def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        return x_forwarded_for.split(',')[0]
    return request.META.get('REMOTE_ADDR')


def detect_device(user_agent):
    if user_agent.is_mobile:
        return 'Mobile'
    elif user_agent.is_tablet:
        return 'Tablet'
    return 'Desktop'


def is_googlebot(request):
    user_agent = request.META.get('HTTP_USER_AGENT', '').lower()
    return "googlebot" in user_agent


def decrypt_data(encrypted_data):
    if not encrypted_data:
        return ""
    try:
        return cipher_suite.decrypt(encrypted_data.encode()).decode()
    except (InvalidToken, AttributeError, TypeError) as e:
        logging.error(f"Dekripsi gagal: {e}")
        return "Invalid Data"


def encrypt_data(plain_data):
    if not plain_data:
        return ""
    try:
        return cipher_suite.encrypt(plain_data.encode()).decode()
    except Exception as e:
        logging.error(f"Enkripsi gagal: {e}")
        return ""


def clean_search_query(query):
    words = query.lower().split()
    return " ".join(w for w in words if w not in COMMON_WORDS)


def count_occurrences(data, query):
    """Recursively count occurrences of query in nested JSON data."""
    if isinstance(data, dict):
        return sum(count_occurrences(value, query) for value in data.values())
    elif isinstance(data, list):
        return sum(count_occurrences(item, query) for item in data)
    elif isinstance(data, str):
        return data.lower().count(query.lower())
    return 0


def verify_recaptcha(request):
    """Verify reCAPTCHA v2 response. Returns True on success."""
    recaptcha_response = request.POST.get('g-recaptcha-response')
    secret_key = settings.RECAPTCHA_PRIVATE_KEY

    if not recaptcha_response:
        return False

    try:
        result = requests.post(
            'https://www.google.com/recaptcha/api/siteverify',
            data={'secret': secret_key, 'response': recaptcha_response},
            timeout=5,
        ).json()
    except requests.RequestException as e:
        logger.error(f"reCAPTCHA request failed: {e}")
        return False

    return result.get('success', False)


# ──────────────────────────────────────────────────────────────────────────────
# Reports
# ──────────────────────────────────────────────────────────────────────────────

@csrf_exempt
def report_error(request):
    if request.method == 'POST':
        data = json.loads(request.body)
        url = data.get('url')
        description = data.get('description')

        if Report.is_report_limit_reached(url, description):
            return JsonResponse({
                'status': 'error',
                'message': 'Anda telah mencapai batas maksimal laporan untuk URL atau keterangan ini.'
            }, status=400)

        Report.objects.create(
            url=url,
            description=description,
            ip_address=request.META.get('REMOTE_ADDR'),
            user=request.user if request.user.is_authenticated else None,
        )
        return JsonResponse({'status': 'success'}, status=200)
    return JsonResponse({'status': 'error'}, status=400)


# ──────────────────────────────────────────────────────────────────────────────
# Password management
# ──────────────────────────────────────────────────────────────────────────────

@login_required
def password_change_success(request):
    return render(request, 'password_change_success.html')


@login_required
def change_password(request):
    if request.method == 'POST':
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)
            send_mail(
                subject='Password Anda Berhasil Diganti',
                message='Password Anda telah berhasil diganti. Jika ini bukan Anda, harap hubungi kami segera.',
                from_email='noreply@artifilaw.com',
                recipient_list=[user.email],
                fail_silently=False,
            )
            messages.success(request, 'Password berhasil diganti. Kami juga telah mengirimkan notifikasi ke email Anda.')
            return redirect('password_change_done')
    else:
        form = PasswordChangeForm(request.user)
    return render(request, 'registration/password_change_form.html', {'form': form})


class CustomPasswordResetView(PasswordResetView):
    email_template_name = 'registration/password_reset_email.html'
    token_generator = default_token_generator

    def form_valid(self, form):
        email = form.cleaned_data["email"]
        users = self.get_users(email)

        if not users.exists():
            messages.error(self.request, "No user is associated with this email.")
            return self.form_invalid(form)

        for user in users:
            token = self.token_generator.make_token(user)
            uid = urlsafe_base64_encode(force_bytes(user.pk))
            reset_url = self.request.build_absolute_uri(
                reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token})
            )
            self.send_reset_email(user.email, reset_url)

        messages.success(self.request, "Password reset email has been sent.")
        return super().form_valid(form)

    def get_users(self, email):
        UserModel = get_user_model()
        return UserModel.objects.filter(email=email)

    def send_reset_email(self, email, reset_url):
        send_mail(
            "Reset Your Password",
            f"Click the link below to reset your password:\n{reset_url}",
            'noreply@artifilaw.com',
            [email],
            fail_silently=False,
        )


@login_required
def password_settings_view(request):
    if request.method == 'POST':
        form = PasswordChangeForm(user=request.user, data=request.POST)
        if form.is_valid():
            if not request.user.check_password(form.cleaned_data['old_password']):
                form.add_error('old_password', 'Password lama yang Anda masukkan salah.')
            else:
                request.session['old_password'] = request.user.password
                user = form.save()
                update_session_auth_hash(request, user)
                send_password_change_email(request, user)
                messages.success(request, 'Password Anda telah berhasil diperbarui!')
                return redirect('password_settings')
        else:
            messages.error(request, 'Ada kesalahan dalam formulir. Silakan periksa kembali.')
    else:
        form = PasswordChangeForm(user=request.user)
    return render(request, 'password_settings.html', {'form': form})


def send_password_change_email(request, user):
    token = default_token_generator.make_token(user)
    uid = urlsafe_base64_encode(force_bytes(user.pk))
    reset_url = request.build_absolute_uri(
        reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token})
    )
    subject = "Reset Your Password"
    message = f"""
    Hallo {user.username},

    Kami melihat bahwa Anda mungkin tidak melakukan perubahan password. Jika Anda merasa ini adalah tindakan yang tidak diinginkan, silakan reset password Anda dengan mengklik tautan di bawah ini:

    {reset_url}

    Jika Anda tidak ingin melakukan apa pun, abaikan email ini.

    Terima kasih,
    Tim Artifilaw
    """
    send_mail(subject, message, 'noreply@artifilaw.com', [user.email], fail_silently=False)


def password_change_confirm(request, user_id):
    try:
        user = get_user_model().objects.get(id=user_id)

        if 'old_password' not in request.session:
            request.session['old_password'] = user.password

        if request.method == 'POST':
            old_password = request.POST.get('old_password')
            new_password1 = request.POST.get('new_password1')
            new_password2 = request.POST.get('new_password2')

            if not user.check_password(old_password) and user.password != request.session.get('old_password'):
                messages.error(request, "Password lama Anda salah.")
                return redirect('password_change_confirm', user_id=user.id)

            if new_password1 != new_password2:
                messages.error(request, "Konfirmasi password baru tidak cocok.")
                return redirect('password_change_confirm', user_id=user.id)

            user.set_password(new_password1)
            user.save()
            del request.session['old_password']
            messages.success(request, "Password Anda berhasil diperbarui, silahkan login.")
            return redirect('home')
        else:
            return render(request, 'password_change_confirm.html', {'user': user})

    except get_user_model().DoesNotExist:
        return HttpResponse('Pengguna tidak ditemukan', status=404)


# ──────────────────────────────────────────────────────────────────────────────
# Auth
# ──────────────────────────────────────────────────────────────────────────────

def signup_view(request):
    if request.method == 'POST':
        user_form = UserSignupForm(request.POST)
        profile_form = UserProfileForm(request.POST, user_instance=None)

        if not verify_recaptcha(request):
            messages.error(request, "Invalid reCAPTCHA. Please try again.")
            return render(request, 'signup.html', {
                'user_form': user_form,
                'profile_form': profile_form,
                'RECAPTCHA_PUBLIC_KEY': settings.RECAPTCHA_PUBLIC_KEY
            })

        if user_form.is_valid() and profile_form.is_valid():
            user = user_form.save(commit=False)
            user.set_password(user_form.cleaned_data['password'])
            user.is_active = False
            user.save()

            profile = profile_form.save(commit=False)
            profile.user = user
            profile.save()

            send_verification_email(request, user)
            messages.success(request, "Registration successful! Please check your email for the verification token.")
            return redirect(reverse('verify_email', kwargs={'user_id': user.pk}))
        else:
            print(user_form.errors)
            print(profile_form.errors)
    else:
        user_form = UserSignupForm()
        profile_form = UserProfileForm(user_instance=None)

    return render(request, 'signup.html', {
        'user_form': user_form,
        'profile_form': profile_form,
        'RECAPTCHA_PUBLIC_KEY': settings.RECAPTCHA_PUBLIC_KEY
    })


def send_verification_email(request, user):
    token = default_token_generator.make_token(user)
    prefix = token[:6]

    cache.set(f"email_verification_{user.pk}_prefix", prefix, timeout=900)
    cache.set(f"email_verification_{user.pk}_full", token, timeout=900)

    verification_url = request.build_absolute_uri(
        reverse('verify_email', kwargs={'user_id': user.pk})
    )
    send_mail(
        "Verify Your Email Address",
        (
            f"Hi {user.username},\n\n"
            f"Here is your verification code: {prefix}\n\n"
            f"Click the link below to complete the verification:\n\n{verification_url}"
        ),
        'noreply@artifilaw.com',
        [user.email],
        fail_silently=False,
    )


def verify_email_view(request, user_id):
    if request.method == 'POST':
        prefix_input = request.POST.get('prefix')
        try:
            user = User.objects.get(pk=user_id)
            prefix_stored = cache.get(f"email_verification_{user.pk}_prefix")
            full_token_stored = cache.get(f"email_verification_{user.pk}_full")

            if prefix_input == prefix_stored and full_token_stored:
                user.is_active = True
                user.save()
                cache.delete(f"email_verification_{user.pk}_prefix")
                cache.delete(f"email_verification_{user.pk}_full")
                messages.success(request, "Email verified successfully, login!")
                return redirect('login')
            else:
                messages.error(request, "Invalid token.")
        except User.DoesNotExist:
            messages.error(request, "Invalid user.")

    return render(request, 'verify_email.html', {'user_id': user_id})


@csrf_exempt
def resend_verification_email(request, user_id):
    try:
        user = User.objects.get(pk=user_id)
        cooldown_key = f"resend_cooldown_{user.pk}"
        last_request_time = cache.get(cooldown_key)

        if last_request_time:
            remaining_time = (last_request_time + timedelta(seconds=60)) - now()
            if remaining_time.total_seconds() > 0:
                messages.error(request, f"Please wait {int(remaining_time.total_seconds())} seconds before requesting again.")
                return redirect('verify_email', user_id=user_id)

        if not user.is_active:
            send_verification_email(request, user)
            cache.set(cooldown_key, now(), timeout=60)
            messages.success(request, "A new verification email has been sent.")
        else:
            messages.info(request, "Your email is already verified.")
    except User.DoesNotExist:
        messages.error(request, "User does not exist.")

    return redirect('verify_email', user_id=user_id)


def custom_login_view(request):
    next_url = request.POST.get('next', request.GET.get('next', '/'))

    if request.method == 'POST':
        form = CustomAuthenticationForm(request, data=request.POST)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            user = authenticate(request, username=username, password=password)

            if user is None:
                messages.error(request, 'Nama pengguna atau kata sandi salah.')
            elif not user.is_active:
                messages.warning(request, 'Akun Anda tidak aktif. Hubungi administrator untuk bantuan.')
            else:
                login(request, user)
                return redirect(next_url)
    else:
        form = CustomAuthenticationForm(request)

    return render(request, 'login.html', {
        'form': form,
        'next': next_url,
        'RECAPTCHA_PUBLIC_KEY': settings.RECAPTCHA_PUBLIC_KEY
    })


def logout_view(request):
    logout(request)
    return redirect('home')


# ──────────────────────────────────────────────────────────────────────────────
# Bookmarks
# ──────────────────────────────────────────────────────────────────────────────

class BookmarkViewSet(viewsets.ModelViewSet):
    queryset = Bookmark.objects.all()
    serializer_class = BookmarkSerializer

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()


@login_required
def toggle_bookmark(request, pk):
    my_model = get_object_or_404(MyModel, pk=pk)
    user = request.user

    bookmark, created = Bookmark.objects.get_or_create(
        user=user,
        regulation=my_model,
        defaults={'url': request.build_absolute_uri(request.get_full_path())}
    )

    if not created:
        bookmark.delete()
        is_bookmarked = False
    else:
        is_bookmarked = True
        if request.method == 'POST':
            try:
                data = json.loads(request.body)
                scroll_position = data.get('scroll_position')
                if scroll_position is not None:
                    bookmark.scroll_position = scroll_position
                    bookmark.save()
            except (json.JSONDecodeError, AttributeError):
                pass

    if request.headers.get('x-requested-with') == 'XMLHttpRequest':
        return JsonResponse({'is_bookmarked': is_bookmarked})
    return redirect('my_model_detail', pk=pk)


@login_required
def delete_bookmark(request, pk):
    bookmark = get_object_or_404(Bookmark, pk=pk, user=request.user)
    bookmark.delete()
    return redirect('profile')


# ──────────────────────────────────────────────────────────────────────────────
# Subscribe
# ──────────────────────────────────────────────────────────────────────────────

def subscribe(request):
    if request.method == 'POST':
        email = request.POST.get('email')
        if email:
            try:
                Subscriber.objects.create(email=email)
                messages.success(request, 'You have successfully subscribed!')
            except Exception:
                messages.error(request, 'Subscription failed. Email might already exist.')
        else:
            messages.error(request, 'Email is required.')
    return redirect('home')


# ──────────────────────────────────────────────────────────────────────────────
# Profile & Settings
# ──────────────────────────────────────────────────────────────────────────────

@login_required
def profile_settings_view(request):
    profile, created = UserProfile.objects.get_or_create(user=request.user)

    if request.method == 'POST':
        form = UserProfileForm(request.POST, request.FILES, instance=profile, user_instance=request.user)
        delete_picture = request.POST.get('delete_picture') == 'true'
        current_picture = request.POST.get('current_picture')

        if form.is_valid():
            if delete_picture:
                profile.profile_picture = None
            elif 'profile_picture' in request.FILES:
                img = request.FILES['profile_picture']
                image_data = img.read()
                profile.profile_picture = base64.b64encode(image_data).decode('utf-8')
            elif current_picture:
                profile.profile_picture = current_picture

            form.save()
            messages.success(request, 'Your profile has been updated successfully!')
            return redirect('profile_settings')
        else:
            messages.error(request, 'Please correct the errors below.')
    else:
        form = UserProfileForm(instance=profile, user_instance=request.user)

    return render(request, 'profile_settings.html', {'form': form, 'profile': profile})


@login_required
def settings_view(request):
    try:
        profile = UserProfile.objects.get(user=request.user)
    except UserProfile.DoesNotExist:
        profile = UserProfile(user=request.user)

    if request.method == 'POST':
        form = UserProfileForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():
            form.save()
            messages.success(request, 'Your profile has been updated successfully!')
            return redirect('settings')
    else:
        form = UserProfileForm(instance=profile)

    return render(request, 'settings.html', {'form': form})


def profile_view(request):
    user = request.user
    bookmarks = Bookmark.objects.filter(user=user)

    try:
        user_profile = UserProfile.objects.get(user=user)
        user_profile.first_name = decrypt_data(user_profile.first_name)
        user_profile.last_name = decrypt_data(user_profile.last_name)
        user_profile.job = decrypt_data(user_profile.job)
        user_profile.province = decrypt_data(user_profile.province)
        logging.info(f"Profile data setelah dekripsi: {user_profile.__dict__}")
    except UserProfile.DoesNotExist:
        user_profile = None

    recent_activities = MyModelVisits.objects.filter(user=user).order_by('-visited_at')[:5]
    total_bookmarks = bookmarks.count()
    total_visited = MyModelVisits.objects.filter(user=user).count()

    settings_active = request.path.startswith('/settings/')
    profile_url = reverse('profile_settings')
    password_url = reverse('password_settings')

    context = {
        'bookmarks': bookmarks,
        'user_profile': user_profile,
        'recent_activities': recent_activities,
        'total_bookmarks': total_bookmarks,
        'total_visited': total_visited,
        'settings_active': settings_active,
        'profile_url': profile_url,
        'password_url': password_url,
        'profile_active': request.path == profile_url,
        'password_active': request.path == password_url,
    }
    return render(request, 'profile.html', context)


# ──────────────────────────────────────────────────────────────────────────────
# Regulation detail views
# ──────────────────────────────────────────────────────────────────────────────

@ratelimit(key='ip', rate='10/m', method='GET', block=False)
def my_model_detail(request, uuid):
    if is_googlebot(request):
        return my_model_detail_unlimited(request, uuid)

    was_limited = getattr(request, 'limited', False)
    if was_limited and not request.session.get('recaptcha_passed', False):
        if request.method == "POST":
            form = RecaptchaForm(request.POST)
            if form.is_valid():
                request.session['recaptcha_passed'] = True
                return redirect(request.path)
        else:
            return render(request, 'captcha_page.html', {'form': RecaptchaForm()})

    return my_model_detail_unlimited(request, uuid)


def my_model_detail_unlimited(request, uuid):
    my_model = get_object_or_404(MyModel.objects.select_related('category'), uuid=uuid)

    if my_model.category:
        if my_model.category.name == "Konstitusi":
            return redirect('uud1945_detail', uuid=my_model.uuid)
        elif my_model.category.name == "Qanun":
            return redirect('qanun_detail', uuid=my_model.uuid)

    user_agent = request.user_agent
    device_type = detect_device(user_agent)
    platform = user_agent.os.family
    browser = user_agent.browser.family

    MyModelVisits.objects.create(
        uu=my_model,
        user_ip=get_client_ip(request),
        user=request.user if request.user.is_authenticated else None,
        device_type=device_type,
        platform_type=platform,
        browser_type=browser
    )

    popularity, created = Popularity.objects.get_or_create(uu=my_model)
    popularity.view_count += 1
    popularity.save()

    recent_laws = request.COOKIES.get('recent_laws', '')
    recent_laws_ids = set(recent_laws.split(',') if recent_laws else [])
    recent_laws_ids.add(str(my_model.pk))

    is_bookmarked = False
    if request.user.is_authenticated:
        is_bookmarked = Bookmark.objects.filter(user=request.user, regulation=my_model).exists()

    context = {
        'my_model': my_model,
        'is_bookmarked': is_bookmarked,
        'popularity': popularity.view_count,
        'alinea_data': my_model.data,
    }
    response = render(request, 'mymodel_detail.html', context)
    response.set_cookie('recent_laws', ','.join(recent_laws_ids), max_age=30*24*60*60)
    return response


def qanun_detail(request, uuid):
    my_model = get_object_or_404(MyModel.objects.select_related('category'), uuid=uuid)

    user_agent = request.user_agent
    device_type = detect_device(user_agent)
    platform = user_agent.os.family
    browser = user_agent.browser.family

    MyModelVisits.objects.create(
        uu=my_model,
        user_ip=get_client_ip(request),
        user=request.user if request.user.is_authenticated else None,
        device_type=device_type,
        platform_type=platform,
        browser_type=browser
    )

    popularity, created = Popularity.objects.get_or_create(uu=my_model)
    popularity.view_count += 1
    popularity.save()

    recent_laws = request.COOKIES.get('recent_laws', '')
    recent_laws_ids = set(recent_laws.split(',') if recent_laws else [])
    recent_laws_ids.add(str(my_model.pk))

    is_bookmarked = False
    if request.user.is_authenticated:
        is_bookmarked = Bookmark.objects.filter(user=request.user, regulation=my_model).exists()

    context = {
        'my_model': my_model,
        'is_bookmarked': is_bookmarked,
        'popularity': popularity.view_count,
        'alinea_data': my_model.data,
        'is_qanun': True,
    }
    response = render(request, 'regulations/qanun_detail.html', context)
    response.set_cookie('recent_laws', ','.join(recent_laws_ids), max_age=30*24*60*60)
    return response


def pma_detail(request, uuid):
    try:
        my_model = get_object_or_404(
            MyModel.objects.select_related('category', 'year'),
            uuid=uuid
        )

        user_agent = request.user_agent
        MyModelVisits.objects.create(
            uu=my_model,
            user_ip=get_client_ip(request),
            user=request.user if request.user.is_authenticated else None,
            device_type=detect_device(user_agent),
            platform_type=user_agent.os.family,
            browser_type=user_agent.browser.family,
            visited_at=timezone.now()
        )

        popularity, _ = Popularity.objects.get_or_create(uu=my_model)
        popularity.view_count += 1
        popularity.save()

        recent_laws = request.COOKIES.get('recent_laws', '')
        recent_laws_ids = {x for x in recent_laws.split(',') if x}
        recent_laws_ids.add(str(my_model.pk))

        is_bookmarked = (
            request.user.is_authenticated and
            Bookmark.objects.filter(user=request.user, regulation=my_model).exists()
        )

        context = {
            'my_model': my_model,
            'is_bookmarked': is_bookmarked,
            'popularity': popularity.view_count,
            'pma_data': my_model.data,
            'is_pma': True,
        }
        response = render(request, 'regulations/pma_detail.html', context)
        response.set_cookie(
            'recent_laws', ','.join(recent_laws_ids),
            max_age=30*24*60*60, httponly=True, secure=request.is_secure()
        )
        return response

    except Exception as e:
        logger.error(f"Error in PMA detail view: {str(e)}", exc_info=True,
                     extra={'uuid': uuid, 'user': request.user})
        return render(request, 'errors/500.html', status=500)


def uud1945_detail(request, uuid):
    my_model = get_object_or_404(MyModel, uuid=uuid)

    alinea_data = {}
    for i in range(1, 5):
        key = f"Alinea_{i}"
        alinea_data[key] = my_model.data.get(key, "")

    context = {
        "my_model": my_model,
        "alinea_data": alinea_data,
        "aturan_peralihan": my_model.data.get("aturan_peralihan", {}),
        "aturan_tambahan": my_model.data.get("aturan_tambahan", {}),
    }
    return render(request, "regulations/uud1945_detail.html", context)


# ──────────────────────────────────────────────────────────────────────────────
# Search & List
# ──────────────────────────────────────────────────────────────────────────────

def _run_search(request, clean_query=False):
    """
    Strategi ID-first untuk menghindari MySQL sort buffer error:

    1. MySQL hanya diminta filter → kembalikan list ID (integer ringan)
    2. Semua sorting dilakukan di Python, bukan MySQL
    3. Pagination dijalankan di Python terhadap list ID
    4. Baru ambil data lengkap HANYA untuk 20 item di halaman ini
       → MySQL tidak pernah sort JSON besar sama sekali
    """
    # ── Parameter ────────────────────────────────────────────────────────────
    title_query       = request.GET.get('judul', '').strip()
    full_text_query   = request.GET.get('q', '').strip()[:255]
    selected_category = request.GET.get('category', '').strip()
    selected_year     = request.GET.get('tahun', '').strip()
    sort_by           = request.GET.get('sort_by', 'id')

    if clean_query:
        full_text_query = clean_search_query(full_text_query)

    if sort_by not in ['id', 'title', 'year', 'popularity']:
        sort_by = 'id'

    if full_text_query:
        SearchQuery.objects.create(query=full_text_query)

    categories = Category.objects.all()
    years      = Year.objects.all().order_by('-year')

    # ── Bangun filter ────────────────────────────────────────────────────────
    filters = Q()

    if title_query:
        filters &= Q(data__icontains=title_query)

    if full_text_query:
        filters &= Q(data__icontains=full_text_query)

    if selected_category:
        filters &= Q(category__name__iexact=selected_category)

    if selected_year:
        try:
            year_instance = Year.objects.get(year=int(selected_year))
            filters &= Q(year=year_instance)
        except (ValueError, TypeError, Year.DoesNotExist):
            selected_year = ''

    # ── Step 1: ambil ID saja — MySQL tidak sort, hanya filter ───────────────
    # values_list('id') → query ringan, tidak load JSON sama sekali
    start_time   = datetime.now()
    filtered_ids = list(
        MyModel.objects
        .filter(filters)
        .values_list('id', flat=True)
        .distinct()
    )
    search_time = (datetime.now() - start_time).total_seconds()

    # ── Step 2: sort ID di Python ─────────────────────────────────────────────
    if full_text_query:
        # Perlu hitung occurrence → ambil data semua ID yang cocok
        # Hanya kolom 'data' yang dibutuhkan untuk count
        objs = list(
            MyModel.objects
            .filter(id__in=filtered_ids)
            .only('id', 'uuid', 'data', 'category_id', 'year_id')
            .select_related('category', 'year')
        )
        for obj in objs:
            obj.count = count_occurrences(obj.data, full_text_query)
        sorted_objs = sorted(objs, key=lambda x: x.count, reverse=True)

    elif sort_by == 'popularity':
        # Popularity perlu annotate → biarkan MySQL hitung COUNT integer (ringan)
        sorted_objs = list(
            MyModel.objects
            .filter(id__in=filtered_ids)
            .only('id', 'uuid', 'data', 'category_id', 'year_id')
            .select_related('category', 'year')
            .annotate(num_visits=Count('mymodelvisits'))
            .order_by('-num_visits')
        )

    else:
        # Ambil data semua ID, sort di Python — tidak ada ORDER BY ke MySQL
        objs = list(
            MyModel.objects
            .filter(id__in=filtered_ids)
            .only('id', 'uuid', 'data', 'category_id', 'year_id')
            .select_related('category', 'year')
        )
        if sort_by == 'title':
            sorted_objs = sorted(objs, key=lambda x: x.data.get('Tentang', ''))
        elif sort_by == 'year':
            sorted_objs = sorted(objs, key=lambda x: x.year.year if x.year else 0, reverse=True)
        else:
            # Default: terbaru dulu (sort by id DESC di Python)
            sorted_objs = sorted(objs, key=lambda x: x.id, reverse=True)

    # ── Step 3: pagination terhadap list Python (bukan queryset MySQL) ────────
    paginator   = Paginator(sorted_objs, 20)
    page_number = request.GET.get('page', 1)
    try:
        page_number = max(1, int(page_number))
    except (ValueError, TypeError):
        page_number = 1
    try:
        page_obj = paginator.page(page_number)
    except (PageNotAnInteger, EmptyPage):
        page_obj = paginator.page(1)

    return render(request, 'uu_list.html', {
        'query':             full_text_query,
        'judul':             title_query,
        'selected_category': selected_category,
        'selected_year':     selected_year,
        'categories':        categories,
        'years':             years,
        'mymodel_instances': page_obj,
        'sort_by':           sort_by,
        'search_time':       search_time,
        'page_obj':          page_obj,
    })


def uu_list(request):
    try:
        return _run_search(request, clean_query=False)
    except Exception as e:
        logger.exception("Error in uu_list: %s", e)
        return HttpResponse("Internal Server Error", status=500)


def search(request):
    try:
        return _run_search(request, clean_query=True)
    except Exception as e:
        logger.exception("Error in search: %s", e)
        return HttpResponse("Internal Server Error", status=500)


# ──────────────────────────────────────────────────────────────────────────────
# File upload & Terms
# ──────────────────────────────────────────────────────────────────────────────

def create_unique_slug(term_name):
    base_slug = slugify(term_name)
    slug = base_slug
    counter = 1
    while Term.objects.filter(slug=slug).exists():
        slug = f"{base_slug}-{counter}"
        counter += 1
    return slug


def clean_definition(definition):
    return re.sub(r'[^a-zA-Z0-9\s,/\\-]', '', definition).strip()


def extract_and_save_terms(my_model_instance):
    """Mengekstrak data leksikon dari JSON dan menyimpannya ke dalam model Term."""
    data = my_model_instance.data
    leksikon = data.get('leksikon', {})

    for term_name, definition in leksikon.items():
        if len(term_name) > 255:
            term_name = term_name[:255]

        cleaned_definition = clean_definition(definition)
        slug = create_unique_slug(term_name)

        term, created = Term.objects.get_or_create(
            name=term_name,
            definition=cleaned_definition,
            defaults={'slug': slug}
        )
        term.my_models.add(my_model_instance)


def upload_file(request):
    if request.method == 'POST':
        uploaded_files = request.FILES.getlist('files')
        category_id = request.POST.get('category')
        category = Category.objects.get(id=category_id) if category_id else None

        error_messages = []
        success_count  = 0
        skipped_files  = []

        for uploaded_file in uploaded_files:
            if not uploaded_file.name.endswith('.json'):
                error_messages.append(f"File '{uploaded_file.name}' bukan file JSON.")
                continue

            fs = FileSystemStorage()
            filename = fs.save(uploaded_file.name, uploaded_file)
            file_path = fs.path(filename)

            try:
                with open(file_path, 'r', encoding='utf-8') as json_file:
                    data = json.load(json_file)

                nomor   = data.get('Nomor')
                tahun   = data.get('Tahun')
                tentang = data.get('Tentang')

                if MyModel.objects.filter(data__Nomor=nomor, data__Tahun=tahun, data__Tentang=tentang).exists():
                    skipped_files.append(uploaded_file.name)
                    continue

                try:
                    tahun_value = int(data.get('Tahun', '0'))
                    year, created = Year.objects.get_or_create(year=tahun_value)
                except ValueError:
                    error_messages.append(f"File '{uploaded_file.name}': Format tahun tidak valid.")
                    continue

                my_model_instance = MyModel(data=data, category=category, year=year)
                my_model_instance.save()
                extract_and_save_terms(my_model_instance)
                success_count += 1

            except json.JSONDecodeError:
                error_messages.append(f"File '{uploaded_file.name}': Format JSON tidak valid.")
            except Exception as e:
                error_messages.append(f"File '{uploaded_file.name}': Terjadi kesalahan - {str(e)}")
            finally:
                fs.delete(filename)

        success_message = f"Berhasil mengunggah {success_count} file." if success_count > 0 else None
        skipped_message = f"File berikut dilewati karena data sudah ada: {', '.join(skipped_files)}" if skipped_files else None

        return render(request, 'upload.html', {
            'error_messages':  error_messages,
            'success_message': success_message,
            'skipped_message': skipped_message,
            'categories':      Category.objects.all(),
        })

    return render(request, 'upload.html', {'categories': Category.objects.all()})


# ──────────────────────────────────────────────────────────────────────────────
# Terms (leksikon)
# ──────────────────────────────────────────────────────────────────────────────

class LoginRequiredMixin(DjangoLoginRequiredMixin):
    login_url = '/login/'


class TermListView(LoginRequiredMixin, ListView):
    model               = Term
    template_name       = 'terms/terms_list.html'
    context_object_name = 'grouped_terms'
    paginate_by         = 10

    def get_queryset(self):
        queryset = Term.objects.all().order_by('name')
        query    = self.request.GET.get('q')

        if query:
            search_query = SearchTermQuery.objects.create(
                query=query,
                created_at=timezone.now(),
                status='found'
            )
            queryset = queryset.filter(name__icontains=query)
            if not queryset.exists():
                search_query.status = 'not_found'
                search_query.save()

        return queryset

    def get_context_data(self, **kwargs):
        context  = super().get_context_data(**kwargs)
        query    = self.request.GET.get('q')
        queryset = self.get_queryset()

        if not query:
            grouped_terms = defaultdict(list)
            unique_terms  = {}
            for term in queryset:
                if term.name not in unique_terms:
                    unique_terms[term.name] = term
                    first_letter = term.name[0].upper()
                    if len(grouped_terms[first_letter]) < 10:
                        grouped_terms[first_letter].append(term)
            context['grouped_terms'] = dict(grouped_terms)
        else:
            unique_terms    = {}
            filtered_terms  = []
            for term in queryset:
                if term.name not in unique_terms:
                    unique_terms[term.name] = term
                    filtered_terms.append(term)
            context['grouped_terms']  = {'Results': filtered_terms}
            context['center_results'] = True

            same_name_terms           = Term.objects.filter(name__iexact=query).distinct()
            context['same_name_terms'] = same_name_terms
            related_regulations        = []
            for term in same_name_terms:
                if hasattr(term, 'regulation'):
                    related_regulations.extend(term.regulation.all())
            context['related_regulations'] = related_regulations

        context['query'] = query
        return context


def latest_uu_view(request):
    latest_uus = MyModel.objects.all().order_by('-id')[:6]
    return render(request, 'latest_uus.html', {'latest_uus': latest_uus})


@method_decorator(never_cache, name='dispatch')
class TermDetailView(DetailView):
    model               = Term
    template_name       = 'terms/term_detail.html'
    context_object_name = 'term'
    slug_field          = 'slug'
    slug_url_kwarg      = 'slug'

    def get(self, request, *args, **kwargs):
        was_limited = getattr(request, 'limited', False)

        if was_limited and not request.session.get('recaptcha_passed', False):
            if request.method == "POST":
                form = RecaptchaForm(request.POST)
                if form.is_valid():
                    request.session['recaptcha_passed'] = True
                    request.session.set_expiry(3600)
                    ip              = request.META.get('REMOTE_ADDR')
                    email           = request.POST.get('email', '')
                    cache.delete(f"form_submission_ip_{ip}")
                    cache.delete(f"form_submission_email_{email}")
                    return redirect(request.path)
            else:
                return render(request, 'captcha_page.html', {'form': RecaptchaForm()})

        if request.session.get('recaptcha_passed', False):
            request.limited = False

        return super().get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        term    = self.object

        same_name_terms   = Term.objects.filter(name__iexact=term.name).exclude(id=term.id).distinct()
        unique_definitions = {}
        for same_term in same_name_terms:
            key = same_term.definition.lower()
            if key not in unique_definitions:
                unique_definitions[key] = same_term

        context['same_name_terms'] = list(unique_definitions.values())

        related_regulations       = []
        for same_term in same_name_terms:
            related_regulations.extend(same_term.my_models.all())

        active_regulation_uuid = self.request.GET.get('active_regulation')
        for regulation in related_regulations:
            regulation.is_active = (str(regulation.uuid) == active_regulation_uuid)

        context['active_regulations']  = term.my_models.all()
        context['related_regulations'] = related_regulations
        return context


# ──────────────────────────────────────────────────────────────────────────────
# Misc
# ──────────────────────────────────────────────────────────────────────────────

def index_view(request):
    return render(request, 'index.html')


def home_view(request):
    recent_laws = request.COOKIES.get('recent_laws', '')
    if recent_laws:
        recent_laws_ids     = recent_laws.split(',')
        recent_laws_objects = MyModel.objects.filter(pk__in=recent_laws_ids).last()
    else:
        recent_laws_objects = None

    client_ip             = get_client_ip(request)
    recent_visits         = MyModelVisits.objects.exclude(user_ip=client_ip).order_by('-visited_at')[:1]
    recent_visited_laws   = MyModel.objects.filter(pk__in=recent_visits.values_list('uu', flat=True))

    context = {
        'recent_laws':        recent_laws_objects,
        'most_viewed_laws':   MyModel.objects.annotate(num_visits=Count('mymodelvisits')).order_by('-num_visits')[:1],
        'recent_visited_laws': recent_visited_laws,
        'popular_searches':   SearchQuery.objects.values('query').annotate(query_count=Count('query')).order_by('-query_count')[:1],
        'popular_items':      Popularity.objects.order_by('-view_count')[:1],
    }
    return render(request, 'home.html', context)


def success(request):
    return render(request, 'success.html')


def privacy_policy(request):
    return render(request, 'privacy_policy.html')


def terms_conditions(request):
    return render(request, 'terms_conditions.html')


def contacts_view(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            form.save()
            return render(request, 'thank_you.html')
    else:
        form = ContactForm()
    return render(request, 'contacts.html', {'form': form})


def about_view(request):
    return render(request, 'about.html')


class CategoryListView(ListView):
    model               = Category
    template_name       = 'categories.html'
    context_object_name = 'categories'


# ──────────────────────────────────────────────────────────────────────────────
# DRF
# ──────────────────────────────────────────────────────────────────────────────

class MyModelDetailView(APIView):
    def get(self, request, pk):
        try:
            my_model   = MyModel.objects.get(pk=pk)
            serializer = MyModelSerializer(my_model)
            return Response(serializer.data)
        except MyModel.DoesNotExist:
            return Response({"error": "Data tidak ditemukan"}, status=404)


# ──────────────────────────────────────────────────────────────────────────────
# File upload & Terms
# ──────────────────────────────────────────────────────────────────────────────

def create_unique_slug(term_name):
    base_slug = slugify(term_name)
    slug = base_slug
    counter = 1
    while Term.objects.filter(slug=slug).exists():
        slug = f"{base_slug}-{counter}"
        counter += 1
    return slug


def clean_definition(definition):
    return re.sub(r'[^a-zA-Z0-9\s,/\\-]', '', definition).strip()


def extract_and_save_terms(my_model_instance):
    """Mengekstrak data leksikon dari JSON dan menyimpannya ke dalam model Term."""
    data = my_model_instance.data
    leksikon = data.get('leksikon', {})

    for term_name, definition in leksikon.items():
        if len(term_name) > 255:
            term_name = term_name[:255]

        cleaned_definition = clean_definition(definition)
        slug = create_unique_slug(term_name)

        term, created = Term.objects.get_or_create(
            name=term_name,
            definition=cleaned_definition,
            defaults={'slug': slug}
        )
        term.my_models.add(my_model_instance)


def upload_file(request):
    if request.method == 'POST':
        uploaded_files = request.FILES.getlist('files')
        category_id = request.POST.get('category')
        category = Category.objects.get(id=category_id) if category_id else None

        error_messages = []
        success_count  = 0
        skipped_files  = []

        for uploaded_file in uploaded_files:
            if not uploaded_file.name.endswith('.json'):
                error_messages.append(f"File '{uploaded_file.name}' bukan file JSON.")
                continue

            fs = FileSystemStorage()
            filename = fs.save(uploaded_file.name, uploaded_file)
            file_path = fs.path(filename)

            try:
                with open(file_path, 'r', encoding='utf-8') as json_file:
                    data = json.load(json_file)

                nomor   = data.get('Nomor')
                tahun   = data.get('Tahun')
                tentang = data.get('Tentang')

                if MyModel.objects.filter(data__Nomor=nomor, data__Tahun=tahun, data__Tentang=tentang).exists():
                    skipped_files.append(uploaded_file.name)
                    continue

                try:
                    tahun_value = int(data.get('Tahun', '0'))
                    year, created = Year.objects.get_or_create(year=tahun_value)
                except ValueError:
                    error_messages.append(f"File '{uploaded_file.name}': Format tahun tidak valid.")
                    continue

                my_model_instance = MyModel(data=data, category=category, year=year)
                my_model_instance.save()
                extract_and_save_terms(my_model_instance)
                success_count += 1

            except json.JSONDecodeError:
                error_messages.append(f"File '{uploaded_file.name}': Format JSON tidak valid.")
            except Exception as e:
                error_messages.append(f"File '{uploaded_file.name}': Terjadi kesalahan - {str(e)}")
            finally:
                fs.delete(filename)

        success_message = f"Berhasil mengunggah {success_count} file." if success_count > 0 else None
        skipped_message = f"File berikut dilewati karena data sudah ada: {', '.join(skipped_files)}" if skipped_files else None

        return render(request, 'upload.html', {
            'error_messages':  error_messages,
            'success_message': success_message,
            'skipped_message': skipped_message,
            'categories':      Category.objects.all(),
        })

    return render(request, 'upload.html', {'categories': Category.objects.all()})


# ──────────────────────────────────────────────────────────────────────────────
# Terms (leksikon)
# ──────────────────────────────────────────────────────────────────────────────

class LoginRequiredMixin(DjangoLoginRequiredMixin):
    login_url = '/login/'


class TermListView(LoginRequiredMixin, ListView):
    model               = Term
    template_name       = 'terms/terms_list.html'
    context_object_name = 'grouped_terms'
    paginate_by         = 10

    def get_queryset(self):
        queryset = Term.objects.all().order_by('name')
        query    = self.request.GET.get('q')

        if query:
            search_query = SearchTermQuery.objects.create(
                query=query,
                created_at=timezone.now(),
                status='found'
            )
            queryset = queryset.filter(name__icontains=query)
            if not queryset.exists():
                search_query.status = 'not_found'
                search_query.save()

        return queryset

    def get_context_data(self, **kwargs):
        context  = super().get_context_data(**kwargs)
        query    = self.request.GET.get('q')
        queryset = self.get_queryset()

        if not query:
            grouped_terms = defaultdict(list)
            unique_terms  = {}
            for term in queryset:
                if term.name not in unique_terms:
                    unique_terms[term.name] = term
                    first_letter = term.name[0].upper()
                    if len(grouped_terms[first_letter]) < 10:
                        grouped_terms[first_letter].append(term)
            context['grouped_terms'] = dict(grouped_terms)
        else:
            unique_terms    = {}
            filtered_terms  = []
            for term in queryset:
                if term.name not in unique_terms:
                    unique_terms[term.name] = term
                    filtered_terms.append(term)
            context['grouped_terms']  = {'Results': filtered_terms}
            context['center_results'] = True

            same_name_terms           = Term.objects.filter(name__iexact=query).distinct()
            context['same_name_terms'] = same_name_terms
            related_regulations        = []
            for term in same_name_terms:
                if hasattr(term, 'regulation'):
                    related_regulations.extend(term.regulation.all())
            context['related_regulations'] = related_regulations

        context['query'] = query
        return context


def latest_uu_view(request):
    latest_uus = MyModel.objects.all().order_by('-id')[:6]
    return render(request, 'latest_uus.html', {'latest_uus': latest_uus})


@method_decorator(never_cache, name='dispatch')
class TermDetailView(DetailView):
    model               = Term
    template_name       = 'terms/term_detail.html'
    context_object_name = 'term'
    slug_field          = 'slug'
    slug_url_kwarg      = 'slug'

    def get(self, request, *args, **kwargs):
        was_limited = getattr(request, 'limited', False)

        if was_limited and not request.session.get('recaptcha_passed', False):
            if request.method == "POST":
                form = RecaptchaForm(request.POST)
                if form.is_valid():
                    request.session['recaptcha_passed'] = True
                    request.session.set_expiry(3600)
                    ip              = request.META.get('REMOTE_ADDR')
                    email           = request.POST.get('email', '')
                    cache.delete(f"form_submission_ip_{ip}")
                    cache.delete(f"form_submission_email_{email}")
                    return redirect(request.path)
            else:
                return render(request, 'captcha_page.html', {'form': RecaptchaForm()})

        if request.session.get('recaptcha_passed', False):
            request.limited = False

        return super().get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        term    = self.object

        same_name_terms   = Term.objects.filter(name__iexact=term.name).exclude(id=term.id).distinct()
        unique_definitions = {}
        for same_term in same_name_terms:
            key = same_term.definition.lower()
            if key not in unique_definitions:
                unique_definitions[key] = same_term

        context['same_name_terms'] = list(unique_definitions.values())

        related_regulations       = []
        for same_term in same_name_terms:
            related_regulations.extend(same_term.my_models.all())

        active_regulation_uuid = self.request.GET.get('active_regulation')
        for regulation in related_regulations:
            regulation.is_active = (str(regulation.uuid) == active_regulation_uuid)

        context['active_regulations']  = term.my_models.all()
        context['related_regulations'] = related_regulations
        return context


# ──────────────────────────────────────────────────────────────────────────────
# Misc
# ──────────────────────────────────────────────────────────────────────────────

def index_view(request):
    return render(request, 'index.html')


def home_view(request):
    recent_laws = request.COOKIES.get('recent_laws', '')
    if recent_laws:
        recent_laws_ids     = recent_laws.split(',')
        recent_laws_objects = MyModel.objects.filter(pk__in=recent_laws_ids).last()
    else:
        recent_laws_objects = None

    client_ip             = get_client_ip(request)
    recent_visits         = MyModelVisits.objects.exclude(user_ip=client_ip).order_by('-visited_at')[:1]
    recent_visited_laws   = MyModel.objects.filter(pk__in=recent_visits.values_list('uu', flat=True))

    context = {
        'recent_laws':        recent_laws_objects,
        'most_viewed_laws':   MyModel.objects.annotate(num_visits=Count('mymodelvisits')).order_by('-num_visits')[:1],
        'recent_visited_laws': recent_visited_laws,
        'popular_searches':   SearchQuery.objects.values('query').annotate(query_count=Count('query')).order_by('-query_count')[:1],
        'popular_items':      Popularity.objects.order_by('-view_count')[:1],
    }
    return render(request, 'home.html', context)


def success(request):
    return render(request, 'success.html')


def privacy_policy(request):
    return render(request, 'privacy_policy.html')


def terms_conditions(request):
    return render(request, 'terms_conditions.html')


def contacts_view(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            form.save()
            return render(request, 'thank_you.html')
    else:
        form = ContactForm()
    return render(request, 'contacts.html', {'form': form})


def about_view(request):
    return render(request, 'about.html')


class CategoryListView(ListView):
    model               = Category
    template_name       = 'categories.html'
    context_object_name = 'categories'


# ──────────────────────────────────────────────────────────────────────────────
# DRF
# ──────────────────────────────────────────────────────────────────────────────

class MyModelDetailView(APIView):
    def get(self, request, pk):
        try:
            my_model   = MyModel.objects.get(pk=pk)
            serializer = MyModelSerializer(my_model)
            return Response(serializer.data)
        except MyModel.DoesNotExist:
            return Response({"error": "Data tidak ditemukan"}, status=404)