import json
from datetime import datetime
from decimal import Decimal
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Sum, Count, Q
from django.http import JsonResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from django.views import View
from businesses.models import Business
from products.models import Product, ProductImage
from sales.models import Sale, SaleItem
from orders.models import Order
from payments.models import Payment
from payments.paystack import PaystackClient, compute_booking_price
from core.middleware import is_api_or_postman_request

class DashboardMixin(LoginRequiredMixin):
    """Ensures the user has a business and provides it to context."""
    def handle_no_permission(self):
        if is_api_or_postman_request(self.request):
            return JsonResponse({
                'error': 'Unauthorized',
                'detail': 'Authentication required. Access denied for unauthenticated API request.'
            }, status=401)
        return super().handle_no_permission()

    def get_business(self):
        return get_object_or_404(Business, user=self.request.user)
    def get_context(self, **extra):
        ctx = {'business': self.get_business()}
        ctx.update(extra)
        return ctx
class OverviewView(DashboardMixin, View):
    """Dashboard home with quick stats."""
    def get(self, request):
        business = self.get_business()
        now = timezone.now()
        active_sale = Sale.objects.filter(
            business=business, status='live'
        ).first()
        total_orders = Order.objects.filter(
            sale__business=business, status='paid'
        ).count()
        total_revenue = Order.objects.filter(
            sale__business=business, status='paid'
        ).aggregate(total=Sum('total_amount'))['total'] or Decimal('0.00')
        total_products = Product.objects.filter(
            business=business, is_active=True
        ).count()
        upcoming_sales = Sale.objects.filter(
            business=business, status='upcoming'
        ).count()
        recent_orders = Order.objects.filter(
            sale__business=business
        ).select_related('sale').order_by('-created_at')[:5]
        return render(request, 'dashboard/overview.html', self.get_context(
            active_sale=active_sale,
            total_orders=total_orders,
            total_revenue=total_revenue,
            total_products=total_products,
            upcoming_sales=upcoming_sales,
            recent_orders=recent_orders,
        ))
# ── Product CRUD ──────────────────────────────────────────────────────
class ProductListView(DashboardMixin, View):
    def get(self, request):
        business = self.get_business()
        products = Product.objects.filter(
            business=business
        ).prefetch_related('images').order_by('-created_at')
        return render(request, 'dashboard/products/list.html', self.get_context(
            products=products,
        ))
class ProductCreateView(DashboardMixin, View):
    def get(self, request):
        return render(request, 'dashboard/products/form.html', self.get_context(
            editing=False,
        ))
    def post(self, request):
        business = self.get_business()
        name = request.POST.get('name', '').strip()
        description = request.POST.get('description', '').strip()
        base_price = request.POST.get('base_price', '0')
        if not name:
            return render(request, 'dashboard/products/form.html', self.get_context(
                editing=False,
                error='Product name is required.',
            ))
        try:
            base_price = Decimal(base_price)
        except Exception:
            base_price = Decimal('0.00')
        product = Product.objects.create(
            business=business,
            name=name,
            description=description,
            base_price=base_price,
            video=request.FILES.get('video'),
        )
        # Handle image uploads (up to 3)
        images = request.FILES.getlist('images')
        for i, img_file in enumerate(images[:3]):
            ProductImage.objects.create(
                product=product,
                image=img_file,
                order=i,
                processing_status='pending',
            )
        return redirect('dashboard:product_list')
class ProductEditView(DashboardMixin, View):
    def get(self, request, product_id):
        business = self.get_business()
        product = get_object_or_404(Product, id=product_id, business=business)
        return render(request, 'dashboard/products/form.html', self.get_context(
            editing=True,
            product=product,
        ))
    def post(self, request, product_id):
        business = self.get_business()
        product = get_object_or_404(Product, id=product_id, business=business)
        product.name = request.POST.get('name', product.name).strip()
        product.description = request.POST.get('description', product.description).strip()
        try:
            product.base_price = Decimal(request.POST.get('base_price', str(product.base_price)))
        except Exception:
            pass
        if 'video' in request.FILES:
            product.video = request.FILES['video']
        product.save()
        # Handle new image uploads
        images = request.FILES.getlist('images')
        current_count = product.images.count()
        for i, img_file in enumerate(images[:max(0, 3 - current_count)]):
            ProductImage.objects.create(
                product=product,
                image=img_file,
                order=current_count + i,
                processing_status='pending',
            )
        return redirect('dashboard:product_list')
