using System.Data;
using System.Globalization;
using ComputerSalesApp.Data;
using ComputerSalesApp.Models;
using ComputerSalesApp.Services;

namespace ComputerSalesApp.UI;

internal sealed class EditRecordForm : Form
{
    private readonly TableDefinition _table;
    private readonly DataRowView? _row;
    private readonly bool _isEdit;
    private readonly TableRepository _repository = new();
    private readonly Dictionary<string, Control> _controls = new();
    private readonly TableLayoutPanel _layout = new();
    private readonly Button _saveButton = new();
    private readonly Button _cancelButton = new();
    private readonly TextBox _orderItemTotalBox = new();
    private bool _isLoadingOrderItemFields;

    public Dictionary<string, object?> Values { get; } = new();

    public EditRecordForm(TableDefinition table, DataRowView? row)
    {
        _table = table;
        _row = row;
        _isEdit = row != null;

        Text = (_isEdit ? "Изменение: " : "Добавление: ") + table.DisplayName;
        StartPosition = FormStartPosition.CenterParent;
        MinimumSize = new Size(560, 220);
        Width = 620;
        Height = Math.Min(760, 170 + table.Fields.Count * 56 + (table.TableName == "order_items" ? 56 : 0));
        BackColor = AppTheme.Background;

        BuildUi();
        Load += EditRecordForm_Load;
    }

