from decimal import Decimal

from django.db import transaction
from django.db.models import Sum, Q
from django.shortcuts import get_object_or_404

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.views import APIView

from .models import Customer, CreditPayment, Expense, Product, Sale, ShopSettings
from .serializers import (
    CustomerSerializer, CreditPaymentSerializer, DashboardSerializer,
    ExpenseSerializer, ProductSerializer, SaleSerializer,
)


# ── Token authentication ──────────────────────────────────────────────────────

def get_token_from_request(request):
    auth = request.headers.get("Authorization", "")
    if auth.startswith("Token "):
        return auth[6:].strip()
    return None


def require_token(request):
    """
    Returns None if the token is valid, or a 401 Response if not.
    Call at the top of every protected view:
        if err := require_token(request): return err
    """
    token = get_token_from_request(request)
    if not token:
        return Response({"detail": "Authentication required."}, status=status.HTTP_401_UNAUTHORIZED)
    settings = ShopSettings.get()
    if not settings.token or settings.token != token:
        return Response({"detail": "Invalid or expired token."}, status=status.HTTP_401_UNAUTHORIZED)
    return None


# ── Auth endpoints ────────────────────────────────────────────────────────────

@api_view(["POST"])
def verify_pin(request):
    """
    POST /api/auth/verify/
    Body: { "pin": "1234" }
    Returns: { "token": "<hex>", "must_change_pin": true|false }
    must_change_pin is true when the default PIN has never been changed.
    """
    raw_pin = str(request.data.get("pin", "")).strip()
    if not raw_pin:
        return Response({"detail": "PIN is required."}, status=status.HTTP_400_BAD_REQUEST)

    settings = ShopSettings.get()
    if not settings.verify_pin(raw_pin):
        return Response({"detail": "Incorrect PIN."}, status=status.HTTP_401_UNAUTHORIZED)

    token = settings.rotate_token()
    return Response({"token": token, "must_change_pin": settings.pin_is_default})


@api_view(["POST"])
def change_pin(request):
    """
    POST /api/auth/change-pin/
    Header: Authorization: Token <token>   ← must be logged in
    Body: { "current_pin": "1234", "new_pin": "5678" }

    Requires a valid session token — only available to logged-in users.
    Current PIN is still required as a second confirmation so an unattended
    unlocked screen cannot be used to silently change the PIN.
    """
    if err := require_token(request): return err   # must be logged in first

    current = str(request.data.get("current_pin", "")).strip()
    new_pin = str(request.data.get("new_pin", "")).strip()

    if not current or not new_pin:
        return Response({"detail": "current_pin and new_pin are required."}, status=400)
    if len(new_pin) != 4 or not new_pin.isdigit():
        return Response({"detail": "PIN must be exactly 4 digits."}, status=400)
    if new_pin == current:
        return Response({"detail": "New PIN must be different from the current PIN."}, status=400)

    settings = ShopSettings.get()
    if not settings.verify_pin(current):
        return Response({"detail": "Current PIN is incorrect."}, status=status.HTTP_401_UNAUTHORIZED)

    settings.set_pin(new_pin)
    token = settings.rotate_token()   # fresh token — current session remains active
    return Response({"detail": "PIN changed successfully.", "token": token})


@api_view(["POST"])
def logout(request):
    """
    POST /api/auth/logout/
    Invalidates the current session token.
    """
    if err := require_token(request): return err
    ShopSettings.get().invalidate_token()
    return Response({"detail": "Logged out."})


# ── Dashboard ─────────────────────────────────────────────────────────────────

class DashboardView(APIView):
    def get(self, request):
        if err := require_token(request): return err

        products = Product.objects.all()
        sales    = Sale.objects.all()
        expenses = Expense.objects.all()

        total_full  = products.aggregate(t=Sum('full_qty'))['t'] or 0
        total_empty = products.aggregate(t=Sum('empty_qty'))['t'] or 0
        total_revenue      = sales.aggregate(t=Sum('paid'))['t'] or Decimal('0')
        outstanding_credit = sales.filter(credit__gt=0).aggregate(t=Sum('credit'))['t'] or Decimal('0')
        total_expenses     = expenses.aggregate(t=Sum('amount'))['t'] or Decimal('0')

        breakdown = []
        for cat, _ in Expense.CATEGORY_CHOICES:
            total = expenses.filter(category=cat).aggregate(t=Sum('amount'))['t']
            if total:
                breakdown.append({'category': cat, 'total': str(total)})

        data = {
            'total_full': total_full,
            'total_empty': total_empty,
            'total_revenue': total_revenue,
            'outstanding_credit': outstanding_credit,
            'total_expenses': total_expenses,
            'net_profit': total_revenue - total_expenses,
            'product_count': products.count(),
            'customer_count': Customer.objects.count(),
            'sale_count': sales.count(),
            'expense_breakdown': breakdown,
        }
        return Response(DashboardSerializer(data).data)


# ── Products ──────────────────────────────────────────────────────────────────