class ProductDeleteView(DashboardMixin, View):
    """Soft-delete: set is_active=False."""
    def post(self, request, product_id):
        business = self.get_business()
        product = get_object_or_404(Product, id=product_id, business=business)
        product.is_active = False
        product.save(update_fields=['is_active', 'updated_at'])
        return redirect('dashboard:product_list')
class ProductImageDeleteView(DashboardMixin, View):
    """Delete a specific product image."""
    def post(self, request, image_id):
        business = self.get_business()
        image = get_object_or_404(ProductImage, id=image_id, product__business=business)
        image.delete()
        return JsonResponse({'success': True})
# ── Sale Management ───────────────────────────────────────────────────
class SaleListView(DashboardMixin, View):
    def get(self, request):
        business = self.get_business()
        sales = Sale.objects.filter(business=business).order_by('-created_at')
        return render(request, 'dashboard/sales/list.html', self.get_context(
            sales=sales,
        ))
class SaleCreateView(DashboardMixin, View):
    """
    Creating a new sale triggers Paystack payment for the selected date window & pricing model.
    """
    def get(self, request):
        business = self.get_business()
        products = Product.objects.filter(business=business, is_active=True)
        return render(request, 'dashboard/sales/create.html', self.get_context(
            products=products,
        ))
    def post(self, request):
        business = self.get_business()
        title = request.POST.get('title', '').strip()
        start_date = request.POST.get('start_date', '')
        start_time = request.POST.get('start_time', '00:00')
        end_date = request.POST.get('end_date', '')
        end_time = request.POST.get('end_time', '23:59')
        pricing_model = request.POST.get('pricing_model', 'daily_rate')
        products = Product.objects.filter(business=business, is_active=True)
        if not all([title, start_date, end_date]):
            return render(request, 'dashboard/sales/create.html', self.get_context(
                products=products,
                error='Title, start date, and end date are required.',
            ))
        try:
            start_at = timezone.make_aware(
                datetime.strptime(f"{start_date} {start_time}", "%Y-%m-%d %H:%M")
            )
            end_at = timezone.make_aware(
                datetime.strptime(f"{end_date} {end_time}", "%Y-%m-%d %H:%M")
            )
        except ValueError:
            return render(request, 'dashboard/sales/create.html', self.get_context(
                products=products,
                error='Invalid date/time format.',
            ))
        now = timezone.now()
        if start_at < now:
            return render(request, 'dashboard/sales/create.html', self.get_context(
                products=products,
                error='Cannot set start date/time in the past.',
            ))
        if end_at <= start_at:
            return render(request, 'dashboard/sales/create.html', self.get_context(
                products=products,
                error='End date must be after start date.',
            ))
        # Build items list
        item_ids = request.POST.getlist('product_ids')
        items_payload = []
        for pid in item_ids:
            try:
                product = Product.objects.get(id=pid, business=business, is_active=True)
            except Product.DoesNotExist:
                continue
            price = request.POST.get(f'price_{pid}', '0')
            discount = request.POST.get(f'discount_{pid}', '')
            stock = request.POST.get(f'stock_{pid}', '0')
            try:
                price_dec = Decimal(price)
                stock_int = int(stock)
                discount_dec = Decimal(discount) if discount else None
            except (ValueError, TypeError):
                continue
            if price_dec > 0 and stock_int > 0:
                items_payload.append({
                    'product_id': str(product.id),
                    'price': str(price_dec),
                    'discount_percent': str(discount_dec) if discount_dec else None,
                    'stock_total': stock_int,
                })
        if not items_payload:
            return render(request, 'dashboard/sales/create.html', self.get_context(
                products=products,
                error='You must select at least one product with valid price and stock.',
            ))
        # Compute price quote
        price, num_days = compute_booking_price(start_at, end_at, pricing_model)
        charge_amount = price if pricing_model == 'daily_rate' else Decimal('10.00')
        # Paystack metadata
        metadata = {
            'type': 'dashboard_sale_booking',
            'business_id': str(business.id),
            'title': title,
            'start_at': start_at.isoformat(),
            'end_at': end_at.isoformat(),
            'pricing_model': pricing_model,
            'items': items_payload,
        }
        client = PaystackClient()
        callback_url = request.build_absolute_uri('/payments/booking/callback/')
        result = client.initialize_transaction(
            email=business.email,
            amount_ghs=charge_amount,
            callback_url=callback_url,
            metadata=metadata,
        )
        if result['success']:
            Payment.objects.create(
                type='booking_fee',
                paystack_reference=result['reference'],
                amount=charge_amount,
                status='initiated',
                metadata=metadata,
            )
            return redirect(result['authorization_url'])
        return render(request, 'dashboard/sales/create.html', self.get_context(
            products=products,
            error=result.get('message', 'Payment initiation failed. Please try again.'),
        ))
