import datetime
from decimal import Decimal as D
from itertools import chain

from django.db import models
from django.template.defaultfilters import date as date_filter
from django.utils.translation import gettext_lazy as _

from oscar.apps.order import reports as oscar
from oscar.core.loading import get_class, get_classes, get_model

from ocyan.core.fender import config
from ocyan.plugin.oscar.pipeline import PIPELINE_STATUS_COMPLETE, PIPELINE_STATUS_PAID

ReportGenerator = get_class("dashboard.reports.reports", "ReportGenerator")
ReportCSVFormatter = get_class("dashboard.reports.reports", "ReportCSVFormatter")
ReportHTMLFormatter = get_class("dashboard.reports.reports", "ReportHTMLFormatter")
Product = get_model("catalogue", "Product")
Order = get_model("order", "Order")
Line = get_model("order", "Line")
OrderLinesReportCSVFormatter = get_class(
    "order.formatters", "OrderLinesReportCSVFormatter"
)
OrderLinesReportHTMLFormatter = get_class(
    "order.formatters", "OrderLinesReportHTMLFormatter"
)
ProductSalesReportCSVFormatter = get_class(
    "order.formatters", "ProductSalesReportCSVFormatter"
)
ProductSalesReportHTMLFormatter = get_class(
    "order.formatters", "ProductSalesReportHTMLFormatter"
)
ShippingCostLineWrapper, DiscountLineWrapper, SurchargeLineWrapper = get_classes(
    "order.wrappers",
    ["ShippingCostLineWrapper", "DiscountLineWrapper", "SurchargeLineWrapper"],
)


class OrderReportCSVFormatter(oscar.OrderReportCSVFormatter):
    filename_template = "orders-%(start_date)s-to-%(end_date)s.csv"
    header_row = [
        _("Order number"),
        _("Order status"),
        _("Email"),
        _("Name"),
        _("Billing address"),
        _("Shipping country"),
        _("Payment method"),
        _("VAT ID"),
        _("VAT applied"),
        _("Total incl. tax"),
        _("Total excl. tax"),
        _("Total tax"),
        _("Date placed"),
    ]

    def make_row(self, order):
        address = (
            order.billing_address
            if order.billing_address is not None
            else order.shipping_address
        )
        name = order.user.get_full_name() if order.user is not None else address.name
        try:
            vat = order.vat_entry.vat_number if order.vat_entry else None
        except AttributeError:
            vat = None

        source = order.sources.last()
        payment_method = source.source_type.name if source else None

        return [
            order.number,
            order.status,
            order.email,
            "-" if order.is_anonymous else name,
            address.summary,
            order.shipping_address.country_id,
            payment_method,
            vat,
            bool(order.total_tax),
            order.total_incl_tax,
            order.total_excl_tax,
            order.total_tax,
            self.format_datetime(order.date_placed),
        ]

    def generate_csv(self, response, orders):
        writer = self.get_csv_writer(response)
        writer.writerow(self.header_row)
        for order in orders:
            row = self.make_row(order)
            writer.writerow(row)

    def filename(self, **kwargs):
        return (self.filename_template % kwargs).lower()


class OrderReportGenerator(oscar.OrderReportGenerator):
    formatters = {
        "CSV_formatter": OrderReportCSVFormatter,
        "HTML_formatter": oscar.OrderReportHTMLFormatter,
    }


class InvoicedOrderReportCSVFormatter(OrderReportCSVFormatter):
    filename_template = "invoiced-orders-%(start_date)s-to-%(end_date)s.csv"
    company_address_field = config.get("oscar", "company_address_field", "line3")
    header_row = [
        _("Order number"),
        _("Order status"),
        _("Email"),
        _("Invoice number"),
        _("Invoice date"),
        _("Company name"),
        _("Name"),
        _("Billing address"),
        _("Shipping country"),
        _("Payment method"),
        _("VAT ID"),
        _("VAT applied"),
        _("Total incl. tax"),
        _("Total excl. tax"),
        _("Total tax"),
        _("Date placed"),
    ]

    def make_row(self, order):
        row = super().make_row(order)

        address = (
            order.billing_address
            if order.billing_address is not None
            else order.shipping_address
        )

        row[3:3] = [
            order.invoice.number,
            self.format_date(order.invoice.date),
            getattr(address, self.company_address_field),
        ]

        return row

    def format_date(self, d):
        try:
            return super().format_date(d)
        except AttributeError:
            return date_filter(d, "DATE_FORMAT")


