using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Npgsql;

namespace SimpleDBViewer
{


    public partial class RecordEditForm : Form
    {
        private readonly string connectionString;
        private readonly TableInfo table;
        private readonly DataRow row;
        private readonly bool isEdit;
        private readonly List<FieldBinding> bindings = new List<FieldBinding>();

        public RecordEditForm()
        {
            connectionString = string.Empty;
            table = null;
            row = null;
            isEdit = false;
            InitializeComponent();
        }

        public RecordEditForm(string connectionString, TableInfo table, DataRow row)
        {
            this.connectionString = connectionString;
            this.table = table;
            this.row = row;
            isEdit = row != null;

            InitializeComponent();
            PrepareWindow();
            LoadFields();
        }
        private void PrepareWindow()
        {
            Text = (isEdit ? "Изменить: " : "Добавить: ") + table.DisplayName;
            lblTitle.Text = table.DisplayName;
        }

        private void BtnCancel_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
            Close();
        }

        private void LoadFields()
        {
            List<ColumnInfo> columns = GetColumns();

            foreach (ColumnInfo column in columns)
            {
                if (string.Equals(column.Name, table.PrimaryKey, StringComparison.OrdinalIgnoreCase))
                    continue;

                if (table.Name == "app_users" && column.Name == "password_hash")
                {
                    AddPasswordField();
                    continue;
                }

                AddColumnField(column);
            }
        }

