from datetime import date, datetime, time, timedelta
from decimal import Decimal

from django.db.models import Sum
from django.utils import timezone
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from account.permissions import HasTabPermission, get_user_shop_filter
from shop_management.models.data.inventory import StockBalance, StockMovement
from shop_management.models.data.organization import CompanySetting
from shop_management.models.data.shop import Shop
from shop_management.models.data.stock_adjustment import StockAdjustment
from shop_management.models.data.warehouse_transfer import WarehouseTransfer


def number(value, places="0.00"):
    return str(Decimal(value or 0).quantize(Decimal(places)))


class InventoryReportView(APIView):
    permission_classes = [IsAuthenticated, HasTabPermission]
    required_tab = "reports"

    def get(self, request):
        try:
            end = date.fromisoformat(request.query_params.get("end_date") or str(date.today()))
            start = date.fromisoformat(request.query_params.get("start_date") or str(end - timedelta(days=29)))
        except ValueError:
            raise ValidationError({"date": "Use YYYY-MM-DD format."})
        if start > end:
            raise ValidationError({"end_date": "End date cannot be before start date."})
        try:
            shop = Shop.objects.get(get_user_shop_filter(request.user), pk=request.query_params.get("shop"))
        except (Shop.DoesNotExist, ValueError, TypeError):
            raise ValidationError({"shop": "Branch was not found or is not accessible."})

        setting = CompanySetting.objects.select_related("base_currency").filter(shop=shop).first()
        warning_days = setting.expiry_warning_days if setting else 60
        today = timezone.localdate(); warning_date = today + timedelta(days=warning_days)
        balances = list(StockBalance.objects.filter(
            warehouse__shop=shop, quantity__gt=0,
        ).select_related("warehouse", "product", "batch", "cost_currency"))
        product_totals = StockBalance.objects.filter(warehouse__shop=shop).values("product_id").annotate(total=Sum("quantity"))
        total_by_product = {row["product_id"]: Decimal(row["total"] or 0) for row in product_totals}
        valuation_by_currency = {}
        balance_rows = []
        for row in balances:
            value = row.quantity * row.average_cost
            currency = row.cost_currency.title
            valuation_by_currency[currency] = valuation_by_currency.get(currency, Decimal("0")) + value
            expiry = row.batch.expiry_date
            if expiry and expiry < today: stock_status = "EXPIRED"
            elif expiry and expiry <= warning_date: stock_status = "NEAR_EXPIRY"
            elif total_by_product.get(row.product_id, 0) <= row.product.minimum_stock_level: stock_status = "LOW_STOCK"
            else: stock_status = "AVAILABLE"
            balance_rows.append({
                "warehouse": row.warehouse.name, "product_id": row.product_id,
                "product": row.product.name, "product_code": row.product.code,
                "batch": row.batch.batch_number, "expiry_date": expiry,
                "quantity": number(row.quantity, "0.0000"), "average_cost": number(row.average_cost),
                "stock_value": number(value), "currency": currency, "status": stock_status,
            })

        start_dt = timezone.make_aware(datetime.combine(start, time.min))
        end_dt = timezone.make_aware(datetime.combine(end, time.max))
        movements = StockMovement.objects.filter(
            warehouse__shop=shop, movement_date__range=(start_dt, end_dt),
        ).select_related("warehouse", "product", "batch", "currency")
        movement_rows = [{
            "date": row.movement_date, "type": row.get_movement_type_display(),
            "warehouse": row.warehouse.name, "product": row.product.name,
            "batch": row.batch.batch_number, "quantity_in": number(row.quantity_in, "0.0000"),
            "quantity_out": number(row.quantity_out, "0.0000"), "unit_cost": number(row.unit_cost),
            "currency": row.currency.title, "reference": f"{row.reference_type} {row.reference_id}".strip(),
        } for row in movements[:1000]]
        adjustments = StockAdjustment.objects.filter(
            warehouse__shop=shop, adjustment_date__range=(start, end), status=StockAdjustment.Status.POSTED,
        ).select_related("warehouse").prefetch_related("items")
        adjustment_rows = [{
            "date": row.adjustment_date, "number": row.adjustment_number, "warehouse": row.warehouse.name,
            "kind": row.get_adjustment_kind_display(), "reason": row.reason,
            "increase": number(sum((item.quantity for item in row.items.all() if item.direction == "IN"), Decimal("0")), "0.0000"),
            "decrease": number(sum((item.quantity for item in row.items.all() if item.direction == "OUT"), Decimal("0")), "0.0000"),
        } for row in adjustments]
        transfers = WarehouseTransfer.objects.filter(
            shop=shop, transfer_date__range=(start, end),
        ).exclude(status=WarehouseTransfer.Status.DRAFT).select_related("source_warehouse", "destination_warehouse").prefetch_related("items")
        transfer_rows = [{
            "date": row.transfer_date, "number": row.transfer_number, "source": row.source_warehouse.name,
            "destination": row.destination_warehouse.name, "status": row.status,
            "quantity": number(sum((item.quantity for item in row.items.all()), Decimal("0")), "0.0000"),
        } for row in transfers]
        return Response({
            "shop": shop.name, "start_date": start, "end_date": end, "expiry_warning_days": warning_days,
            "summary": {"total_units": number(sum((row.quantity for row in balances), Decimal("0")), "0.0000"),
                        "products": len({row.product_id for row in balances}), "batches": len(balances),
                        "low_stock": sum(row["status"] == "LOW_STOCK" for row in balance_rows),
                        "near_expiry": sum(row["status"] == "NEAR_EXPIRY" for row in balance_rows),
                        "expired": sum(row["status"] == "EXPIRED" for row in balance_rows),
                        "valuation_by_currency": [{"currency": key, "value": number(value)} for key, value in sorted(valuation_by_currency.items())]},
            "balances": balance_rows, "movements": movement_rows,
            "adjustments": adjustment_rows, "transfers": transfer_rows,
        })
