from decimal import Decimal
import json
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse
from apps.core.models import SiteSettings
from .models import StorePageContent, Product, Order, OrderItem, Coupon, DigitalAccess
from django.db import transaction
from django.urls import reverse
from django.views.decorators.http import require_GET
from apps.core.telegram import notify_store_order
from . import coupons

def collection_home(request, listing='all'):
    """/shop/ (everything), /shop/merchandise/ and /magazine/ share one template."""
    settings = SiteSettings.get_solo()
    page = StorePageContent.get_solo()
    products = Product.objects.filter(is_active=True).order_by('-created_at')
    issues = Product.objects.filter(is_active=True, product_type='magazine')
    read_card = None
    if listing in ('merch', 'nostalgic'):
        products = products.filter(product_type=listing).order_by('-is_featured', '-created_at')
    elif listing == 'magazine':
        products = products.filter(product_type='magazine').order_by('-published_on', '-created_at')
    else:
        # The shop lists things to buy; the magazine gets one "Read Magazine" card that leads to /magazine/.
        products = products.exclude(product_type='magazine')
        read_card = issues.order_by('-is_featured', '-published_on', '-created_at').first() or True

    return render(request, 'store/collection.html', {
        'site': settings,
        'page': page,
        'products': products,
        'listing': listing,
        'magazine_count': issues.count(),
        'read_card': read_card,
    })

def product_detail(request, slug):
    settings = SiteSettings.get_solo()
    page = StorePageContent.get_solo()
    product = get_object_or_404(Product, slug=slug, is_active=True)
    if product.is_magazine:
        return redirect(product.get_absolute_url(), permanent=True)
    images = product.images.all()
    
    sizes = [s.strip() for s in product.sizes.split(',')] if product.sizes else []
    colors = [c.strip() for c in product.colors.split(',')] if product.colors else []
    
    return render(request, 'store/product_detail.html', {
        'site': settings,
        'page': page,
        'product': product,
        'images': images,
        'sizes': sizes,
        'colors': colors,
    })

@require_GET
def cart_images(request):
    """{product_id: image url} for cart items saved before images were stored in the cart."""
    try:
        ids = [int(i) for i in request.GET.get('ids', '').split(',') if i][:50]
    except ValueError:
        ids = []
    data = {str(p.pk): p.image.url for p in Product.objects.filter(pk__in=ids, is_active=True) if p.image}
    return JsonResponse(data)


def _cart_lines(items):
    """Cart items from the browser → [(product, server price, qty, 'print')]. Prices always come from the DB."""
    lines = []
    for item in items or []:
        try:
            qty = max(1, min(int(item.get('quantity', 1)), 99))
            product = Product.objects.filter(id=int(item.get('product_id')), is_active=True).first()
        except (TypeError, ValueError, AttributeError):
            continue
        if product:
            lines.append((product, product.discount_price or product.price, qty, item))
    return lines


def _coupon_lines(lines):
    return [(p, price, qty, 'print') for p, price, qty, _ in lines]


def validate_coupon(request):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'})
    try:
        data = json.loads(request.body)
    except ValueError:
        return JsonResponse({'success': False, 'error': 'Invalid request'})
    lines = _cart_lines(data.get('items'))
    if not lines:
        return JsonResponse({'success': False, 'error': 'Your cart is empty.'})
    try:
        result = coupons.check(data.get('code'), _coupon_lines(lines), user=request.user, phone=data.get('phone', ''))
    except coupons.CouponError as exc:
        return JsonResponse({'success': False, 'error': str(exc)})
    return JsonResponse({
        'success': True,
        'code': result.coupon.code,
        'discount_amount': float(result.discount),
        'description': result.coupon.describe(),
    })


LOGIN_NEEDED = 'কিনতে আগে সাইন ইন করুন বা একটি ফ্রি অ্যাকাউন্ট খুলুন।'
TERMS_NEEDED = 'অর্ডার করতে শর্তাবলী ও গোপনীয়তা নীতি মেনে নিন।'


def buyer_problem(request, accepted):
    """Buying needs an account and a ticked Terms box. Returns (code, message) or None."""
    if not request.user.is_authenticated:
        return 'login', LOGIN_NEEDED
    if str(accepted).lower() not in ('true', '1', 'on', 'yes'):
        return 'terms', TERMS_NEEDED
    request.user.profile.accept_terms()
    return None