    private void BuildUi()
    {
        var root = new TableLayoutPanel
        {
            Dock = DockStyle.Fill,
            RowCount = 2,
            ColumnCount = 1,
            Padding = new Padding(15),
            BackColor = AppTheme.Background
        };
        root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
        root.RowStyles.Add(new RowStyle(SizeType.Absolute, 55));
        Controls.Add(root);

        _layout.Dock = DockStyle.Fill;
        _layout.AutoScroll = true;
        _layout.BackColor = AppTheme.Background;
        _layout.ColumnCount = 2;
        _layout.RowCount = _table.Fields.Count + (_table.TableName == "order_items" ? 1 : 0);
        _layout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 190));
        _layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
        root.Controls.Add(_layout, 0, 0);

        int rowIndex = 0;
        foreach (FieldDefinition field in _table.Fields)
        {
            var label = new Label
            {
                Text = field.Header + (field.Required && !(_isEdit && field.IsPassword) ? " *" : ""),
                Dock = DockStyle.Fill,
                TextAlign = ContentAlignment.MiddleLeft,
                Font = AppTheme.Regular(10f)
            };
            _layout.Controls.Add(label, 0, rowIndex);

            Control control = CreateControl(field);
            control.Dock = DockStyle.Top;
            control.Margin = new Padding(3, 8, 3, 3);
            _controls[field.ColumnName] = control;
            _layout.Controls.Add(control, 1, rowIndex);
            _layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 56));
            rowIndex++;
        }

        if (_table.TableName == "order_items")
        {
            var totalLabel = new Label
            {
                Text = "Сумма",
                Dock = DockStyle.Fill,
                TextAlign = ContentAlignment.MiddleLeft,
                Font = AppTheme.Regular(10f)
            };
            _layout.Controls.Add(totalLabel, 0, rowIndex);

            _orderItemTotalBox.ReadOnly = true;
            _orderItemTotalBox.Font = AppTheme.Regular(10f);
            _orderItemTotalBox.Dock = DockStyle.Top;
            _orderItemTotalBox.Margin = new Padding(3, 8, 3, 3);
            _layout.Controls.Add(_orderItemTotalBox, 1, rowIndex);
            _layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 56));
        }

        var buttonPanel = new FlowLayoutPanel
        {
            Dock = DockStyle.Fill,
            FlowDirection = FlowDirection.RightToLeft,
            BackColor = AppTheme.Background
        };
        root.Controls.Add(buttonPanel, 0, 1);

        _cancelButton.Text = "Отмена";
        _cancelButton.Width = 120;
        _cancelButton.Height = 38;
        AppTheme.StyleSecondaryButton(_cancelButton);
        _cancelButton.Click += (_, _) => DialogResult = DialogResult.Cancel;
        buttonPanel.Controls.Add(_cancelButton);

        _saveButton.Text = "Сохранить";
        _saveButton.Width = 120;
        _saveButton.Height = 38;
        AppTheme.StylePrimaryButton(_saveButton);
        _saveButton.Click += SaveButton_Click;
        buttonPanel.Controls.Add(_saveButton);
    }

    private Control CreateControl(FieldDefinition field)
    {
        if (_table.TableName == "order_items" && field.ColumnName == "unit_price")
        {
            return new TextBox
            {
                ReadOnly = true,
                Font = AppTheme.Regular(10f),
                TextAlign = HorizontalAlignment.Left
            };
        }

        switch (field.Kind)
        {
            case FieldKind.Integer:
                return new NumericUpDown
                {
                    Minimum = field.MinValue ?? 0,
                    Maximum = field.MaxValue ?? 100000000,
                    DecimalPlaces = 0,
                    ThousandsSeparator = true,
                    Font = AppTheme.Regular(10f)
                };

            case FieldKind.Decimal:
                return new NumericUpDown
                {
                    Minimum = field.MinValue ?? 0,
                    Maximum = field.MaxValue ?? 100000000,
                    DecimalPlaces = 2,
                    ThousandsSeparator = true,
                    Font = AppTheme.Regular(10f),
                    Increment = 100
                };

            case FieldKind.Date:
                var picker = new DateTimePicker
                {
                    Format = DateTimePickerFormat.Short,
                    Font = AppTheme.Regular(10f)
                };
                if (field.ColumnName == "birth_date")
                {
                    picker.MaxDate = DateTime.Today;
                }
                return picker;

            case FieldKind.Boolean:
                return new CheckBox
                {
                    Text = "Да",
                    Checked = true,
                    Font = AppTheme.Regular(10f)
                };

            case FieldKind.Combo:
                return new ComboBox
                {
                    DropDownStyle = ComboBoxStyle.DropDownList,
                    Font = AppTheme.Regular(10f)
                };

            case FieldKind.Status:
            case FieldKind.Role:
                var combo = new ComboBox
                {
                    DropDownStyle = ComboBoxStyle.DropDownList,
                    Font = AppTheme.Regular(10f)
                };
                combo.Items.AddRange(field.Items ?? Array.Empty<string>());
                return combo;

            case FieldKind.Password:
                return new TextBox
                {
                    UseSystemPasswordChar = true,
                    Font = AppTheme.Regular(10f),
                    MaxLength = field.MaxLength > 0 ? field.MaxLength : 32767,
                    PlaceholderText = _isEdit ? "Оставьте пустым, чтобы не менять пароль" : string.Empty
                };

            default:
                return new TextBox
                {
                    Font = AppTheme.Regular(10f),
                    MaxLength = field.MaxLength > 0 ? field.MaxLength : 32767,
                    Multiline = field.ColumnName is "address" or "characteristics",
                    Height = field.ColumnName is "address" or "characteristics" ? 70 : 26
                };
        }
    }

    private async void EditRecordForm_Load(object? sender, EventArgs e)
    {
        try
        {
            _isLoadingOrderItemFields = true;

            foreach (FieldDefinition field in _table.Fields.Where(f => f.Kind == FieldKind.Combo))
            {
                if (_table.TableName == "order_items" && field.ColumnName == "product_id")
                {
                    continue;
                }

                var combo = (ComboBox)_controls[field.ColumnName];
                combo.DataSource = await _repository.LoadComboItemsAsync(field);
                combo.ValueMember = nameof(ComboItem.Id);
                combo.DisplayMember = nameof(ComboItem.Text);
            }

            FillInitialValues();

            if (_table.TableName == "order_items")
            {
                await ConfigureOrderItemFormAsync();
            }

            _isLoadingOrderItemFields = false;
        }
        catch (Exception ex)
        {
            ErrorHandler.Show("Ошибка загрузки формы", "Не удалось загрузить данные для формы.", ex);
            DialogResult = DialogResult.Cancel;
            Close();
        }
    }

    private void FillInitialValues()
    {
        if (_row == null)
        {
            foreach (FieldDefinition field in _table.Fields)
            {
                if ((field.Kind == FieldKind.Status || field.Kind == FieldKind.Role) && _controls[field.ColumnName] is ComboBox combo && combo.Items.Count > 0)
                {
                    combo.SelectedIndex = 0;
                }
            }
            return;
        }

        foreach (FieldDefinition field in _table.Fields)
        {
            if (field.IsPassword)
            {
                continue;
            }

            if (_table.TableName == "order_items" && field.ColumnName == "product_id")
            {
                continue;
            }

            object value = _row.Row.Table.Columns.Contains(field.ColumnName) ? _row[field.ColumnName] : DBNull.Value;
            if (value == DBNull.Value)
            {
                SetEmptyValue(field);
                continue;
            }

            Control control = _controls[field.ColumnName];
            switch (control)
            {
                case TextBox textBox:
                    textBox.Text = Convert.ToString(value) ?? string.Empty;
                    break;
                case NumericUpDown numeric:
                    numeric.Value = Convert.ToDecimal(value);
                    break;
                case DateTimePicker picker:
                    picker.Value = Convert.ToDateTime(value);
                    break;
                case CheckBox checkBox:
                    checkBox.Checked = Convert.ToBoolean(value);
                    break;
                case ComboBox combo when field.Kind == FieldKind.Combo:
                    combo.SelectedValue = Convert.ToInt32(value);
                    break;
                case ComboBox combo:
                    combo.SelectedItem = Convert.ToString(value);
                    break;
            }
        }
    }

    private async Task ConfigureOrderItemFormAsync()
    {
        if (!_controls.TryGetValue("order_id", out Control? orderControl) || orderControl is not ComboBox orderCombo)
        {
            return;
        }

        if (!_controls.TryGetValue("product_id", out Control? productControl) || productControl is not ComboBox productCombo)
        {
            return;
        }

        productCombo.ValueMember = nameof(ComboItem.Id);
        productCombo.DisplayMember = nameof(ComboItem.Text);
        productCombo.Enabled = orderCombo.SelectedValue != null;

        orderCombo.SelectedValueChanged += async (_, _) =>
        {
            if (_isLoadingOrderItemFields)
            {
                return;
            }

            await LoadProductsForSelectedOrderAsync(null);
        };

        productCombo.SelectedValueChanged += async (_, _) =>
        {
            if (_isLoadingOrderItemFields)
            {
                return;
            }

            await SetPriceForSelectedProductAsync();
        };

        if (_controls.TryGetValue("quantity", out Control? quantityControl) && quantityControl is NumericUpDown quantityNumeric)
        {
            quantityNumeric.ValueChanged += (_, _) => UpdateOrderItemTotal();
        }

        int? selectedProductId = null;
        if (_row != null && _row.Row.Table.Columns.Contains("product_id") && _row["product_id"] != DBNull.Value)
        {
            selectedProductId = Convert.ToInt32(_row["product_id"]);
        }

        await LoadProductsForSelectedOrderAsync(selectedProductId);

        if (selectedProductId.HasValue)
        {
            productCombo.SelectedValue = selectedProductId.Value;
        }

        await SetPriceForSelectedProductAsync();
    }

    private async Task LoadProductsForSelectedOrderAsync(int? selectedProductId)
    {
        if (_controls["order_id"] is not ComboBox orderCombo || _controls["product_id"] is not ComboBox productCombo)
        {
            return;
        }

        if (orderCombo.SelectedValue == null)
        {
            productCombo.DataSource = new List<ComboItem>();
            productCombo.Enabled = false;
            return;
        }

        int orderId = Convert.ToInt32(orderCombo.SelectedValue);
        List<ComboItem> products = await _repository.LoadProductsForOrderAsync(orderId, selectedProductId);

        productCombo.DataSource = products;
        productCombo.ValueMember = nameof(ComboItem.Id);
        productCombo.DisplayMember = nameof(ComboItem.Text);
        productCombo.Enabled = products.Count > 0;

        if (products.Count == 0)
        {
            SetUnitPriceValue(0);

            MessageBox.Show("На складе выбранного заказа нет товаров с положительным остатком.", "Проверка данных", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        }
    }

    private async Task SetPriceForSelectedProductAsync()
    {
        if (_controls["product_id"] is not ComboBox productCombo)
        {
            return;
        }

        if (productCombo.SelectedValue == null)
        {
            SetUnitPriceValue(0);
            return;
        }

        int productId = Convert.ToInt32(productCombo.SelectedValue);
        decimal price = await _repository.LoadProductPriceAsync(productId);
        SetUnitPriceValue(price);
    }

    private void SetUnitPriceValue(decimal price)
    {
        if (_controls.TryGetValue("unit_price", out Control? priceControl))
        {
            if (priceControl is TextBox priceTextBox)
            {
                priceTextBox.Text = price.ToString("N2", CultureInfo.CurrentCulture);
            }
            else if (priceControl is NumericUpDown priceNumeric)
            {
                priceNumeric.Value = Math.Min(priceNumeric.Maximum, Math.Max(priceNumeric.Minimum, price));
            }
        }

        UpdateOrderItemTotal();
    }

    private void UpdateOrderItemTotal()
    {
        if (_table.TableName != "order_items")
        {
            return;
        }

        int quantity = 0;
        decimal price = 0;

        if (_controls.TryGetValue("quantity", out Control? quantityControl) && quantityControl is NumericUpDown quantityNumeric)
        {
            quantity = Convert.ToInt32(quantityNumeric.Value);
        }

        if (_controls.TryGetValue("unit_price", out Control? priceControl))
        {
            if (priceControl is TextBox priceTextBox)
            {
                decimal.TryParse(priceTextBox.Text, NumberStyles.Number, CultureInfo.CurrentCulture, out price);
            }
            else if (priceControl is NumericUpDown priceNumeric)
            {
                price = priceNumeric.Value;
            }
        }

        _orderItemTotalBox.Text = (quantity * price).ToString("N2", CultureInfo.CurrentCulture);
    }

    private void SetEmptyValue(FieldDefinition field)
    {
        if (_controls[field.ColumnName] is ComboBox combo && field.Nullable)
        {
            combo.SelectedIndex = 0;
        }
        else if (_controls[field.ColumnName] is TextBox textBox)
        {
            textBox.Text = string.Empty;
        }
    }

    private void SaveButton_Click(object? sender, EventArgs e)
    {
        if (!ValidateAndCollectValues())
        {
            return;
        }

        DialogResult = DialogResult.OK;
        Close();
    }

    private bool ValidateAndCollectValues()
    {
        Values.Clear();

        foreach (FieldDefinition field in _table.Fields)
        {
            Control control = _controls[field.ColumnName];
            object? value;

            switch (control)
            {
                case TextBox textBox:
                    string text = textBox.Text.Trim();
                    bool passwordMayBeEmpty = _isEdit && field.IsPassword;

                    if (field.Required && !passwordMayBeEmpty && string.IsNullOrWhiteSpace(text))
                    {
                        WarnRequired(field);
                        return false;
                    }

                    if (field.Kind == FieldKind.Decimal)
                    {
                        if (!decimal.TryParse(text, NumberStyles.Number, CultureInfo.CurrentCulture, out decimal decimalValue))
                        {
                            MessageBox.Show($"Поле \"{field.Header}\" должно быть числом.", "Проверка данных", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                            return false;
                        }

                        if (field.MinValue.HasValue && decimalValue < field.MinValue.Value)
                        {
                            MessageBox.Show($"Поле \"{field.Header}\" не может быть меньше {field.MinValue.Value}.", "Проверка данных", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                            return false;
                        }

                        value = decimalValue;
                        break;
                    }

                    if (field.MaxLength > 0 && text.Length > field.MaxLength)
                    {
                        MessageBox.Show($"Поле \"{field.Header}\" не должно быть длиннее {field.MaxLength} символов.", "Проверка данных", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                        return false;
                    }

                    value = string.IsNullOrWhiteSpace(text) && field.Nullable ? null : text;
                    break;

                case NumericUpDown numeric:
                    value = field.Kind == FieldKind.Integer ? Convert.ToInt32(numeric.Value) : numeric.Value;
                    if (field.MinValue.HasValue && Convert.ToDecimal(value) < field.MinValue.Value)
                    {
                        MessageBox.Show($"Поле \"{field.Header}\" не может быть меньше {field.MinValue.Value}.", "Проверка данных", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                        return false;
                    }
                    break;

                case DateTimePicker picker:
                    if (field.ColumnName == "birth_date" && picker.Value.Date > DateTime.Today)
                    {
                        MessageBox.Show("Дата рождения не может быть в будущем.", "Проверка данных", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                        return false;
                    }
                    value = picker.Value.Date;
                    break;

                case CheckBox checkBox:
                    value = checkBox.Checked;
                    break;

                case ComboBox combo when field.Kind == FieldKind.Combo:
                    if (field.Required && combo.SelectedValue == null)
                    {
                        WarnRequired(field);
                        return false;
                    }
                    value = combo.SelectedValue;
                    break;

                case ComboBox combo:
                    if (field.Required && combo.SelectedItem == null)
                    {
                        WarnRequired(field);
                        return false;
                    }
                    value = Convert.ToString(combo.SelectedItem);
                    break;

                default:
                    value = null;
                    break;
            }

            Values[field.ColumnName] = value;
        }

        return true;
    }

    private static void WarnRequired(FieldDefinition field)
    {
        MessageBox.Show($"Заполните обязательное поле \"{field.Header}\".", "Проверка данных", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    }
}