class InvoicedOrderReportHTMLFormatter(ReportHTMLFormatter):
    filename_template = "dashboard/reports/partials/invoice_order_report.html"


class InvoicedOrderReportGenerator(ReportGenerator):
    model_class = Order
    code = "invoiced_order_report"
    description = _("Orders invoiced")
    date_range_field_name = "invoice__date"

    formatters = {
        "CSV_formatter": InvoicedOrderReportCSVFormatter,
        "HTML_formatter": InvoicedOrderReportHTMLFormatter,
    }

    def filter_with_date_range(self, queryset):
        qs = queryset.filter(
            invoice__isnull=False,
            status__in=[PIPELINE_STATUS_PAID, PIPELINE_STATUS_COMPLETE],
        )
        if self.start_date:
            qs = qs.filter(invoice__date__gte=self.start_date)
        if self.end_date:
            qs = qs.filter(invoice__date__lt=self.end_date + datetime.timedelta(days=1))
        return qs

    def generate(self):
        # pylint: disable=protected-access
        qs = self.filter_with_date_range(Order._default_manager)
        return self.formatter.generate_response(
            qs, start_date=self.start_date, end_date=self.end_date
        )

    def is_available_to(self, user):
        return user.is_staff


class OrderLinesReportGenerator(oscar.ReportGenerator):
    model_class = Line
    code = "order_lines_report"
    description = _("Order lines")
    date_range_field_name = "order__date_placed"

    # Loaded with get_class, so they can be overridden.
    formatters = {
        "CSV_formatter": OrderLinesReportCSVFormatter,
        "HTML_formatter": OrderLinesReportHTMLFormatter,
    }

    def generate(self):
        order_lines = Line.objects.filter(
            order__status__in=[PIPELINE_STATUS_PAID, PIPELINE_STATUS_COMPLETE]
        )

        if self.start_date:
            order_lines = order_lines.filter(order__date_placed__gte=self.start_date)

        if self.end_date:
            order_lines = order_lines.filter(
                order__date_placed__lt=self.end_date + datetime.timedelta(days=1)
            )

        order_pks = order_lines.values_list("order", flat=True).distinct().order_by()
        orders = Order.objects.filter(pk__in=order_pks)

        # For bookkeeping, you need discount, shipping and any other additional costs as well.
        # That's what be build here.
        extra_lines = []
        for order_obj in orders:
            extra_lines.append(ShippingCostLineWrapper(order=order_obj))

            if order_obj.total_discount_excl_tax > D("0.00"):
                extra_lines.append(DiscountLineWrapper(order=order_obj))

            if order_obj.surcharges.exists():
                for surcharge in order_obj.surcharges.all():
                    if surcharge.incl_tax > D("0.00"):
                        extra_lines.append(
                            SurchargeLineWrapper(order=order_obj, surcharge=surcharge)
                        )

        additional_data = {"start_date": self.start_date, "end_date": self.end_date}

        # Merges the order_lines and the extra lines together.
        qs = list(chain(order_lines, extra_lines))
        qs.sort(key=lambda x: x.order.number, reverse=True)

        return self.formatter.generate_response(qs, **additional_data)

    def is_available_to(self, user):
        return user.is_staff


class ProductSalesReportGenerator(oscar.ReportGenerator):
    code = "product_sales_report"
    description = "Producten verkoop"

    formatters = {
        "CSV_formatter": ProductSalesReportCSVFormatter,
        "HTML_formatter": ProductSalesReportHTMLFormatter,
    }

    def get_queryset(self):
        lines_query = models.Q(
            line__order__status__in=[PIPELINE_STATUS_PAID, PIPELINE_STATUS_COMPLETE]
        )

        if self.start_date:
            lines_query.add(
                models.Q(line__order__date_placed__gte=self.start_date), models.Q.AND
            )

        if self.start_date:
            lines_query.add(
                models.Q(
                    line__order__date_placed__lte=self.end_date
                    + datetime.timedelta(days=1)
                ),
                models.Q.AND,
            )

        return (
            Product.objects.all()
            .annotate(sale_count=models.Sum("line__quantity", filter=lines_query))
            .filter(sale_count__gte=1)
        )

    def generate(self):
        return self.formatter.generate_response(
            self.queryset, start_date=self.start_date, end_date=self.end_date
        )