def place_order(request):
    if request.method != 'POST':
        return JsonResponse({'success': False, 'error': 'Invalid request'})
    try:
        data = json.loads(request.body)
    except ValueError:
        return JsonResponse({'success': False, 'error': 'Invalid request'})
    problem = buyer_problem(request, data.get('accept_terms'))
    if problem:
        return JsonResponse({'success': False, 'error': problem[1], 'need': problem[0]}, status=403)
    from apps.accounts.locations import full_address, is_valid
    name = (data.get('name') or '').strip()[:150]
    phone = (data.get('phone') or '').strip()[:20]
    alt_phone = (data.get('alt_phone') or '').strip()[:20]
    division, district, area = (str(data.get(k) or '').strip() for k in ('division', 'district', 'area'))
    address_line = (data.get('address_line') or '').strip()[:300]
    if division or district or area:
        if not is_valid(division, district, area):
            return JsonResponse({'success': False, 'error': 'Please choose your division, district and area from the lists.'})
        location = full_address(address_line, area, district, division) if address_line else ''
    else:
        location = (data.get('location') or '').strip()  # pages cached before the address dropdowns
    lines = _cart_lines(data.get('items'))
    if not name or not phone or not location or not lines:
        return JsonResponse({'success': False, 'error': 'Missing required fields. Please fill out all required fields.'})

    result = None
    if (data.get('coupon_code') or '').strip():
        try:
            result = coupons.check(data['coupon_code'], _coupon_lines(lines), user=request.user, phone=phone)
        except coupons.CouponError as exc:
            return JsonResponse({'success': False, 'error': f'Coupon: {exc}', 'coupon_error': True})

    try:
        with transaction.atomic():
            order = Order.objects.create(
                user=request.user if request.user.is_authenticated else None,
                name=name, phone=phone, alt_phone=alt_phone, division=division, district=district, area=area,
                address_line=address_line, location=location, note=(data.get('note') or '')[:2000], total_amount=0)
            subtotal = Decimal('0.00')
            for product, price, qty, item in lines:
                OrderItem.objects.create(order=order, product=product, quantity=qty, price=price,
                                         size=str(item.get('size') or '')[:50], color=str(item.get('color') or '')[:50])
                subtotal += price * qty
            discount = Decimal('0.00')
            if result:
                coupons.redeem(result, order=order)
                discount = result.discount
                order.coupon = result.coupon
            order.discount_amount = discount
            order.total_amount = subtotal - discount
            order.save()
    except coupons.CouponError as exc:
        return JsonResponse({'success': False, 'error': f'Coupon: {exc}', 'coupon_error': True})

    if request.user.is_authenticated and data.get('save_address') and division:
        profile = request.user.profile
        profile.division, profile.district, profile.area, profile.address = division, district, area, address_line
        if alt_phone and not profile.alt_phone:
            profile.alt_phone = alt_phone
        profile.save(update_fields=['division', 'district', 'area', 'address', 'alt_phone'])
    try:
        notify_store_order(order)
    except Exception:
        pass  # don't fail the order if Telegram is down
    track = reverse('account_order', args=[order.pk]) if order.user_id else ''
    return JsonResponse({'success': True, 'order_id': order.id, 'total': float(order.total_amount), 'track_url': track})


# ─── Digital magazine ──────────────────────────────────────────────────────────

from django.contrib import messages
from django.core.exceptions import ValidationError
from django.http import FileResponse, Http404, HttpResponsePermanentRedirect, HttpResponseRedirect
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_POST

from . import magazine as mag


def _magazine(slug):
    return get_object_or_404(Product, slug=slug, is_active=True, product_type='magazine')


def magazine_home(request):
    """/magazine/ — The Reverse Times Magazine (all issues)."""
    page = StorePageContent.get_solo()
    issues = list(Product.objects.filter(is_active=True, product_type='magazine')
                  .order_by('-is_featured', '-published_on', '-created_at'))
    from apps.articles.models import Article
    stories = (Article.objects.filter(status='published', magazines__isnull=False)
               .select_related('author').prefetch_related('magazines').distinct().order_by('-published_at')[:6])
    return render(request, 'store/magazine_home.html', {
        'page': page,
        'hero': issues[0] if issues else None,
        'issues': issues,
        'stories': stories,
        'free_count': sum(1 for i in issues if i.is_free_digital),
        'free_pages_total': sum(i.free_page_count for i in issues),
    })


