from saleboxdjango.lib.binpack import BinPack


class SaleboxShippingOptions:
    DEFAULT_SELECT = "FIRST"  # or 'CHEAPEST' or None
    REMOVE_UNAVAILABLE = True
    SORT_BY = "PRICE_ASC"  # 'LABEL' or None

    # variant variable names
    SHIPPING_WIDTH = "int_1"
    SHIPPING_HEIGHT = "int_2"
    SHIPPING_DEPTH = "int_3"
    SHIPPING_WEIGHT = "shipping_weight"

    def get_options(self):
        # This is the function you need to replace.
        # Add your own functions here, e.g.
        #
        #   return [
        #     self._example_option_1(),
        #     self._example_option_2(),
        #   ]
        #
        # Useful vars:
        #   self.checkout['shipping_address']['address']['country']
        #   self.checkout['shipping_address']['address']['country_state']
        #
        return [
            self._example_option_1(),
            self._example_option_2(),
        ]

    def go(self, request, checkout, context):
        self.request = request
        self.checkout = checkout
        self.context = context

        # get packages into a handy list for bin packing
        self.meta = {
            "total_items": 0,
            "total_items_onshelf": 0,
            "total_items_preorder": 0,
            "total_weight": 0,
            "total_weight_onshelf": 0,
            "total_weight_preorder": 0,
            "items": [],
            "items_onshelf": [],
            "items_preorder": [],
        }
        for b in checkout["basket"]["items"]:
            for i in range(0, b["qty"]):
                self.meta["total_items"] += 1
                self.meta["total_weight"] += b["variant"][self.SHIPPING_WEIGHT] or 0
                self.meta["items"].append(
                    {
                        "variant_id": b["variant"]["id"],
                        "width": b["variant"][self.SHIPPING_WIDTH],
                        "height": b["variant"][self.SHIPPING_HEIGHT],
                        "depth": b["variant"][self.SHIPPING_DEPTH],
                        "weight": b["variant"][self.SHIPPING_WEIGHT],
                    }
                )

                if b["variant"]["preorder_flag"]:
                    self.meta["total_items_preorder"] += 1
                    self.meta["total_weight_preorder"] += (
                        b["variant"][self.SHIPPING_WEIGHT] or 0
                    )
                    self.meta["items_preorder"].append(
                        {
                            "variant_id": b["variant"]["id"],
                            "width": b["variant"][self.SHIPPING_WIDTH],
                            "height": b["variant"][self.SHIPPING_HEIGHT],
                            "depth": b["variant"][self.SHIPPING_DEPTH],
                            "weight": b["variant"][self.SHIPPING_WEIGHT],
                        }
                    )
                else:
                    self.meta["total_items_onshelf"] += 1
                    self.meta["total_weight_onshelf"] += (
                        b["variant"][self.SHIPPING_WEIGHT] or 0
                    )
                    self.meta["items_onshelf"].append(
                        {
                            "variant_id": b["variant"]["id"],
                            "width": b["variant"][self.SHIPPING_WIDTH],
                            "height": b["variant"][self.SHIPPING_HEIGHT],
                            "depth": b["variant"][self.SHIPPING_DEPTH],
                            "weight": b["variant"][self.SHIPPING_WEIGHT],
                        }
                    )

        # build options
        opts = self.get_options()

        # remove nulls
        # if an shipping option is not available, simply return None. However...
        opts = [o for o in opts if o is not None]

        # optional: remove unavailable
        # you *may* want to keep unavailable options in the list but greyed out,
        # in which case, return the dict with ['available'] = False
        if self.REMOVE_UNAVAILABLE:
            opts = [o for o in opts if o["available"] == True]

        # set selected value (if exists)
        if self.checkout["shipping_method"]["id"] is not None:
            option = next(
                o for o in opts if o["id"] == self.checkout["shipping_method"]["id"]
            )
            option["selected"] = True

        # optional: sorting
        if self.SORT_BY == "PRICE_ASC":
            opts = sorted(opts, key=lambda k: k["price"])
        elif self.SORT_BY == "LABEL":
            opts = sorted(opts, key=lambda k: k["label"]["label"])

        # optional: default select
        if len(opts) > 0:
            if self.DEFAULT_SELECT == "CHEAPEST":
                tmp = sorted(opts, key=lambda k: k["price"])
                id = tmp[0]["id"]
                option = next(o for o in opts if o["id"] == id)
                option["selected"] = True
            elif self.DEFAULT_SELECT == "FIRST":
                opts[0]["selected"] = True

        # calculate combined_price
        for o in opts:
            o["combined_price"] = self.checkout["basket"]["sale_price"] + o["price"]

        # return
        self.context["shipping_options"] = opts
        return self.context

    def init_option(self, id, code, label, remarks, service, meta=None):
        return {
            "available": True,
            "code": code,
            "id": id,
            "label": {
                "label": label,  # e.g. 'Post Office'
                "remarks": remarks,  # e.g. '2-3 days'
                "service": service,  # 'ExpressPost'
            },
            "meta": meta,
            "price": 0,
            "selected": False,
        }

    def _do_binpack(self, containers, packages):
        # containers example:
        # [
        #   ['name', price, width, height, depth],
        #   ['name', price, width, height, depth],
        # ]
        bp = BinPack()

        # add containers
        for c in containers:
            bp.add_bin(c[0], c[1], c[2], c[3], c[4])

        # add packages
        for p in packages:
            bp.add_package(
                p["variant_id"],
                p["width"] or 0,
                p["height"] or 0,
                p["depth"] or 0,
            )

        return bp.go()

    def _example_option_1(self):
        method = self.init_option(
            1, "postoffice", "Post Office", "2-4 days", "Surface mail"
        )

        method["price"] = 10000
        return method

    def _example_option_2(self):
        method = self.init_option(
            2, "postofficeexpress", "Post Office", "1 day", "NextDay"
        )

        method["price"] = 25000
        return method