class SaleManageView(DashboardMixin, View):
    def get(self, request, sale_id):
        business = self.get_business()
        sale = get_object_or_404(Sale, id=sale_id, business=business)
        items = sale.items.select_related('product').prefetch_related('product__images')
        orders = Order.objects.filter(sale=sale).order_by('-created_at')
        return render(request, 'dashboard/sales/manage.html', self.get_context(
            sale=sale,
            items=items,
            orders=orders,
        ))
class SaleEditView(DashboardMixin, View):
    """
    Edit details for sales that have not started yet (upcoming).
    Pricing model (payment mode) is READONLY and cannot be changed.
    """
    def get(self, request, sale_id):
        business = self.get_business()
        sale = get_object_or_404(Sale, id=sale_id, business=business)
        now = timezone.now()
        if sale.start_at <= now:
            return redirect('dashboard:sale_manage', sale_id=sale.id)
        products = Product.objects.filter(business=business, is_active=True)
        sale_items_map = {item.product_id: item for item in sale.items.all()}
        for p in products:
            p.sale_item = sale_items_map.get(p.id)
        return render(request, 'dashboard/sales/edit.html', self.get_context(
            sale=sale,
            products=products,
        ))
    def post(self, request, sale_id):
        business = self.get_business()
        sale = get_object_or_404(Sale, id=sale_id, business=business)
        now = timezone.now()
        if sale.start_at <= now:
            return redirect('dashboard:sale_manage', sale_id=sale.id)
        title = request.POST.get('title', '').strip()
        start_date = request.POST.get('start_date', '')
        start_time = request.POST.get('start_time', '00:00')
        end_date = request.POST.get('end_date', '')
        end_time = request.POST.get('end_time', '23:59')
        if title:
            sale.title = title
        try:
            start_at = timezone.make_aware(
                datetime.strptime(f"{start_date} {start_time}", "%Y-%m-%d %H:%M")
            )
            end_at = timezone.make_aware(
                datetime.strptime(f"{end_date} {end_time}", "%Y-%m-%d %H:%M")
            )
            if start_at >= now and end_at > start_at:
                sale.start_at = start_at
                sale.end_at = end_at
        except ValueError:
            pass
        # Save title and dates (PRICING MODEL IS INTENTIONALLY EXCLUDED / CANNOT BE MODIFIED)
        sale.save()
        # Update or add sale items
        item_ids = request.POST.getlist('product_ids')
        for pid in item_ids:
            try:
                product = Product.objects.get(id=pid, business=business, is_active=True)
            except Product.DoesNotExist:
                continue
            price = request.POST.get(f'price_{pid}', '0')
            discount = request.POST.get(f'discount_{pid}', '')
            stock = request.POST.get(f'stock_{pid}', '0')
            try:
                price_dec = Decimal(price)
                stock_int = int(stock)
                discount_dec = Decimal(discount) if discount else None
            except (ValueError, TypeError):
                continue
            if price_dec > 0 and stock_int > 0:
                sale_item, created = SaleItem.objects.get_or_create(
                    sale=sale, product=product,
                    defaults={
                        'price': price_dec,
                        'discount_percent': discount_dec,
                        'stock_total': stock_int,
                        'stock_remaining': stock_int,
                    }
                )
                if not created:
                    sale_item.price = price_dec
                    sale_item.discount_percent = discount_dec
                    # Adjust stock total & remaining
                    stock_diff = stock_int - sale_item.stock_total
                    sale_item.stock_total = stock_int
                    sale_item.stock_remaining = max(0, sale_item.stock_remaining + stock_diff)
                    sale_item.save()
        return redirect('dashboard:sale_manage', sale_id=sale.id)