@never_cache
def magazine_detail(request, slug):
    product = _magazine(slug)
    page = StorePageContent.get_solo()
    access = mag.access_for(request, product)
    full = mag.has_full_access(request, product)
    login_needed = product.is_free_digital and not full
    login_url = f"{reverse('account_login')}?next={product.get_absolute_url()}%23read"
    shown = mag.reader_pages(product, full) if product.has_digital else []
    if full and product.has_digital:
        pdf_url = reverse('magazine_pdf', kwargs={'slug': slug})
    elif product.preview_pdf:
        pdf_url = product.preview_pdf.url
    else:
        pdf_url = ''
    # Coupon for the digital edition: ?coupon=CODE applies it (kept in the session), ?coupon= clears it.
    from . import coupons
    key = f'mag_coupon_{product.pk}'
    if 'coupon' in request.GET:
        request.session[key] = request.GET['coupon'].strip()[:50]
    code = request.session.get(key, '')
    deal = {'code': code, 'price': product.digital_price, 'final': product.digital_price}
    if code and product.can_buy_digital:
        try:
            r = mag.digital_coupon(product, code, user=request.user)
            deal.update(discount=r.discount, final=max(0, product.digital_price - r.discount), ok=True,
                        description=r.coupon.describe_bn())
        except coupons.CouponError as exc:
            deal['error'] = str(exc)
    my_coupons = []
    if request.user.is_authenticated and product.can_buy_digital and not full:
        my_coupons = [c for c in request.user.coupons.filter(is_active=True) if c.is_valid() and c.covers(product, 'digital')
                      and (c.per_customer_limit is None or coupons.used_by(c, request.user) < c.per_customer_limit)][:3]
    return render(request, 'store/magazine_detail.html', {
        'deal': deal,
        'my_coupons': my_coupons,
        'page': page,
        'product': product,
        'access': access,
        'full_access': full and product.has_digital,
        'pdf_url': pdf_url,
        'reader_config': {
            'pdf': pdf_url,
            'pages': shown,
            'total': product.page_count or len(shown),
            'full': bool(full and product.has_digital),
            'price': f'{product.digital_price:.0f}' if product.digital_price else '',
            'watermark': (f'{access.name} · {access.phone} · {access.reference}' if (access and full and not product.is_free_digital)
                          else 'thereversetimes.com'),
            'login': login_url if login_needed else '',
            'pending': bool(access and access.status == 'pending' and not full),
            'print': ({'id': product.pk, 'title': product.title, 'price': f'{product.current_price:.2f}',
                       'image': product.image.url if product.image else ''}
                      if product.print_available and product.in_stock else None),
        },
        'login_needed': login_needed,
        'login_url': login_url,
        'whatsapp_link': mag.whatsapp_link(access, page) if access and access.status == 'pending' else '',
        'errors': request.session.pop('mag_errors', {}),
        'form': request.session.pop('mag_form', None) or _prefill(request),
        'open_unlock': request.GET.get('unlock') == '1',
        'stories': product.tagged_articles.filter(status='published').select_related('author')[:6],
        'more_issues': Product.objects.filter(is_active=True, product_type='magazine').exclude(pk=product.pk)[:4],
    })


def _prefill(request):
    if not request.user.is_authenticated:
        return {}
    profile = getattr(request.user, 'profile', None)
    phone = (profile.phone or '') if profile else ''
    return {'name': request.user.get_full_name(), 'phone': phone, 'sender_number': phone}


@never_cache
def magazine_pdf(request, slug):
    """Full PDF — only for approved readers (and staff)."""
    product = _magazine(slug)
    if not product.pdf_file or not mag.has_full_access(request, product):
        raise Http404
    access = mag.access_for(request, product)
    if access:
        DigitalAccess.objects.filter(pk=access.pk).update(open_count=access.open_count + 1)
    response = FileResponse(product.pdf_file.open('rb'), content_type='application/pdf')
    response['Content-Disposition'] = 'inline; filename="magazine.pdf"'
    response['Cache-Control'] = 'private, no-store'
    response['X-Robots-Tag'] = 'noindex'
    return response


