using System.Data;
using TravelAgencyWinForms.Data;
using TravelAgencyWinForms.Models;
using TravelAgencyWinForms.Utils;

namespace TravelAgencyWinForms.Forms;

public sealed class EntityEditDialog : Form
{
    private readonly EntityConfig _config;
    private readonly bool _isEdit;
    private readonly Dictionary<string, Control> _controls = new();
    private readonly Dictionary<string, FieldConfig> _fields = new();

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

    public EntityEditDialog(EntityConfig config, DataRow? row = null)
    {
        _config = config;
        _isEdit = row != null;
        Text = (_isEdit ? "Изменение" : "Добавление") + " — " + config.Title;
        StartPosition = FormStartPosition.CenterParent;
        Width = 560;
        Height = Math.Min(760, 180 + config.EditableFields.Count() * 46);

        AppTheme.ApplyForm(this, true);
        BuildLayout(row);
    }

    private void BuildLayout(DataRow? row)
    {
        var main = new TableLayoutPanel
        {
            Dock = DockStyle.Fill,
            Padding = new Padding(18),
            ColumnCount = 2,
            RowCount = _config.EditableFields.Count() + 2,
            AutoScroll = true
        };
        main.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 180));
        main.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));

        var currentRow = 0;
        foreach (var field in _config.EditableFields)
        {
            _fields[field.Name] = field;
            main.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));

            var label = new Label
            {
                Text = field.Label + (field.Required ? " *" : string.Empty),
                AutoSize = true,
                Anchor = AnchorStyles.Left,
                ForeColor = AppTheme.Text
            };

            var control = CreateControl(field, row);
            control.Anchor = AnchorStyles.Left | AnchorStyles.Right;

            if (_isEdit && (field.ReadOnlyOnEdit || field.IsKey))
                control.Enabled = false;

            main.Controls.Add(label, 0, currentRow);
            main.Controls.Add(control, 1, currentRow);
            _controls[field.Name] = control;
            currentRow++;
        }

        var buttons = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, Dock = DockStyle.Fill };
        var saveButton = new Button { Text = "Сохранить", Width = 120, Height = 36 };
        var cancelButton = new Button { Text = "Отмена", Width = 120, Height = 36 };
        AppTheme.StylePrimaryButton(saveButton);
        AppTheme.StyleSecondaryButton(cancelButton);
        saveButton.Click += (_, _) => Save();
        cancelButton.Click += (_, _) => DialogResult = DialogResult.Cancel;
        buttons.Controls.Add(saveButton);
        buttons.Controls.Add(cancelButton);

        main.RowStyles.Add(new RowStyle(SizeType.Absolute, 46));
        main.Controls.Add(buttons, 0, currentRow);
        main.SetColumnSpan(buttons, 2);
        Controls.Add(main);
    }

    private Control CreateControl(FieldConfig field, DataRow? row)
    {
        object? value = null;
        if (row != null && row.Table.Columns.Contains(field.Name) && row[field.Name] != DBNull.Value)
            value = row[field.Name];

        switch (field.Kind)
        {
            case FieldKind.Date:
                var datePicker = new DateTimePicker
                {
                    Format = DateTimePickerFormat.Short,
                    Width = 240,
                    ShowCheckBox = !field.Required
                };
                if (value is DateTime dt)
                {
                    datePicker.Value = dt;
                    datePicker.Checked = true;
                }
                else
                {
                    datePicker.Value = DateTime.Today;
                    datePicker.Checked = field.Required;
                }
                return datePicker;

            case FieldKind.Boolean:
                var checkBox = new CheckBox { Text = "Да", AutoSize = true };
                checkBox.Checked = value == null || Convert.ToBoolean(value);
                return checkBox;

            case FieldKind.Lookup:
                var comboBox = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 320 };
                AppTheme.StyleComboBox(comboBox);
                LoadLookup(comboBox, field, value);
                return comboBox;

            case FieldKind.Enum:
                var enumBox = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 320 };
                AppTheme.StyleComboBox(enumBox);
                foreach (var option in field.Options)
                    enumBox.Items.Add(option);
                if (value != null && enumBox.Items.Contains(Convert.ToString(value)))
                    enumBox.SelectedItem = Convert.ToString(value);
                else if (enumBox.Items.Count > 0)
                    enumBox.SelectedIndex = 0;
                return enumBox;

            case FieldKind.Password:
                var passwordBox = new TextBox { Width = 320, UseSystemPasswordChar = true, Text = string.Empty };
                AppTheme.StyleTextBox(passwordBox);
                return passwordBox;

            default:
                var textBox = new TextBox { Width = 320, Text = value?.ToString() ?? string.Empty };
                AppTheme.StyleTextBox(textBox);
                return textBox;
        }
    }

    private static void LoadLookup(ComboBox comboBox, FieldConfig field, object? selectedValue)
    {
        if (string.IsNullOrWhiteSpace(field.LookupSql))
            return;

        var source = Db.Query(field.LookupSql);
        var table = new DataTable();
        table.Columns.Add(field.LookupValueColumn, typeof(object));
        table.Columns.Add(field.LookupDisplayColumn, typeof(string));

        if (!field.Required)
            table.Rows.Add(DBNull.Value, "—");

        foreach (DataRow sourceRow in source.Rows)
            table.Rows.Add(sourceRow[field.LookupValueColumn] == DBNull.Value ? DBNull.Value : sourceRow[field.LookupValueColumn], Convert.ToString(sourceRow[field.LookupDisplayColumn]) ?? string.Empty);

        comboBox.DataSource = table;
        comboBox.ValueMember = field.LookupValueColumn;
        comboBox.DisplayMember = field.LookupDisplayColumn;

        if (selectedValue != null && selectedValue != DBNull.Value)
            comboBox.SelectedValue = selectedValue;
        else if (comboBox.Items.Count > 0)
            comboBox.SelectedIndex = 0;
    }

    private void Save()
    {
        Values.Clear();
        PasswordWasEntered = false;

        foreach (var (name, control) in _controls)
        {
            var field = _fields[name];
            object? value;
            try
            {
                value = ReadControlValue(field, control);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ErrorHelper.ToUserMessage(ex), "Проверка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (field.Required && (value == null || string.IsNullOrWhiteSpace(Convert.ToString(value))))
            {
                MessageBox.Show($"Поле '{field.Label}' должно быть заполнено.", "Проверка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (field.Kind == FieldKind.Password)
            {
                PasswordWasEntered = !string.IsNullOrWhiteSpace(Convert.ToString(value));
                if (!_isEdit && !PasswordWasEntered)
                {
                    MessageBox.Show("Введите пароль.", "Проверка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    return;
                }
            }

            Values[name] = value;
        }

        if (!ValidateDomainRules())
            return;

        DialogResult = DialogResult.OK;
        Close();
    }

    private object? ReadControlValue(FieldConfig field, Control control)
    {
        return field.Kind switch
        {
            FieldKind.Date => ((DateTimePicker)control).Checked ? ((DateTimePicker)control).Value.Date : null,
            FieldKind.Boolean => ((CheckBox)control).Checked,
            FieldKind.Lookup => ((ComboBox)control).SelectedValue == DBNull.Value ? null : ((ComboBox)control).SelectedValue,
            FieldKind.Enum => ((ComboBox)control).SelectedItem?.ToString(),
            FieldKind.Integer => ParseInteger(field, control.Text),
            FieldKind.Decimal => ParseDecimal(field, control.Text),
            _ => string.IsNullOrWhiteSpace(control.Text.Trim()) ? null : control.Text.Trim()
        };
    }

    private static int? ParseInteger(FieldConfig field, string text)
    {
        var value = text.Trim();
        if (string.IsNullOrWhiteSpace(value))
            return null;
        if (!int.TryParse(value, out var number))
            throw new InvalidOperationException($"Поле '{field.Label}' должно быть целым числом.");
        return number;
    }

    private static decimal? ParseDecimal(FieldConfig field, string text)
    {
        var value = text.Trim().Replace(',', '.');
        if (string.IsNullOrWhiteSpace(value))
            return null;
        if (!decimal.TryParse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var number))
            throw new InvalidOperationException($"Поле '{field.Label}' должно быть числом.");
        return number;
    }

    private bool ValidateDomainRules()
    {
        try
        {
            if (_config.TableName == "positions" && Values.TryGetValue("salary", out var salaryObj) && salaryObj != null && Convert.ToDecimal(salaryObj) < 0)
                return Warn("Зарплата не может быть отрицательной.");

            if (_config.TableName == "tours" && Values.TryGetValue("price", out var priceObj) && priceObj != null && Convert.ToDecimal(priceObj) < 0)
                return Warn("Стоимость тура не может быть отрицательной.");

            if (_config.TableName == "employees" && Values.TryGetValue("birth_date", out var birthObj) && birthObj is DateTime birthDate)
            {
                if (birthDate > DateTime.Today)
                    return Warn("Дата рождения не может быть в будущем.");
                if (birthDate > DateTime.Today.AddYears(-18))
                    return Warn("Сотрудник должен быть старше 18 лет.");
            }

            if (_config.TableName == "tour_list" && Values.TryGetValue("start_date", out var startObj) && Values.TryGetValue("end_date", out var endObj) && startObj is DateTime start && endObj is DateTime end && end < start)
                return Warn("Дата окончания не может быть раньше даты начала.");

            if (_config.TableName == "bookings" && Values.TryGetValue("booking_date", out var bookingObj) && bookingObj is DateTime bookingDate && bookingDate > DateTime.Today)
                return Warn("Дата оформления не может быть в будущем.");
        }
        catch (Exception ex)
        {
            return Warn(ErrorHelper.ToUserMessage(ex));
        }

        return true;
    }

    private static bool Warn(string message)
    {
        MessageBox.Show(message, "Проверка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return false;
    }
}
