using CareFusion.Lib.StorageSystem;
using CareFusion.Lib.StorageSystem.Stock;
using ItSystem.Simulator.Base;
using Rowa.Observables;
using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;

namespace ItSystem.Simulator.Gui.Pages
{
    public partial class StockUpdateControl : UserControl
    {
        private readonly BindingSource _source;

        private readonly IDisposable _btnEnabledSubscription;
        private readonly IDisposable _dialogSubscription;


        private readonly Subject<bool> _criteriaValid = new Subject<bool>();
        private readonly Subject<bool> _batchNumberValid = new Subject<bool>();
        private readonly SourceSubject<IStorageSystem> _storageSystemSubject = new();
        private readonly Subject<BackgroundTaskInfo> _backgroundState = new Subject<BackgroundTaskInfo>();
        private readonly SourceSubject<AutoCompleteStringCollection> _internalArticleIdCompletionSubject = new();

        public BindingList<StockUpdateCriteria> Criteria { get; } = new BindingList<StockUpdateCriteria>();

        public StockUpdateControl()
        {
            InitializeComponent();
            _source = new BindingSource(Criteria, null);
            _source.AllowNew = true;
            Criteria.AllowRemove = true;
            Criteria.AllowEdit = true;
            Criteria.AllowNew = true;

            criteriaGridDataView.DataSource = _source;
            criteriaGridDataView.AllowUserToAddRows = true;
            criteriaGridDataView.AllowUserToDeleteRows = true;
            criteriaGridDataView.CellValidated += CriteriaGridDataView_CellValidated;
            criteriaGridDataView.EditingControlShowing += CriteriaGridDataView_EditingControlShowing;
      

            bgSendRequest.DoWork += BgSendRequest_DoWork;
            bgSendRequest.WorkerReportsProgress = true;
            bgSendRequest.ProgressChanged += BgSendRequest_ProgressChanged;
            Disposed += (_, _) =>
            {
                _dialogSubscription?.Dispose();
                _btnEnabledSubscription?.Dispose();
            };

            _dialogSubscription = _backgroundState.Subscribe(new DelegateObserver<BackgroundTaskInfo>(info =>
            {
                if (info.State is BackgroundTaskState.Idle or BackgroundTaskState.Busy) return;
                if (info.Exception != null)
                {
                    MessageBox.Show($"Failed to update stock: \r\n{info.Exception.Message}", "Error", MessageBoxButtons.OK);
                }
                else
                {
                    MessageBox.Show("Successfully updated stock", "Success", MessageBoxButtons.OK);
                }
            }));

            _batchNumberValid.Current = true;

            // check if data is valid -> criteria and batchnumber is valid
            var dataValidObservable = ObservableExtensions.Combine(_criteriaValid, _batchNumberValid).Map(x => x.All(b => b));

            // check if we can send data to storage system eg. data is valid and we have storage system
            var btnEnabledObservable =
                ObservableExtensions.Combine(dataValidObservable,
                            _storageSystemSubject.Map(x => x != null),
                            _backgroundState.Map(x => x.State == BackgroundTaskState.Idle))
                    .Map(x => x.All(b => b));

            _btnEnabledSubscription = btnEnabledObservable.Subscribe(new DelegateObserver<bool>(b =>
            {
                btnNew.Enabled = b;
            }));

            errorProvider.BlinkStyle = ErrorBlinkStyle.NeverBlink;
        }

        private void CriteriaGridDataView_CellValidated(object sender, DataGridViewCellEventArgs e)
        {
            bool noError = true;
            for (int rowIndex = 0; rowIndex < criteriaGridDataView.RowCount - 1; rowIndex++)
            {
                var criteria = Criteria[rowIndex];

                if (criteria is null) return;
                if (!criteria.IsValid())
                {
                    noError = false;
                    criteriaGridDataView.Rows[rowIndex].ErrorText = "Invalid Criteria";
                }
                else
                {
                    criteriaGridDataView.Rows[rowIndex].ErrorText = "";
                }

            }
            _criteriaValid.Current = noError;
        }


        private void BgSendRequest_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            var state = (BackgroundTaskInfo)e.UserState;
            _backgroundState.Current = state;
            // if the background task is finished we want to go directly to the idle state to allow further requests
            if (state.State == BackgroundTaskState.Complete)
            {
                _backgroundState.Current = BackgroundTaskInfo.Idle();
            }
        }

        private void BgSendRequest_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                bgSendRequest.ReportProgress(0, BackgroundTaskInfo.Busy());

                if (e.Argument is not IStockUpdate update)
                {
                    bgSendRequest.ReportProgress(100, BackgroundTaskInfo.Failed(new ArgumentNullException(nameof(IStockUpdate))));
                    return;
                }

                var result = update.Execute();
                if (result.IsSuccess)
                {
                    bgSendRequest.ReportProgress(100, BackgroundTaskInfo.Success());
                }
                else
                {
                    bgSendRequest.ReportProgress(100, BackgroundTaskInfo.Failed(new Exception(result.Reason)));
                }



            }
            catch (Exception ex)
            {
                bgSendRequest.ReportProgress(100, BackgroundTaskInfo.Failed(ex));
            }

        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            ValidateChildren();
        }

        public void SetStorageSystemProvider(IObservable<IStorageSystem> observable)
        {
            _storageSystemSubject.SetSource(observable);
        }

        public void SetArticleIdAutoCompleteSource(IObservable<AutoCompleteStringCollection> observable)
        {
           _internalArticleIdCompletionSubject.SetSource(observable);
        }
        

        private void CriteriaGridDataView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
        {
            int column = criteriaGridDataView.CurrentCell.ColumnIndex;
            string header = criteriaGridDataView.Columns[column].HeaderText;
            TextBox tb = e.Control as TextBox;

            if (header == nameof(StockUpdateCriteria.ArticleId))
            {
                tb.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
                tb.AutoCompleteSource = AutoCompleteSource.CustomSource;
                tb.AutoCompleteCustomSource = _internalArticleIdCompletionSubject.Current;
            }

            if (header == nameof(StockUpdateCriteria.PackId))
            {
                tb.KeyPress += Tb_KeyPress;
            }
            // keyboard handler seem to be shared between textboxes (e.g. dubugging shows added handlers for all rows)
            else
            {
                tb.KeyPress -= Tb_KeyPress;
            }

        }

        private void Tb_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar))
            {
                e.Handled = true;
            }

        }

        private void BtnNew_Click(object sender, EventArgs e)
        {
            var update = _storageSystemSubject.Current.CreateStockUpdate();
            var newBatchNumber = txtBatchNumber.Text;
            if (string.IsNullOrWhiteSpace(newBatchNumber))
            {
                update.BatchNumber = FieldUpdate<string>.Clear();
            }
            else
            {
                update.BatchNumber = FieldUpdate<string>.Set(newBatchNumber);
            }

            foreach (var criteria in Criteria.Where(x => x.IsValid()))
            {
                update.AddCriteria(criteria.ArticleId, criteria.BatchNumber,
                    criteria.ExternalId, criteria.PackId, criteria.StockLocationId,
                    criteria.MachineLocation, criteria.SerialNumber);
            }

            // queue request on background to not block ui
            bgSendRequest.RunWorkerAsync(update);

        }

        private void BtnClear_Click(object sender, EventArgs e)
        {
            txtBatchNumber.Text = string.Empty;
            Criteria.Clear();
            Validate();
            ValidateChildren();
        }
    }
}
