/*
         * For calculating the offering cost when there could also be discount info involved.
         * Where it gets more complicated is when there are BULK_DISCOUNTs because each of the
         * tiers has both a maximum and minimum quantity that needs to be checked.
         */
        private OfferingsDisc CalcOfferingCost(OfferingsDisc offer)
        {
            // if the discount type is NULL the totalOfferingCost is just unit_retail * qty
            if (offer.Type == null)
            {
                offer.totalOfferingCost = (Math.Round(Convert.ToDecimal(offer.unit_retail) * offer.Quantity, 2)).ToString();
            }
            else if (offer.Type != "BULK_DISCOUNT") // if the discount isn't NULL and isn't a BULK_DSICOUNT
            {
                // check to max use the Quantity isn't > MaxQty otherwise the discount doesn't apply anymore
                if (offer.Quantity < offer.MaxQty)
                {
                    offer.totalOfferingCost = (Math.Round(Convert.ToDecimal(offer.discount_price) * offer.Quantity, 2)).ToString();
                }
                else
                {
                    offer.totalOfferingCost = (Math.Round(Convert.ToDecimal(offer.unit_retail) * offer.Quantity, 2)).ToString();
                }
            }
            else if (offer.Type == "BULK_DISCOUNT")
            {
                // iterate through all of the Tiers in the BULK_DISCOUNT starting from the last one to ensure we're applying the largest discount possible
                for (int ii = offer.Tiers.Count() - 1; ii >= 0; ii--)
                {
                    // check if Quantity falls within MinQty and MaxQty of the current Tier
                    if (offer.Quantity <= offer.Tiers[ii].MaxQty && offer.Quantity >= offer.Tiers[ii].MinQty)
                    {
                        offer.discount_price    = (Math.Round(Convert.ToDecimal(offer.unit_retail) * (1 - (offer.Tiers[ii].DiscountPercentage / 100)), 2)).ToString();
                        offer.totalOfferingCost = (Math.Round(Convert.ToDecimal(offer.discount_price) * offer.Quantity, 2)).ToString();
                        break;
                    }
                    else if (offer.Quantity >= offer.Tiers[ii].MaxQty && ii == 0) // set discount_price to null is no discounts apply
                    {
                        offer.discount_price    = null;
                        offer.totalOfferingCost = (Math.Round(Convert.ToDecimal(offer.unit_retail) * offer.Quantity, 2)).ToString();
                    }
                }
                if (offer.discount_price == null) // update total offering cost when no BULK_DISCOUNTS apply
                {
                    offer.totalOfferingCost = (Math.Round(Convert.ToDecimal(offer.unit_retail) * offer.Quantity, 2)).ToString();
                }
            }

            return(offer);
        }
        public async Task <IActionResult> AddToBasket(string offeringID, [FromHeader] string authorization, [FromQuery] int qty = 0)
        {
            // make sure an offering_key was sent - can't do anything without it
            if (offeringID == null)
            {
                return(BadRequest(new BadRequestError("no offering_key sent.")));
            }

            // make sure the quantity is greater than zero
            if (qty <= 0)
            {
                return(BadRequest(new BadRequestError("Quantity is zero")));
            }

            // get the user_id
            var ID = GetID();

            if (ID == null)
            {
                return(BadRequest(new BadRequestError("user_id not found.")));
            }

            // attempt to retreive the users Basket document
            var doc = await _bucket.GetAsync <BasketDisc>(ID);

            int        newOffering = 0;
            BasketDisc userDoc     = null;

            // check to see if a document was retreived, need to create a new one if it wasn't
            if (doc.Success)
            {
                userDoc = doc.Value;

                // documents are persistent once they have been created so update the doc with the new time if total_items is zero
                if (userDoc.total_items == 0)
                {
                    userDoc.Date = DateTimeOffset.Now.ToUnixTimeSeconds();
                }

                // check to see if the offeringID sent in the route is already in the OfferingsDisc array
                if (userDoc.OfferingsDisc.Exists(i => i.Offering_key == offeringID))
                {
                    OfferingsDisc offer = userDoc.OfferingsDisc.Find(i => i.Offering_key == offeringID);

                    // find the index of where the element with offeringID is located in the array
                    var index = userDoc.OfferingsDisc.IndexOf(offer, 0);

                    // update all the information about the offering in the basket
                    userDoc.OfferingsDisc[index].Quantity += qty;
                    userDoc.total_cost           = (Convert.ToDecimal(userDoc.total_cost) - Convert.ToDecimal(offer.totalOfferingCost)).ToString();
                    userDoc.OfferingsDisc[index] = CalcOfferingCost(userDoc.OfferingsDisc[index]);
                    userDoc.total_cost           = (Convert.ToDecimal(userDoc.total_cost) + Convert.ToDecimal(offer.totalOfferingCost)).ToString();
                }
                else // if the offeringID isn't in the array set the newOffering flag (used later)
                {
                    newOffering = 1;
                }
            }
            // if there isn't already a Basket document for the user or the newOffering flag is set
            if (!doc.Success || newOffering == 1)
            {
                // split the authorization header string to remove 'Bearer' and isolate the JWT token
                string[] auth = authorization.Split(' ');
                // use the GetOffering() function to send the HTTP request to the CatalogApi to get the information about the offering being added
                HttpResponseMessage httpResponse = await GetOffering(auth[1], "http://localhost:7000/catalog-api/products/disc/singleOffering/" + offeringID);

                // make sure the HTTP response from the CatalogApi was successful
                if (!httpResponse.IsSuccessStatusCode)
                {
                    return(BadRequest(new BadRequestError(httpResponse.StatusCode.ToString() + " HttpRequest Failed.")));
                }

                // read the response as a type OfferingsDisc (what we will be adding to the Basket document)
                OfferingsDisc offering = await httpResponse.Content.ReadAsAsync <OfferingsDisc>();

                offering.Quantity = qty;
                BasketDisc basket = null;

                // check to see if the discount type is a BULK_DISCOUNT, need to use interproces communication again to get the tiers information about it
                if (offering.Type != "BULK_DISCOUNT")
                {
                    offering = CalcOfferingCost(offering);
                }
                else if (offering.Type == "BULK_DISCOUNT")
                {
                    // get the BULK_DISCOUNT tiers information
                    httpResponse = await GetOffering(auth[1], "http://localhost:7000/catalog-api/products/disc/getDiscount/" + offering.Discount_key);

                    if (!httpResponse.IsSuccessStatusCode)
                    {
                        return(BadRequest(new BadRequestError(httpResponse.StatusCode.ToString() + " HttpRequest Failed.")));
                    }

                    // use the tiers information to correctly calculate the cost of the offering being added
                    offering.Tiers = new List <Tiers>();
                    offering.Tiers = await httpResponse.Content.ReadAsAsync <List <Tiers> >();

                    offering = CalcOfferingCost(offering);
                }

                // if newOffering is 0 update all the information and insert the Basket document
                if (newOffering == 0)
                {
                    basket               = new BasketDisc();
                    basket.Uid           = Guid.NewGuid();
                    basket.Date          = DateTimeOffset.Now.ToUnixTimeSeconds();
                    basket.total_items   = 1;
                    basket.OfferingsDisc = new List <OfferingsDisc>();
                    basket.OfferingsDisc.Add(offering);
                    basket.total_cost = offering.totalOfferingCost;

                    var res = await _bucket.UpsertAsync(ID, basket);

                    if (!res.Success)
                    {
                        return(BadRequest(new BadRequestError("Failed to add document to database - new basket")));
                    }

                    return(Ok(basket));
                }

                userDoc.total_cost    = (Convert.ToDecimal(userDoc.total_cost) + Convert.ToDecimal(offering.totalOfferingCost)).ToString();
                userDoc.OfferingsDisc = userDoc.OfferingsDisc.Prepend(offering).ToList();
                userDoc.total_items  += 1;
            }

            // insert the Basket document
            var result = await _bucket.UpsertAsync(ID, userDoc);

            if (!result.Success)
            {
                return(BadRequest(new BadRequestError("Failed to add userDoc to database")));
            }

            // reutrn 200 OK, we don't need to return userDoc this was for testing
            return(Ok(userDoc));
        }
        public async Task <IActionResult> UpdateQuant(string offeringID, int qty = 0)
        {
            var ID = GetID();

            if (ID == null)
            {
                return(BadRequest(new BadRequestError("user_id not found.")));
            }

            if (qty < 0)
            {
                return(BadRequest(new BadRequestError("Invalid quantity.")));
            }

            // using a view to only get the information from the Basket document that we need
            var query = new ViewQuery().From("dev_BasketDisc", "by_id").Key(new List <string> {
                ID, offeringID
            });
            var res = await _bucket.QueryAsync <dynamic>(query);

            // check to make sure the ViewQuery was successful and returned information
            if (!res.Success || res.Rows.Count() == 0)
            {
                return(NotFound(res.Message));
            }

            // set the variables that will be needed from the info retrieved from the query
            var           info       = res.Rows.ToList().First().Value;
            int           index      = info[0];
            OfferingsDisc offering   = JsonConvert.DeserializeObject <OfferingsDisc>(info[3].ToString());
            decimal       total_cost = Convert.ToDecimal(info[1]) - Convert.ToDecimal(offering.totalOfferingCost);

            if (qty == 0)
            {
                /*
                 * If the qty is 0 we need to remove that item from the users Basket document
                 * we also need to update the total_cost and the total_items
                 */
                var response = await _bucket.MutateIn <BasketDisc>(ID)
                               .Upsert("total_cost", total_cost.ToString())
                               .Upsert("total_items", info[2] - 1)
                               .Remove($"offeringsDisc[{index}]")
                               .ExecuteAsync();

                if (!response.Success)
                {
                    return(NotFound(new NotFoundError(response.Message)));
                }

                return(Ok(response));
            }

            // if qty > 0 update the users document
            offering.Quantity = qty;
            offering          = CalcOfferingCost(offering);
            total_cost       += Convert.ToDecimal(offering.totalOfferingCost);

            var update_res = await _bucket.MutateIn <BasketDisc>(ID)
                             .Upsert("total_cost", total_cost.ToString())
                             .Replace($"offeringsDisc[{index}]", offering)
                             .ExecuteAsync();

            if (!update_res.Success)
            {
                return(NotFound(new NotFoundError(update_res.Message)));
            }

            return(Ok(update_res));
        }
        public async Task <IActionResult> AddDocDiscount([FromBody] BasketDisc basket)
        {
            // check if the model binds correctly
            if (ModelState.IsValid)
            {
                // get the users ID from the HttpContext
                var ID = GetID();

                // The ID should never be NULL because they wouldn't have been authorized but checking anyways
                if (ID == null)
                {
                    return(BadRequest(new BadRequestError("user_id not found.")));
                }

                var doc = await _bucket.GetAsync <BasketDisc>(ID); // check to see if a document for the user exists or not

                if (!doc.Success)                                  // if the user doesn't already have a basket document make a new one
                {
                    // check to see if the GUID is set or not
                    if (!basket.Uid.HasValue)
                    {
                        basket.Uid = Guid.NewGuid();
                    }
                    // update the total number of offerings count in the document
                    basket.total_items = basket.OfferingsDisc.Count();
                    // attempt to insert the new document
                    var response = await _bucket.UpsertAsync(ID, basket);

                    // return a BadReuqest if this fails
                    if (!response.Success)
                    {
                        return(BadRequest(basket));
                    }
                    // otherwise return 200 OK
                    return(Ok(response.ToString()));
                }

                BasketDisc userDoc = doc.Value;

                // find if the product offering already exists, if it does replace it with the new one
                if (userDoc.OfferingsDisc.Exists(i => i.Offering_key == basket.OfferingsDisc[0].Offering_key))
                {
                    OfferingsDisc userOffering = userDoc.OfferingsDisc.Find(i => i.Offering_key == basket.OfferingsDisc[0].Offering_key);
                    // get the index of duplicate item currently stored in the basket doc if it eists
                    var index = userDoc.OfferingsDisc.IndexOf(userOffering, 0);

                    // if there is a duplicate item add the quantities together
                    if (index != -1)
                    {
                        userDoc.OfferingsDisc[index].Quantity += basket.OfferingsDisc[0].Quantity;
                    }
                }
                else // if there isn't a duplicate item insert the new item being added at the beginning of the list
                {
                    userDoc.OfferingsDisc = userDoc.OfferingsDisc.Prepend(basket.OfferingsDisc[0]).ToList();
                }

                // update the total count of the number of offerings stored in the document
                userDoc.total_items = userDoc.OfferingsDisc.Count();

                // attempt to insert the updated document into the Basket bucket
                var result = await _bucket.UpsertAsync(ID, userDoc);

                // if the upsert fails return a bad request
                if (!result.Success)
                {
                    return(BadRequest(basket));
                }

                // if document was successfully replaced return 200 OK
                return(Ok(basket));
            }

            return(Conflict());
        }