# ── Orders ────────────────────────────────────────────────────────────
class OrderListView(DashboardMixin, View):
    def get(self, request):
        business = self.get_business()
        orders = Order.objects.filter(
            sale__business=business
        ).select_related('sale').order_by('-created_at')
        return render(request, 'dashboard/orders/list.html', self.get_context(
            orders=orders,
        ))
class OrderDetailView(DashboardMixin, View):
    def get(self, request, order_id):
        business = self.get_business()
        order = get_object_or_404(
            Order.objects.select_related('sale', 'payment'),
            id=order_id,
            sale__business=business,
        )
        items = order.items.select_related('sale_item__product')
        return render(request, 'dashboard/orders/detail.html', self.get_context(
            order=order,
            items=items,
        ))
# ── Analytics ─────────────────────────────────────────────────────────
class AnalyticsView(DashboardMixin, View):
    def get(self, request):
        business = self.get_business()
        sales = Sale.objects.filter(business=business).annotate(
            order_count=Count('orders', filter=Q(orders__status='paid')),
            revenue=Sum('orders__total_amount', filter=Q(orders__status='paid')),
        ).order_by('-start_at')
        return render(request, 'dashboard/analytics.html', self.get_context(
            sales=sales,
        ))
# ── Settings ──────────────────────────────────────────────────────────
class SettingsView(DashboardMixin, View):
    def get(self, request):
        return render(request, 'dashboard/settings.html', self.get_context())
    def post(self, request):
        business = self.get_business()
        business.name = request.POST.get('name', business.name).strip()
        phone_input = request.POST.get('phone', business.phone).strip()
        import re
        clean_phone = re.sub(r'\D', '', phone_input)
        if clean_phone.startswith('233'):
            clean_phone = clean_phone[3:]
        if clean_phone.startswith('0'):
            clean_phone = clean_phone[1:]
        business.phone = clean_phone if len(clean_phone) == 9 else phone_input
        business.email = request.POST.get('email', business.email).strip()
        if 'logo' in request.FILES:
            business.logo = request.FILES['logo']
        business.save()
        return render(request, 'dashboard/settings.html', self.get_context(
            success='Settings updated.',
        ))