def _wants_json(request):
    return request.headers.get('X-Requested-With') == 'fetch'


@require_POST
def magazine_request(request, slug):
    product = _magazine(slug)
    back = product.get_absolute_url() + '#buy'
    problem = buyer_problem(request, request.POST.get('accept_terms'))
    if problem:
        if _wants_json(request):
            return JsonResponse({'ok': False, 'need': problem[0], 'errors': {'general': problem[1]}}, status=403)
        if problem[0] == 'login':
            return HttpResponseRedirect(f"{reverse('account_login')}?next={product.get_absolute_url()}%23buy")
        request.session['mag_errors'] = {'general': problem[1]}
        return HttpResponseRedirect(back)
    try:
        access = mag.create_request(product, request.POST, user=request.user)
    except ValidationError as exc:
        if _wants_json(request):
            return JsonResponse({'ok': False, 'errors': {k: v[0] for k, v in exc.message_dict.items()}}, status=400)
        request.session['mag_errors'] = {k: v[0] for k, v in exc.message_dict.items()}
        request.session['mag_form'] = {k: request.POST.get(k, '') for k in ('name', 'phone', 'sender_number', 'trx_id')}
        if 'coupon' in exc.message_dict:
            request.session[f'mag_coupon_{product.pk}'] = ''
        return HttpResponseRedirect(back)
    request.session.pop(f'mag_coupon_{product.pk}', None)
    if _wants_json(request):
        page = StorePageContent.get_solo()
        return mag.remember(JsonResponse({
            'ok': True, 'reference': access.reference, 'status': access.status,
            'amount': f'{access.amount:.0f}', 'whatsapp': mag.whatsapp_link(access, page),
            'read_url': product.get_absolute_url() + '#read', 'approval_time': page.approval_time_text,
        }), access)
    if access.status == 'approved':
        messages.success(request, 'কুপন দিয়ে পূর্ণ সংখ্যা খুলে গেছে — পড়া শুরু করুন!')
        back = product.get_absolute_url() + '#read'
    return mag.remember(HttpResponseRedirect(back), access)


@require_POST
def magazine_unlock(request, slug):
    product = _magazine(slug)
    access = mag.find_for_unlock(product, request.POST.get('phone'), request.POST.get('code'))
    if not access:
        request.session['mag_errors'] = {'unlock': 'We could not find a payment for that phone number and code.'}
        return HttpResponseRedirect(product.get_absolute_url() + '?unlock=1#buy')
    return mag.remember(HttpResponseRedirect(product.get_absolute_url() + '#buy'), access)


def legacy_shop_redirect(request, rest=''):
    """Old /reverse-collection/... links → /shop/... (keeps Google ranking)."""
    target = '/shop/' + rest
    if request.META.get('QUERY_STRING'):
        target += '?' + request.META['QUERY_STRING']
    if request.method == 'POST':
        response = HttpResponseRedirect(target)
        response.status_code = 308  # keep POST for old cached pages
        return response
    return HttpResponsePermanentRedirect(target)


@require_GET
def magazine_coupon(request, slug):
    """Check a coupon for an issue's digital edition (used by the buy-digital drawer)."""
    product = _magazine(slug)
    code = request.GET.get('code', '').strip()[:50]
    price = product.digital_price or Decimal('0')
    if not code:
        request.session.pop(f'mag_coupon_{product.pk}', None)
        return JsonResponse({'ok': True, 'code': '', 'discount': '0', 'final': f'{price:.0f}'})
    try:
        r = mag.digital_coupon(product, code, user=request.user)
    except coupons.CouponError as exc:
        return JsonResponse({'ok': False, 'error': str(exc), 'final': f'{price:.0f}'})
    request.session[f'mag_coupon_{product.pk}'] = code
    final = max(Decimal('0'), price - r.discount)
    return JsonResponse({'ok': True, 'code': r.coupon.code, 'discount': f'{r.discount:.0f}', 'final': f'{final:.0f}',
                         'description': r.coupon.describe_bn()})