        private List<ColumnInfo> GetColumns()
        {
            List<ColumnInfo> result = new List<ColumnInfo>();
            using (NpgsqlConnection connection = new NpgsqlConnection(connectionString))
            {
                connection.Open();
                const string sql = @"
                    select c.column_name,
                           c.data_type,
                           c.udt_name,
                           c.is_nullable,
                           c.is_identity,
                           exists (
                               select 1
                               from pg_index i
                               join pg_attribute a on a.attrelid = i.indrelid and a.attnum = any(i.indkey)
                               where i.indrelid = to_regclass(@qualified_table_name)
                                 and i.indisprimary
                                 and a.attname = c.column_name
                           ) as is_primary_key
                    from information_schema.columns c
                    where c.table_schema = @table_schema
                      and c.table_name = @table_name
                    order by c.ordinal_position;";

                using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
                {
                    command.Parameters.AddWithValue("@table_schema", MainForm.DbSchema);
                    command.Parameters.AddWithValue("@table_name", table.Name);
                    command.Parameters.AddWithValue("@qualified_table_name", MainForm.FullName(table.Name));
                    using (NpgsqlDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            result.Add(new ColumnInfo
                            {
                                Name = reader["column_name"].ToString(),
                                DataType = reader["data_type"].ToString(),
                                UdtName = reader["udt_name"].ToString(),
                                IsNullable = reader["is_nullable"].ToString() == "YES",
                                IsIdentity = reader["is_identity"].ToString() == "YES",
                                IsPrimaryKey = Convert.ToBoolean(reader["is_primary_key"])
                            });
                        }
                    }
                }
            }
            return result;
        }

        private void AddPasswordField()
        {
            Panel rowPanel = CreateFieldPanel("Пароль" + (isEdit ? " (оставьте пустым, чтобы не менять)" : ""));
            TextBox textBox = new TextBox
            {
                Location = new Point(160, 0),
                Width = 290,
                PasswordChar = '•',
                BorderStyle = BorderStyle.FixedSingle
            };
            rowPanel.Controls.Add(textBox);
            fieldsPanel.Controls.Add(rowPanel);

            bindings.Add(new FieldBinding
            {
                Column = new ColumnInfo { Name = "password_hash", DataType = "text", UdtName = "text", IsNullable = isEdit },
                Control = textBox,
                IsPasswordField = true
            });
        }

        private void AddColumnField(ColumnInfo column)
        {
            Panel rowPanel = CreateFieldPanel(GetLabelText(column));
            Control editor = CreateEditor(column);
            rowPanel.Controls.Add(editor);
            fieldsPanel.Controls.Add(rowPanel);

            bindings.Add(new FieldBinding
            {
                Column = column,
                Control = editor,
                IsPasswordField = false
            });
        }

        private Panel CreateFieldPanel(string labelText)
        {
            Panel panel = new Panel
            {
                Width = 455,
                Height = 58,
                Margin = new Padding(0, 0, 0, 8)
            };

            Label label = new Label
            {
                Text = labelText,
                Location = new Point(0, 0),
                Size = new Size(150, 24),
                ForeColor = Color.FromArgb(51, 65, 85),
                TextAlign = ContentAlignment.MiddleLeft
            };
            panel.Controls.Add(label);
            return panel;
        }

        private string GetLabelText(ColumnInfo column)
        {
            Dictionary<string, string> labels = new Dictionary<string, string>
            {
                { "school_name", "Название" },
                { "address", "Адрес" },
                { "phone", "Телефон" },
                { "school_id", "Автошкола" },
                { "last_name", "Фамилия" },
                { "first_name", "Имя" },
                { "middle_name", "Отчество" },
                { "license_number", "Номер удостоверения" },
                { "birth_date", "Дата рождения" },
                { "route_name", "Маршрут" },
                { "start_address", "Начальный адрес" },
                { "end_address", "Конечный адрес" },
                { "difficulty", "Сложность" },
                { "rank_name", "Звание" },
                { "position_name", "Должность" },
                { "driver_id", "Водитель" },
                { "route_id", "Маршрут" },
                { "inspector_id", "Инспектор" },
                { "exam_date", "Дата экзамена" },
                { "result", "Результат" },
                { "officer_id", "Сотрудник" },
                { "issue_date", "Дата выдачи" },
                { "category", "Категория" },
                { "login", "Логин" },
                { "role", "Роль" },
                { "is_active", "Активен" },
                { "instructor_id", "Инструктор" }
            };

            return labels.ContainsKey(column.Name) ? labels[column.Name] : column.Name;
        }

        private Control CreateEditor(ColumnInfo column)
        {
            object existingValue = isEdit && row.Table.Columns.Contains(column.Name) ? row[column.Name] : DBNull.Value;

            if (column.UdtName == "bool" || column.DataType == "boolean")
            {
                CheckBox checkBox = new CheckBox
                {
                    Location = new Point(160, 6),
                    Width = 290,
                    Checked = existingValue != DBNull.Value && Convert.ToBoolean(existingValue)
                };
                return checkBox;
            }

            if (column.DataType == "date" || column.UdtName == "date")
            {
                DateTimePicker picker = new DateTimePicker
                {
                    Location = new Point(160, 0),
                    Width = 290,
                    Format = DateTimePickerFormat.Short,
                    ShowCheckBox = column.IsNullable
                };

                if (existingValue != DBNull.Value)
                {
                    picker.Value = Convert.ToDateTime(existingValue);
                    picker.Checked = true;
                }
                else if (column.IsNullable)
                {
                    picker.Checked = false;
                }

                return picker;
            }

            List<ComboItem> fkItems = LoadForeignKeyItems(column);
            if (fkItems != null)
            {
                ComboBox comboBox = new ComboBox
                {
                    Location = new Point(160, 0),
                    Width = 290,
                    DropDownStyle = ComboBoxStyle.DropDownList
                };

                foreach (ComboItem item in fkItems)
                    comboBox.Items.Add(item);

                if (existingValue != DBNull.Value)
                {
                    int currentId = Convert.ToInt32(existingValue);
                    for (int i = 0; i < comboBox.Items.Count; i++)
                    {
                        ComboItem item = (ComboItem)comboBox.Items[i];
                        if (item.Id.HasValue && item.Id.Value == currentId)
                        {
                            comboBox.SelectedIndex = i;
                            break;
                        }
                    }
                }

                if (comboBox.SelectedIndex < 0 && comboBox.Items.Count > 0)
                    comboBox.SelectedIndex = 0;

                return comboBox;
            }

            List<string> values = GetFixedValues(column);
            if (values != null)
            {
                ComboBox comboBox = new ComboBox
                {
                    Location = new Point(160, 0),
                    Width = 290,
                    DropDownStyle = ComboBoxStyle.DropDownList
                };

                foreach (string value in values)
                    comboBox.Items.Add(value);

                if (existingValue != DBNull.Value)
                    comboBox.SelectedItem = existingValue.ToString();

                if (comboBox.SelectedIndex < 0 && comboBox.Items.Count > 0)
                    comboBox.SelectedIndex = 0;

                return comboBox;
            }

            TextBox textBox = new TextBox
            {
                Location = new Point(160, 0),
                Width = 290,
                BorderStyle = BorderStyle.FixedSingle,
                Text = existingValue == DBNull.Value ? "" : existingValue.ToString(),
                MaxLength = GetMaxLength(column)
            };
            return textBox;
        }

        private List<string> GetFixedValues(ColumnInfo column)
        {
            if (column.Name == "role")
                return new List<string> { "admin", "user" };
            if (column.Name == "result")
                return new List<string> { "сдан", "не сдан" };
            if (column.Name == "difficulty")
                return new List<string> { "легкий", "средний", "сложный" };
            if (column.Name == "category")
                return new List<string> { "A", "A1", "B", "B1", "C", "C1", "D", "D1", "BE", "CE", "DE" };
            if (column.Name == "rank_name")
                return new List<string> { "лейтенант", "старший лейтенант", "капитан", "майор", "подполковник" };
            if (column.Name == "position_name")
                return new List<string>
                {
                    "инспектор регистрационно-экзаменационного отдела",
                    "старший инспектор",
                    "специалист по выдаче удостоверений",
                    "начальник регистрационно-экзаменационного отдела"
                };
            return null;
        }

        private List<ComboItem> LoadForeignKeyItems(ColumnInfo column)
        {
            string sql = null;

            if (column.Name == "school_id")
                sql = "select school_id as id, school_name as title from akulova_vs.driving_schools order by school_name";
            else if (column.Name == "instructor_id")
                sql = "select instructor_id as id, concat_ws(' ', last_name, first_name, middle_name) as title from akulova_vs.instructors order by last_name, first_name";
            else if (column.Name == "driver_id")
                sql = "select driver_id as id, concat_ws(' ', last_name, first_name, middle_name) as title from akulova_vs.drivers order by last_name, first_name";
            else if (column.Name == "route_id")
                sql = "select route_id as id, route_name as title from akulova_vs.exam_routes order by route_name";
            else if (column.Name == "inspector_id")
                sql = "select inspector_id as id, concat_ws(' ', last_name, first_name, middle_name) as title from akulova_vs.traffic_inspectors order by last_name, first_name";
            else if (column.Name == "officer_id")
                sql = "select officer_id as id, concat_ws(' ', last_name, first_name, middle_name) as title from akulova_vs.traffic_officers order by last_name, first_name";

            if (sql == null)
                return null;

            List<ComboItem> items = new List<ComboItem>();
            if (column.IsNullable)
                items.Add(new ComboItem(null, "— не выбрано —"));

            using (NpgsqlConnection connection = new NpgsqlConnection(connectionString))
            {
                connection.Open();
                using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
                using (NpgsqlDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        items.Add(new ComboItem(Convert.ToInt32(reader["id"]), reader["title"].ToString()));
                    }
                }
            }

            return items;
        }

        private int GetMaxLength(ColumnInfo column)
        {
            Dictionary<string, int> limits = new Dictionary<string, int>
            {
                { "school_name", 150 },
                { "phone", 25 },
                { "last_name", 80 },
                { "first_name", 80 },
                { "middle_name", 80 },
                { "license_number", 30 },
                { "route_name", 120 },
                { "difficulty", 30 },
                { "rank_name", 80 },
                { "position_name", 120 },
                { "result", 20 },
                { "category", 10 },
                { "login", 80 }
            };

            return limits.ContainsKey(column.Name) ? limits[column.Name] : 300;
        }

        private void ValidateUserInput()
        {
            foreach (FieldBinding binding in bindings)
            {
                ColumnInfo column = binding.Column;

                if (binding.IsPasswordField)
                {
                    string password = GetPasswordValue();
                    if (!isEdit && string.IsNullOrWhiteSpace(password))
                        throw new ArgumentException("Введите пароль для нового пользователя.");
                    if (!string.IsNullOrWhiteSpace(password) && password.Length < 4)
                        throw new ArgumentException("Пароль должен содержать минимум 4 символа.");
                    continue;
                }

                if (!column.IsNullable && IsEmptyControl(binding.Control))
                    throw new ArgumentException("Заполните обязательное поле: " + GetLabelText(column) + ".");

                ValidateColumnFormat(binding);
            }
        }

        private bool IsEmptyControl(Control control)
        {
            if (control is TextBox)
                return string.IsNullOrWhiteSpace(((TextBox)control).Text);

            if (control is ComboBox)
            {
                ComboBox comboBox = (ComboBox)control;
                if (comboBox.SelectedItem == null)
                    return true;

                ComboItem item = comboBox.SelectedItem as ComboItem;
                return item != null && !item.Id.HasValue;
            }

            if (control is DateTimePicker)
            {
                DateTimePicker picker = (DateTimePicker)control;
                return picker.ShowCheckBox && !picker.Checked;
            }

            return false;
        }

        private void ValidateColumnFormat(FieldBinding binding)
        {
            ColumnInfo column = binding.Column;
            string value = GetControlText(binding.Control).Trim();

            if (string.IsNullOrWhiteSpace(value))
                return;

            if (column.Name == "last_name" || column.Name == "first_name" || column.Name == "middle_name")
            {
                if (!Regex.IsMatch(value, @"^[А-Яа-яЁёA-Za-z\s\-]+$"))
                    throw new ArgumentException(GetLabelText(column) + " может содержать только буквы, пробелы и дефис.");
            }

            if (column.Name == "phone")
            {
                if (!Regex.IsMatch(value, @"^\+?[0-9\s\-\(\)]{7,25}$"))
                    throw new ArgumentException("Телефон можно вводить только цифрами, пробелами, скобками, дефисом и знаком +.");
            }

            if (column.Name == "login")
            {
                if (!Regex.IsMatch(value, @"^[A-Za-z0-9_.\-]{3,80}$"))
                    throw new ArgumentException("Логин должен содержать 3–80 символов: латинские буквы, цифры, точка, дефис или подчёркивание.");
            }

            if (column.Name == "license_number")
            {
                if (!Regex.IsMatch(value, @"^[A-Za-zА-Яа-яЁё0-9\-\/]{3,30}$"))
                    throw new ArgumentException("Номер удостоверения может содержать буквы, цифры, дефис и символ /.");
            }

            if (column.Name == "birth_date" && binding.Control is DateTimePicker)
            {
                DateTime date = ((DateTimePicker)binding.Control).Value.Date;
                if (date > DateTime.Today.AddYears(-16))
                    throw new ArgumentException("Водителю должно быть не меньше 16 лет.");
            }
        }

        private string GetControlText(Control control)
        {
            if (control is TextBox)
                return ((TextBox)control).Text;
            if (control is ComboBox && ((ComboBox)control).SelectedItem != null)
                return ((ComboBox)control).SelectedItem.ToString();
            if (control is DateTimePicker)
                return ((DateTimePicker)control).Value.ToShortDateString();
            return "";
        }

        private void BtnSave_Click(object sender, EventArgs e)
        {
            try
            {
                ValidateUserInput();

                if (table.Name == "app_users")
                    SaveAppUser();
                else
                    SaveGenericRecord();

                DialogResult = DialogResult.OK;
                Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(AppError.ToUserMessage(ex, "Не удалось сохранить запись."),
                    "Ошибка сохранения", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }


        private void EnsureIdentitySequenceIfNeeded(NpgsqlConnection connection)
        {
            if (string.IsNullOrWhiteSpace(table.PrimaryKey))
                return;

            string tableForPg = MainForm.DbSchema + "." + table.Name;
            string sequenceName = null;

            using (NpgsqlCommand sequenceCommand = new NpgsqlCommand("select pg_get_serial_sequence(cast(@table_name as text), cast(@column_name as text));", connection))
            {
                sequenceCommand.Parameters.AddWithValue("@table_name", tableForPg);
                sequenceCommand.Parameters.AddWithValue("@column_name", table.PrimaryKey);
                object result = sequenceCommand.ExecuteScalar();
                if (result != null && result != DBNull.Value)
                    sequenceName = result.ToString();
            }

            if (string.IsNullOrWhiteSpace(sequenceName))
                return;

            string sql = "select setval(cast(cast(@sequence_name as text) as regclass), " +
                         "coalesce((select max(" + MainForm.Quote(table.PrimaryKey) + ") from " + MainForm.FullName(table.Name) + "), 0) + 1, false);";

            using (NpgsqlCommand resetCommand = new NpgsqlCommand(sql, connection))
            {
                resetCommand.Parameters.AddWithValue("@sequence_name", sequenceName);
                resetCommand.ExecuteNonQuery();
            }
        }

        private void SaveGenericRecord()
        {
            List<string> columns = new List<string>();
            List<string> values = new List<string>();
            List<string> sets = new List<string>();

            using (NpgsqlConnection connection = new NpgsqlConnection(connectionString))
            {
                connection.Open();
                DbAppContext.Apply(connection);
                if (!isEdit)
                    EnsureIdentitySequenceIfNeeded(connection);

                using (NpgsqlCommand command = new NpgsqlCommand())
                {
                    command.Connection = connection;

                    foreach (FieldBinding binding in bindings)
                    {
                        object value = GetValue(binding);
                        string parameterName = "@" + binding.Column.Name;

                        columns.Add(MainForm.Quote(binding.Column.Name));
                        values.Add(parameterName);
                        sets.Add(MainForm.Quote(binding.Column.Name) + " = " + parameterName);
                        command.Parameters.AddWithValue(parameterName, value ?? DBNull.Value);
                    }

                    if (isEdit)
                    {
                        string sql = "update " + MainForm.FullName(table.Name) + " set " + string.Join(", ", sets.ToArray()) +
                                     " where " + MainForm.Quote(table.PrimaryKey) + " = @pk";
                        command.CommandText = sql;
                        command.Parameters.AddWithValue("@pk", row[table.PrimaryKey]);
                    }
                    else
                    {
                        string sql = "insert into " + MainForm.FullName(table.Name) + " (" + string.Join(", ", columns.ToArray()) +
                                     ") values (" + string.Join(", ", values.ToArray()) + ")";
                        command.CommandText = sql;
                    }

                    command.ExecuteNonQuery();
                    DbAppContext.MarkLatestAuditLogin(connection, table.Name, isEdit ? row[table.PrimaryKey] : null);
                }
            }
        }

        private void SaveAppUser()
        {
            string login = GetTextValue("login");
            string password = GetPasswordValue();
            string role = GetComboValue("role");
            object isActive = GetColumnValue("is_active");
            object instructorId = GetColumnValue("instructor_id");
            object driverId = GetColumnValue("driver_id");

            if (string.IsNullOrWhiteSpace(login))
                throw new Exception("Логин не может быть пустым.");

            if (!isEdit && string.IsNullOrWhiteSpace(password))
                throw new Exception("Для нового пользователя нужно указать пароль.");

            if (role == "admin")
                driverId = DBNull.Value;

            if (role == "user" && (driverId == null || driverId == DBNull.Value))
                throw new Exception("Для обычного пользователя нужно выбрать водителя. Так пользователь будет видеть только свои данные.");

            using (NpgsqlConnection connection = new NpgsqlConnection(connectionString))
            {
                connection.Open();
                DbAppContext.Apply(connection);
                if (!isEdit)
                    EnsureIdentitySequenceIfNeeded(connection);

                using (NpgsqlCommand command = new NpgsqlCommand())
                {
                    command.Connection = connection;
                    command.Parameters.AddWithValue("@login", login);
                    command.Parameters.AddWithValue("@role", role);
                    command.Parameters.AddWithValue("@is_active", isActive ?? DBNull.Value);
                    command.Parameters.AddWithValue("@instructor_id", instructorId ?? DBNull.Value);
                    command.Parameters.AddWithValue("@driver_id", driverId ?? DBNull.Value);

                    if (isEdit)
                    {
                        if (string.IsNullOrWhiteSpace(password))
                        {
                            command.CommandText = @"
                                update akulova_vs.app_users
                                set login = @login,
                                    role = cast(@role as akulova_vs.app_role),
                                    is_active = @is_active,
                                    instructor_id = @instructor_id,
                                    driver_id = @driver_id
                                where user_id = @user_id;";
                        }
                        else
                        {
                            command.CommandText = @"
                                update akulova_vs.app_users
                                set login = @login,
                                    password_hash = akulova_vs.fn_hash_password(cast(@password as text)),
                                    role = cast(@role as akulova_vs.app_role),
                                    is_active = @is_active,
                                    instructor_id = @instructor_id,
                                    driver_id = @driver_id
                                where user_id = @user_id;";
                            command.Parameters.AddWithValue("@password", password);
                        }
                        command.Parameters.AddWithValue("@user_id", row[table.PrimaryKey]);
                    }
                    else
                    {
                        command.CommandText = @"
                            insert into akulova_vs.app_users(login, password_hash, role, is_active, instructor_id, driver_id)
                            values (@login, akulova_vs.fn_hash_password(cast(@password as text)), cast(@role as akulova_vs.app_role), @is_active, @instructor_id, @driver_id);";
                        command.Parameters.AddWithValue("@password", password);
                    }

                    command.ExecuteNonQuery();
                    DbAppContext.MarkLatestAuditLogin(connection, table.Name, isEdit ? row[table.PrimaryKey] : null);
                }
            }
        }

        private string GetTextValue(string columnName)
        {
            foreach (FieldBinding binding in bindings)
            {
                if (binding.Column.Name == columnName && binding.Control is TextBox)
                    return ((TextBox)binding.Control).Text.Trim();
            }
            return "";
        }

        private string GetPasswordValue()
        {
            foreach (FieldBinding binding in bindings)
            {
                if (binding.IsPasswordField && binding.Control is TextBox)
                    return ((TextBox)binding.Control).Text;
            }
            return "";
        }

        private string GetComboValue(string columnName)
        {
            foreach (FieldBinding binding in bindings)
            {
                if (binding.Column.Name == columnName && binding.Control is ComboBox)
                {
                    ComboBox comboBox = (ComboBox)binding.Control;
                    return comboBox.SelectedItem == null ? "" : comboBox.SelectedItem.ToString();
                }
            }
            return "";
        }

        private object GetColumnValue(string columnName)
        {
            foreach (FieldBinding binding in bindings)
            {
                if (binding.Column.Name == columnName)
                    return GetValue(binding);
            }
            return DBNull.Value;
        }

        private object GetValue(FieldBinding binding)
        {
            ColumnInfo column = binding.Column;
            Control control = binding.Control;

            if (control is TextBox)
            {
                string text = ((TextBox)control).Text.Trim();
                if (string.IsNullOrEmpty(text) && column.IsNullable)
                    return DBNull.Value;

                if (column.UdtName == "int4" || column.UdtName == "int2" || column.UdtName == "int8" || column.DataType.Contains("integer"))
                    return int.Parse(text);

                return text;
            }

            if (control is CheckBox)
                return ((CheckBox)control).Checked;

            if (control is DateTimePicker)
            {
                DateTimePicker picker = (DateTimePicker)control;
                if (picker.ShowCheckBox && !picker.Checked)
                    return DBNull.Value;
                return picker.Value.Date;
            }

            if (control is ComboBox)
            {
                ComboBox comboBox = (ComboBox)control;
                if (comboBox.SelectedItem is ComboItem)
                {
                    ComboItem item = (ComboItem)comboBox.SelectedItem;
                    return item.Id.HasValue ? (object)item.Id.Value : DBNull.Value;
                }

                if (comboBox.SelectedItem == null)
                    return column.IsNullable ? (object)DBNull.Value : "";

                return comboBox.SelectedItem.ToString();
            }

            return DBNull.Value;
        }

        private void fieldsPanel_Paint(object sender, PaintEventArgs e)
        {

        }
    }
}
