Awaiting Shipping

import datetime

from django.db.models import Case, Count, F, IntegerField, Value, When
from django.utils.timezone import now

import numpy as np
import plotly.express as px
from dashboards.component import Chart, Table
from dashboards.component.chart import ChartSerializer
from dashboards.component.layout import Card, ComponentLayout
from dashboards.component.table import TableSerializer
from dashboards.dashboard import Dashboard
from dashboards.registry import registry

from example.packages.models import Package, PackageStage


class PackageChartSerializer(ChartSerializer):
    class Meta:
        fields = ["state", "count"]
        model = Package
        title = "State"

    def get_queryset(self, *args, **kwargs):
        return (
            Package.objects.values("state")
            .annotate(
                count=Count("pk"),
                order=Case(
                    When(state=Package.PackageStates.received, then=0),
                    When(state=Package.PackageStates.in_transit, then=1),
                    When(state=Package.PackageStates.delivered, then=2),
                    output_field=IntegerField(),
                ),
            )
            .order_by("order")
        )

    def to_fig(self, df):
        fig = px.bar(
            df,
            x="state",
            y="count",
            text_auto=True,
        )
        fig.update_layout(xaxis_title=None, yaxis_title=None, hovermode=False)

        return fig


class WeightVsPriceChartSerializer(ChartSerializer):
    class Meta:
        fields = ["weight", "price", "id"]
        model = Package
        title = "Parcel Weight Vs Price"

    def get_queryset(self, *args, **kwargs):
        return Package.objects.filter(state=Package.PackageStates.delivered)

    def to_fig(self, df):
        df["price"] = df["price"].astype(float)
        df["weight"] = df["weight"].astype(float)
        df["profit"] = df["Price"] = np.random.uniform(
            low=1, high=df["price"] - 1, size=len(df)
        )
        df["percent_profit"] = round(df["profit"] / df["price"] * 100, 2)

        fig = px.scatter(
            df,
            y="weight",
            x="price",
            size="percent_profit",
            color="weight",
            hover_name="id",
        )
        fig.update_layout(
            yaxis_title="Weight (Kilos)",
            xaxis_title="Price (£)",
        )

        return fig


class DeliveryScheduleChartSerializer(ChartSerializer):
    class Meta:
        fields = ["location__country", "status", "count"]
        model = Package
        title = "Delivery Schedule"

    def get_queryset(self, *args, **kwargs):
        current_date = now()

        qs = (
            PackageStage.objects.filter(location__label="Destination")
            .annotate(
                actual_datetime=current_date - F("time_offset"),
                expected_datetime=current_date - F("expected_offset"),
                status=Case(
                    When(
                        actual_datetime__lt=F("expected_datetime__date"),
                        then=Value("Early"),
                    ),
                    When(
                        actual_datetime__date=F("expected_datetime__date"),
                        then=Value("On time"),
                    ),
                    When(
                        actual_datetime__date=F("expected_datetime__date")
                        + datetime.timedelta(days=1),
                        then=Value("24 hrs delay"),
                    ),
                    When(
                        actual_datetime__date=F("expected_datetime__date")
                        + datetime.timedelta(days=2),
                        then=Value("48 hrs delay"),
                    ),
                    When(
                        actual_datetime__date__gt=F("expected_datetime__date")
                        + datetime.timedelta(days=2),
                        then=Value("Significantly delay"),
                    ),
                ),
            )
            .values("location__country", "status")
            .annotate(
                count=Count("pk"),
            )
        )

        return qs.order_by("location__country")[0:25]

    def to_fig(self, df):
        df["status"] = df["status"].astype(str)

        fig = px.bar(
            df, y="location__country", x="count", color="status", orientation="h"
        )
        fig.update_layout(
            yaxis_title="Country",
            xaxis_title=None,
        )

        return fig


class PackagesReceivedTableSerializer(TableSerializer):
    def get_queryset(self, *args, **kwargs):
        return Package.objects.filter(state=Package.PackageStates.received)

    class Meta:
        columns = {
            "id": "#ID",
            "weight": "Weight (kg)",
            "price": "Price",
        }
        order = ["id"]


class PackagesDashboard(Dashboard):
    package_chart = Chart(value=PackageChartSerializer)
    weight_vs_price = Chart(value=WeightVsPriceChartSerializer)
    received_table = Table(value=PackagesReceivedTableSerializer)
    delivery_schedule = Chart(value=DeliveryScheduleChartSerializer)

    class Meta:
        name = "Packages"

    class Layout(Dashboard.Layout):
        components = ComponentLayout(
            Card("package_chart", grid_css_classes="span-6"),
            Card(
                "received_table",
                heading="Awaiting Shipping",
                grid_css_classes="span-6",
            ),
            Card("weight_vs_price", grid_css_classes="span-12"),
            Card("delivery_schedule", grid_css_classes="span-12"),
        )


registry.register(PackagesDashboard)