PAYOUT_PROVIDERS = [
    {'name': 'MTN Mobile Money', 'code': 'MTN'},
    {'name': 'Telecel Cash (Vodafone)', 'code': 'VOD'},
    {'name': 'AT Money (AirtelTigo)', 'code': 'ATL'},
]
class PayoutSettingsView(DashboardMixin, View):
    """Manage seller Payout Details for direct revenue settlement."""
    def get(self, request):
        return render(request, 'dashboard/settings/payout.html', self.get_context(
            providers=PAYOUT_PROVIDERS,
        ))
    def post(self, request):
        business = self.get_business()
        bank_code = request.POST.get('provider_code', '').strip()
        account_number = request.POST.get('account_number', '').strip()
        account_name = request.POST.get('account_name', '').strip()
        provider_info = next((p for p in PAYOUT_PROVIDERS if p['code'] == bank_code), None)
        import re
        import logging
        logger = logging.getLogger('payments.paystack')
        clean_num = re.sub(r'\D', '', account_number)
        if clean_num.startswith('233'):
            clean_num = clean_num[3:]
        if clean_num.startswith('0'):
            clean_num = clean_num[1:]

        if not provider_info or not account_name or len(clean_num) != 9:
            return render(request, 'dashboard/settings/payout.html', self.get_context(
                providers=PAYOUT_PROVIDERS,
                error='Please select a network and enter a valid 9-digit phone number (e.g. 241234567).',
            ))
        # Paystack Ghana MoMo API expects 10-digit number starting with 0 (e.g. 0241234567)
        paystack_account_num = f"0{clean_num}"

        client = PaystackClient()
        res = None

        # If we have an existing subaccount code, verify it exists on Paystack first
        if business.paystack_subaccount_code:
            logger.info("Business '%s' has stored subaccount code: %s — verifying on Paystack...",
                        business.name, business.paystack_subaccount_code)
            verify = client.fetch_subaccount(business.paystack_subaccount_code)
            if verify.get('success'):
                # Subaccount exists, update it
                res = client.update_subaccount(
                    subaccount_code=business.paystack_subaccount_code,
                    business_name=business.name,
                    settlement_bank=bank_code,
                    account_number=paystack_account_num,
                )
            else:
                # Stored code doesn't exist on Paystack — clear it so we create fresh
                logger.warning("Stored subaccount %s NOT found on Paystack: %s. Will create new.",
                               business.paystack_subaccount_code, verify.get('message'))
                business.paystack_subaccount_code = ''

        # If subaccount update failed or didn't exist on Paystack, create a new subaccount
        if not res or not res.get('success'):
            res = client.create_subaccount(
                business_name=business.name,
                settlement_bank=bank_code,
                account_number=paystack_account_num,
            )

        if not res.get('success') or not res.get('subaccount_code'):
            error_detail = res.get('message', 'Failed to register Mobile Money account with Paystack.')
            logger.error("Paystack subaccount setup failed for business '%s': %s", business.name, error_detail)
            return render(request, 'dashboard/settings/payout.html', self.get_context(
                providers=PAYOUT_PROVIDERS,
                error=f"Paystack API Error: {error_detail}",
            ))

        subaccount_code = res['subaccount_code']

        business.payout_provider = provider_info['name']
        business.payout_bank_code = bank_code
        business.payout_account_number = clean_num
        business.payout_account_name = account_name
        business.paystack_subaccount_code = subaccount_code
        business.save()

        logger.info("Business '%s' linked to Paystack subaccount %s", business.name, subaccount_code)
        success_msg = f"Mobile Money Payout Account linked! Paystack Subaccount Code: {subaccount_code}"
        return render(request, 'dashboard/settings/payout.html', self.get_context(
            providers=PAYOUT_PROVIDERS,
            success=success_msg,
        ))
class DeliveryOptionView(DashboardMixin, View):
    """Manage delivery options for the seller's business."""
    def get(self, request):
        business = self.get_business()
        from businesses.models import DeliveryOption
        options = DeliveryOption.objects.filter(business=business, is_active=True)
        return render(request, 'dashboard/settings/delivery.html', self.get_context(
            options=options,
        ))
    def post(self, request):
        business = self.get_business()
        from businesses.models import DeliveryOption
        name = request.POST.get('name', '').strip()
        fee = request.POST.get('fee', '0')
        description = request.POST.get('description', '').strip()
        if not name:
            options = DeliveryOption.objects.filter(business=business, is_active=True)
            return render(request, 'dashboard/settings/delivery.html', self.get_context(
                options=options,
                error='Delivery option name is required.',
            ))
        try:
            fee_dec = Decimal(fee)
        except Exception:
            fee_dec = Decimal('0.00')
        DeliveryOption.objects.create(
            business=business,
            name=name,
            fee=fee_dec,
            description=description,
        )
        return redirect('dashboard:delivery_settings')
class DeliveryOptionDeleteView(DashboardMixin, View):
    def post(self, request, option_id):
        business = self.get_business()
        from businesses.models import DeliveryOption
        option = get_object_or_404(DeliveryOption, id=option_id, business=business)
        option.is_active = False
        option.save(update_fields=['is_active'])
        return redirect('dashboard:delivery_settings')