class ProductListCreateView(APIView):
    def get(self, request):
        if err := require_token(request): return err
        qs = Product.objects.all()
        state = request.query_params.get('state')
        if state == 'full':
            qs = qs.filter(full_qty__gt=0)
        elif state == 'empty':
            qs = qs.filter(empty_qty__gt=0)
        return Response(ProductSerializer(qs, many=True).data)

    def post(self, request):
        if err := require_token(request): return err
        serializer = ProductSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class ProductDetailView(APIView):
    def get_object(self, pk):
        return get_object_or_404(Product, pk=pk)

    def get(self, request, pk):
        if err := require_token(request): return err
        return Response(ProductSerializer(self.get_object(pk)).data)

    def patch(self, request, pk):
        if err := require_token(request): return err
        serializer = ProductSerializer(self.get_object(pk), data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        if err := require_token(request): return err
        self.get_object(pk).delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(['PATCH'])
def adjust_quantity(request, pk):
    """PATCH /api/products/<id>/adjust/ — supports "+5", "-3", or absolute value."""
    if err := require_token(request): return err

    product = get_object_or_404(Product, pk=pk)
    field   = request.data.get('field')
    value   = request.data.get('value')

    if field not in ('full_qty', 'empty_qty'):
        return Response({'error': 'field must be full_qty or empty_qty'}, status=400)

    current = getattr(product, field)
    raw = str(value).strip()
    if raw.startswith('+'):
        new_val = current + int(raw[1:])
    elif raw.startswith('-'):
        new_val = current - int(raw[1:])
    else:
        new_val = int(raw)

    if new_val < 0:
        return Response({'error': 'Quantity cannot be negative'}, status=400)

    setattr(product, field, new_val)
    product.save()
    return Response(ProductSerializer(product).data)


# ── Customers ─────────────────────────────────────────────────────────────────

class CustomerListCreateView(APIView):
    def get(self, request):
        if err := require_token(request): return err
        search = request.query_params.get('search', '')
        qs = Customer.objects.all()
        if search:
            qs = qs.filter(Q(name__icontains=search) | Q(contact__icontains=search))
        return Response(CustomerSerializer(qs, many=True).data)

    def post(self, request):
        if err := require_token(request): return err
        serializer = CustomerSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class CustomerDetailView(APIView):
    def get_object(self, pk):
        return get_object_or_404(Customer, pk=pk)

    def get(self, request, pk):
        if err := require_token(request): return err
        customer = self.get_object(pk)
        data = CustomerSerializer(customer).data
        data['sales'] = SaleSerializer(customer.sales.all(), many=True).data
        return Response(data)

    def patch(self, request, pk):
        if err := require_token(request): return err
        serializer = CustomerSerializer(self.get_object(pk), data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        if err := require_token(request): return err
        self.get_object(pk).delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


# ── Sales ─────────────────────────────────────────────────────────────────────

class SaleListCreateView(APIView):
    def get(self, request):
        if err := require_token(request): return err
        qs = Sale.objects.select_related('customer', 'product').all()
        if request.query_params.get('credit') == 'true':
            qs = qs.filter(credit__gt=0)
        if cid := request.query_params.get('customer'):
            qs = qs.filter(customer_id=cid)
        return Response(SaleSerializer(qs, many=True).data)

    @transaction.atomic
    def post(self, request):
        if err := require_token(request): return err
        serializer = SaleSerializer(data=request.data)
        if not serializer.is_valid():
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        product = serializer.validated_data['product']
        qty     = serializer.validated_data.get('qty', 1)
        product.full_qty  -= qty
        product.empty_qty += qty
        product.save()

        sale = serializer.save()
        return Response(SaleSerializer(sale).data, status=status.HTTP_201_CREATED)


class SaleDetailView(APIView):
    def get_object(self, pk):
        return get_object_or_404(Sale, pk=pk)

    def get(self, request, pk):
        if err := require_token(request): return err
        return Response(SaleSerializer(self.get_object(pk)).data)

    def patch(self, request, pk):
        if err := require_token(request): return err
        sale = self.get_object(pk)
        serializer = SaleSerializer(sale, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(SaleSerializer(sale).data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(['POST'])
@transaction.atomic
def record_credit_payment(request, pk):
    """POST /api/sales/<id>/pay/ — record a credit repayment."""
    if err := require_token(request): return err

    sale   = get_object_or_404(Sale, pk=pk)
    amount = Decimal(str(request.data.get('amount', 0)))

    if amount <= 0:
        return Response({'error': 'Amount must be greater than 0'}, status=400)
    if amount > sale.credit:
        return Response({'error': f'Amount exceeds outstanding credit of {sale.credit}'}, status=400)

    payment = CreditPayment.objects.create(
        sale=sale, amount=amount,
        note=request.data.get('note', ''),
    )
    sale.paid  += amount
    sale.credit = max(Decimal('0'), sale.amount - sale.paid)
    sale.save()

    return Response({
        'payment': CreditPaymentSerializer(payment).data,
        'sale': SaleSerializer(sale).data,
    }, status=status.HTTP_201_CREATED)


# ── Expenses ──────────────────────────────────────────────────────────────────

class ExpenseListCreateView(APIView):
    def get(self, request):
        if err := require_token(request): return err
        qs = Expense.objects.all()
        if cat := request.query_params.get('category'):
            qs = qs.filter(category=cat)
        return Response(ExpenseSerializer(qs, many=True).data)

    def post(self, request):
        if err := require_token(request): return err
        serializer = ExpenseSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class ExpenseDetailView(APIView):
    def get_object(self, pk):
        return get_object_or_404(Expense, pk=pk)

    def get(self, request, pk):
        if err := require_token(request): return err
        return Response(ExpenseSerializer(self.get_object(pk)).data)

    def patch(self, request, pk):
        if err := require_token(request): return err
        serializer = ExpenseSerializer(self.get_object(pk), data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        if err := require_token(request): return err
        self.get_object(pk).delete()
        return Response(status=status.HTTP_204_NO_CONTENT)