using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.SQLite;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Windows.Forms;

namespace BulkPrinting
{
    public partial class OrderForm : ObservedForm
    {
        private class OrderItem : INotifyPropertyChanged
        {
            private int _quantity;
            public int Quantity {
                get { return _quantity; }
                set
                {
                    _quantity = value;
                    _notifyPropertyChanged("ToString");
                }
            }
            public event PropertyChangedEventHandler PropertyChanged;
            private void _notifyPropertyChanged([CallerMemberName] String propertyName = "")
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
            public int ProductId;
            public string ProductDescription;
            public decimal FaceValue;
            public decimal DiscountPercentage;
            public override string ToString() {
                return this.Quantity.ToString() + " X " + this.ProductDescription;
            }
        }

        private BindingList<OrderItem> SelectedProducts = new BindingList<OrderItem>();
        public BatchForm SourceForm;

        public OrderForm()
        {
            InitializeComponent();
        }

        private void OrderForm_Load(object sender, EventArgs e)
        {
            SourceForm.Enabled = false;
            this.lstSelectedProducts.DataSource = SelectedProducts;
            this.lstSelectedProducts.DisplayMember = "ToString";
            bool Result = Utility.RESTRequest<ICollection<NetworkCatalogue>>(ref Globals.ProductCatalogue, "/api/products/");
            if (Result == true)
            {
                //lstSelectedProducts.Items.Clear();
                lstProductCatalogue.Items.Clear();
                foreach (NetworkCatalogue Network in Globals.ProductCatalogue) {
                    foreach (ProductSubCatalogue Product in Network.Products)
                    {
                        lstProductCatalogue.Items.Add(Product);
                    }
                }
                this.txtReference.Text = Globals.SessionData.Credentials.Payload.User.FirstName;
            }
            else {
                MessageBox.Show("Could not retrieve product catalogue. Please ensure that you are online.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                this.Close();
            }
            decimal RemainingUserLimit = 0;
            this.lblRemainingBalance.Text = "R" + Globals.SessionData.Credentials.Payload.User.Account.Balance.ToString("0.##");
            RemainingUserLimit = Utility.CheckRemainingUserLimit(UserLimits.UserLimitTypes.BulkOrder);
            if (RemainingUserLimit > -1)
            {
                this.lblRemainingAllowance.Text = "R" + RemainingUserLimit.ToString("0.##");
                this.lblRemainingAllowanceText.Show();
                this.lblRemainingAllowance.Show();
            }
            else
            {
                this.lblRemainingAllowanceText.Hide();
                this.lblRemainingAllowance.Hide();
            }
        }


        private void btnPlaceOrder_Click(object sender, EventArgs e)
        {
            if (this.SelectedProducts.Count==0)
            {
                MessageBox.Show("Please add products to the order list on the right before clicking the Order button.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            if (this.txtReference.Text == "")
            {
                MessageBox.Show("Please enter a reference code.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            string OrderReviewMessage = "Please ensure that you carefully review the list of products you wish to order:\n";
            decimal TotalCost = 0;
            for (int i = 0; i < this.SelectedProducts.Count; i++)
            {
                OrderItem OrderedItem = this.SelectedProducts[i];
                decimal ItemCost = OrderedItem.Quantity * OrderedItem.FaceValue * (1 - (OrderedItem.DiscountPercentage / 100));
                TotalCost += ItemCost;
                OrderReviewMessage += "R" + ItemCost.ToString("0.##") + " - " + OrderedItem.Quantity.ToString() + " X " + OrderedItem.ProductDescription + "\n";
            }
            OrderReviewMessage += "______________________\nTotal Order Cost: R" + TotalCost.ToString("0.##") + "\n";
            OrderReviewMessage += "Remaining Balance After Order: R" + (Globals.SessionData.Credentials.Payload.User.Account.Balance - TotalCost).ToString("0.##") + "\n\n";
            OrderReviewMessage += "Would you like to proceed with the order?";
            DialogResult PlaceOrder = MessageBox.Show(OrderReviewMessage, "Proceed?", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (PlaceOrder == DialogResult.Yes) {
                string InternalReference = Utility.GetNextInternalReference();
                Globals.DB.ExecuteNonQuery(
                    "INSERT INTO Orders (UserId,UserName,OrderDate,OrderCost,BalanceAfterOrder,InternalReference) VALUES (@userid,@username,datetime('now'),@ordercost,@balanceafterorder,@internalreference)",
                    new SQLiteParameter("@userid", Globals.SessionData.Credentials.Payload.User.Id),
                    new SQLiteParameter("@username", Globals.SessionData.Credentials.Payload.User.FirstName + " " + Globals.SessionData.Credentials.Payload.User.Surname),
                    new SQLiteParameter("@ordercost", TotalCost),
                    new SQLiteParameter("@balanceafterorder", Globals.SessionData.Credentials.Payload.User.Account.Balance - TotalCost),
                    new SQLiteParameter("@internalreference", InternalReference));

                int OrderId = int.Parse(Globals.DB.ExecuteScalar("SELECT MAX(Id) FROM Orders").ToString());

                lock (Globals.DB.WriteLock)
                {
                    using (var trans = Globals.DB.BeginTransaction())
                    {
                        var Sql = "INSERT INTO OrderedItems (OrderId,Cost,Quantity,Description,ProductId) VALUES (@orderId,@cost,@quantity,@description,@productid)";
                        using (var Command = Globals.DB.CreateCommand(Sql, trans))
                        {
                            for (int i = 0; i < this.SelectedProducts.Count; i++)
                            {
                                OrderItem OrderedItem = this.SelectedProducts[i];
                                decimal ItemCost = OrderedItem.Quantity * OrderedItem.FaceValue * (1 - (OrderedItem.DiscountPercentage / 100));
                                Command.Parameters.Clear();
                                Command.Parameters.AddWithValue("@orderId", OrderId);
                                Command.Parameters.AddWithValue("@cost", ItemCost);
                                Command.Parameters.AddWithValue("@quantity", OrderedItem.Quantity);
                                Command.Parameters.AddWithValue("@description", OrderedItem.ProductDescription);
                                Command.Parameters.AddWithValue("@productid", OrderedItem.ProductId);
                                Command.ExecuteNonQuery();
                            }
                        }
                        trans.Commit();
                    }
                }

                this.Hide();
                string CustomerReference = txtReference.Text;

                ProgressDialog Progress = new ProgressDialog();
                Progress.Show();
                Thread t = new Thread(() => {
                    var db = Globals.DB;
                    //using (var db = Utility.OpenDBConnection())
                    {
                        Progress.UpdateProgressText("Please wait...");
                        Progress.UpdateProgressBar(0);
                        for (int i = 0; i < this.SelectedProducts.Count; i++)
                        {
                            OrderItem OrderedItem = this.SelectedProducts[i];
                            Progress.UpdateProgressText("Placing order for " + OrderedItem.Quantity.ToString() + " of " + OrderedItem.ProductDescription + "...");
                            OrderPlacementData OrderData = new OrderPlacementData();
                            OrderData.ProductId = OrderedItem.ProductId;
                            OrderData.Quantity = OrderedItem.Quantity;
                            OrderData.CustomerReference = CustomerReference;
                            OrderData.InternalReference = InternalReference;
                            BatchListing OrderedBatch = new BatchListing();
                            bool OrderResult = Utility.RESTRequest<OrderPlacementData, BatchListing>(OrderData, ref OrderedBatch, "/api/batches/");
                            if (OrderedBatch.Batch == null)
                            {
                                string ErrorMessage = "An error occurred while attempting to order " + OrderedItem.Quantity.ToString() + " of " + OrderedItem.ProductDescription;
                                if (i < (SelectedProducts.Count - 1))
                                {
                                    ErrorMessage += "\nPress OK to continue attempting to order remaining items.";
                                }
                                MessageBox.Show(ErrorMessage, "Error Ordering", MessageBoxButtons.OK, MessageBoxIcon.Error);
                                continue;
                            }
                            //TODO: Handle failed order
                            Utility.SaveBatch(db, OrderedBatch.Batch);
                            SourceForm.NewBatchAvailable();
                            Globals.SessionData.Credentials.Payload.User.Account.Balance = OrderedBatch.RemainingBalance;
                            Utility.AddUserUsage(db, UserLimits.UserLimitTypes.BulkOrder, OrderedBatch.Batch.Cost);
                            Progress.UpdateProgressBar((int)Math.Round(100 * (((decimal)i + 1) / (decimal)SelectedProducts.Count)));
                            if (OrderedBatch.Batch.DeliveredQuantity == 0)
                            {
                                string ErrorMessage = "Your order for " +
                                    OrderedItem.Quantity.ToString() + " of " + OrderedItem.ProductDescription +
                                    " could not be fulfilled as no vouchers are available.";
                                if (i < (SelectedProducts.Count - 1))
                                {
                                    ErrorMessage += "\nPress OK to continue attempting to order remaining items.";
                                }
                                MessageBox.Show(ErrorMessage, "Error Ordering", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                            Thread.Sleep(50);
                        }

                        Progress.Invoke((Action)(() => Progress.Close()));
                        this.Invoke((Action)(() => { SourceForm.PopulateGrid(); this.Close(); }));
                    }
                });
                t.Start();
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnAddProduct_Click(object sender, EventArgs e)
        {
            if (this.lstProductCatalogue.SelectedIndex == -1)
            {
                MessageBox.Show("Please select a product from the left list to add to the order list on the right.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            if (this.numQuantity.Value < 1)
            {
                MessageBox.Show("Please select a valid quantity.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            decimal TotalValue = 0;
            decimal TotalCost = 0;


            ProductSubCatalogue SelectedProduct = (ProductSubCatalogue)this.lstProductCatalogue.SelectedItem;
            bool ProductFound = false;
            decimal SelectionCost;
            foreach (OrderItem OrderedItem in this.SelectedProducts)
            {
                TotalValue += OrderedItem.Quantity * OrderedItem.FaceValue;
                TotalCost += OrderedItem.Quantity * OrderedItem.FaceValue * (1 - (OrderedItem.DiscountPercentage / 100));
            }
            foreach (OrderItem OrderedItem in this.SelectedProducts)
            {
                if (SelectedProduct.Id == OrderedItem.ProductId)
                {
                    TotalValue += (decimal)this.numQuantity.Value * OrderedItem.FaceValue;
                    SelectionCost = (decimal)this.numQuantity.Value * OrderedItem.FaceValue * (1 - (OrderedItem.DiscountPercentage / 100));
                    TotalCost += SelectionCost;
                    if (TotalCost > Globals.SessionData.Credentials.Payload.User.Account.Balance)
                    {
                        MessageBox.Show("Your balance is insufficient to add this selection (R" + SelectionCost.ToString("0.##") + ").", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return;
                    }
                    if (!Utility.IsValueWithinRemainingUserLimit(UserLimits.UserLimitTypes.BulkOrder,TotalCost))
                    {
                        MessageBox.Show("Adding this selection (R" + SelectionCost.ToString("0.##") + ") exceeds your user limit for the day.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return;
                    }
                    ProductFound = true;
                    OrderedItem.Quantity += (int)this.numQuantity.Value;
                }
            }
            if (ProductFound == false)
            {
                TotalValue += (decimal)this.numQuantity.Value * SelectedProduct.FaceValue;
                SelectionCost = (decimal)this.numQuantity.Value * SelectedProduct.FaceValue * (1 - (SelectedProduct.DiscountPercentage / 100));
                TotalCost += SelectionCost;
                if (TotalCost > Globals.SessionData.Credentials.Payload.User.Account.Balance)
                {
                    MessageBox.Show("Your balance is insufficient to add this selection (R" + SelectionCost.ToString("0.##") + ").", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
                if (!Utility.IsValueWithinRemainingUserLimit(UserLimits.UserLimitTypes.BulkOrder, TotalCost))
                {
                    MessageBox.Show("Adding this selection (R" + SelectionCost.ToString("0.##") + ") exceeds your user limit for the day.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
                OrderItem NewOrderItem = new OrderItem();
                NewOrderItem.ProductId = SelectedProduct.Id;
                NewOrderItem.ProductDescription = SelectedProduct.Description;
                NewOrderItem.Quantity = (int)this.numQuantity.Value;
                NewOrderItem.FaceValue = SelectedProduct.FaceValue;
                NewOrderItem.DiscountPercentage = SelectedProduct.DiscountPercentage;
                this.SelectedProducts.Add(NewOrderItem);
            }
            this.lblTotalValue.Text = "R"+TotalValue.ToString("0.##");
            this.lblTotalCost.Text = "R"+TotalCost.ToString("0.##");
        }

        private void btnRemoveProduct_Click(object sender, EventArgs e)
        {
            if (this.lstProductCatalogue.SelectedIndex != -1)
            {
                this.SelectedProducts.Remove((OrderItem)this.lstSelectedProducts.SelectedItem);
            }
        }

        private void OrderForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            SourceForm.Enabled = true;
        }
    }
}
