using System;
using System.Configuration;
using System.Windows.Forms;
using Npgsql;
using NpgsqlTypes;

namespace SimpleDBViewer
{
    public partial class LoginForm : Form
    {
        public LoginForm()
        {
            InitializeComponent();
        }

        private void TxtPassword_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                e.SuppressKeyPress = true;
                BtnLogin_Click(sender, EventArgs.Empty);
            }
        }

        private void BtnLogin_Click(object sender, EventArgs e)
        {
            string login = txtLogin.Text.Trim();
            string password = txtPassword.Text;

            lblError.Text = "";

            if (string.IsNullOrWhiteSpace(login))
            {
                lblError.Text = "Введите логин.";
                txtLogin.Focus();
                return;
            }

            if (string.IsNullOrWhiteSpace(password))
            {
                lblError.Text = "Введите пароль.";
                txtPassword.Focus();
                return;
            }

            try
            {
                btnLogin.Enabled = false;
                btnLogin.Text = "Проверка...";
                Application.DoEvents();

                using (NpgsqlConnection connection = new NpgsqlConnection(DbSettings.ConnectionString))
                {
                    connection.Open();

                    const string sql = @"
                        select user_id, login, role::text as role, instructor_id, driver_id
                        from akulova_vs.fn_authenticate_user(cast(@login as text), cast(@password as text))";

                    using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
                    {
                        command.Parameters.AddWithValue("@login", login);
                        command.Parameters.AddWithValue("@password", password);

                        using (NpgsqlDataReader reader = command.ExecuteReader())
                        {
                            if (!reader.Read())
                            {
                                lblError.Text = "Неверный логин или пароль.";
                                return;
                            }

                            UserSession.CurrentUser = new UserInfo
                            {
                                UserId = Convert.ToInt32(reader["user_id"]),
                                Login = reader["login"].ToString(),
                                Role = reader["role"].ToString(),
                                InstructorId = reader["instructor_id"] == DBNull.Value
                                    ? (int?)null
                                    : Convert.ToInt32(reader["instructor_id"]),
                                DriverId = reader["driver_id"] == DBNull.Value
                                    ? (int?)null
                                    : Convert.ToInt32(reader["driver_id"])
                            };
                        }
                    }
                }

                using (MainForm mainForm = new MainForm())
                {
                    Hide();
                    mainForm.ShowDialog();
                    txtPassword.Clear();
                    Show();
                    txtPassword.Focus();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(AppError.ToUserMessage(ex, "Не удалось выполнить вход."),
                    "Ошибка подключения или авторизации", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                btnLogin.Enabled = true;
                btnLogin.Text = "Войти";
            }
        }

        private void BtnExit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void lblInfo_Click(object sender, EventArgs e)
        {

        }
    }

    public static class DbSettings
    {
        public static string ConnectionString
        {
            get
            {
                ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings["AutoschoolDb"];
                return settings.ConnectionString;
            }
        }
    }

    public class UserInfo
    {
        public int UserId { get; set; }
        public string Login { get; set; }
        public string Role { get; set; }
        public int? InstructorId { get; set; }
        public int? DriverId { get; set; }
    }

    public static class DbAppContext
    {
        public static void Apply(NpgsqlConnection connection)
        {
            if (connection == null || !UserSession.IsLoggedIn)
                return;

            using (NpgsqlCommand command = new NpgsqlCommand(@"
                select set_config('app.user_login', cast(@login as text), false);", connection))
            {
                command.Parameters.AddWithValue("@login", UserSession.CurrentUser.Login ?? "system");
                command.ExecuteNonQuery();
            }
        }

        public static void MarkLatestAuditLogin(NpgsqlConnection connection, string tableName)
        {
            MarkLatestAuditLogin(connection, tableName, null);
        }

        public static void MarkLatestAuditLogin(NpgsqlConnection connection, string tableName, object recordId)
        {
            if (connection == null || !UserSession.IsLoggedIn || string.IsNullOrWhiteSpace(tableName))
                return;

            int parsedRecordId;
            object recordIdValue = DBNull.Value;
            if (recordId != null && recordId != DBNull.Value && int.TryParse(recordId.ToString(), out parsedRecordId))
                recordIdValue = parsedRecordId;

            using (NpgsqlCommand command = new NpgsqlCommand(@"
                update akulova_vs.audit_log
                set changed_by = cast(@login as varchar)
                where log_id = (
                    select log_id
                    from akulova_vs.audit_log
                    where table_name = cast(@table_name as text)
                      and (cast(@record_id as integer) is null or record_id = cast(@record_id as integer))
                      and changed_at >= now() - interval '5 minutes'
                    order by log_id desc
                    limit 1
                );", connection))
            {
                command.Parameters.AddWithValue("@login", UserSession.CurrentUser.Login ?? "system");
                command.Parameters.AddWithValue("@table_name", tableName);
                NpgsqlParameter recordParameter = command.Parameters.Add("@record_id", NpgsqlDbType.Integer);
                recordParameter.Value = recordIdValue;
                command.ExecuteNonQuery();
            }
        }
    }

    public static class UserSession
    {
        public static UserInfo CurrentUser { get; set; }

        public static bool IsLoggedIn
        {
            get { return CurrentUser != null; }
        }

        public static bool IsAdmin
        {
            get { return IsLoggedIn && CurrentUser.Role == "admin"; }
        }

        public static void Logout()
        {
            CurrentUser = null;
        }
    }

    public static class AppError
    {
        public static string ToUserMessage(Exception ex, string actionText)
        {
            if (ex is ArgumentException)
                return ex.Message;

            PostgresException pg = ex as PostgresException;
            if (pg != null)
            {
                switch (pg.SqlState)
                {
                    case "23505":
                        return actionText + "\n\nТакая запись уже существует. Проверьте уникальные поля: логин, номер удостоверения или номер водительских прав.";
                    case "23503":
                        return actionText + "\n\nЗапись нельзя сохранить или удалить, потому что она связана с другими данными. Сначала проверьте связанные таблицы.";
                    case "23502":
                        return actionText + "\n\nНе заполнено обязательное поле. Проверьте форму и заполните все обязательные данные.";
                    case "23514":
                        return actionText + "\n\nЗначение не проходит проверку базы данных. Проверьте выбранный результат экзамена, сложность маршрута, категорию прав или дату рождения.";
                    case "22P02":
                    case "22007":
                    case "22008":
                        return actionText + "\n\nНекорректный формат данных. Проверьте числа и даты.";
                    case "42P01":
                        return actionText + "\n\nТаблица или представление не найдено. Проверьте, что объекты созданы в схеме akulova_vs и в строке подключения указан Search Path=akulova_vs,public.";
                    case "42883":
                        return actionText + "\n\nФункция базы данных не найдена. Проверьте, что функции fn_authenticate_user и fn_hash_password созданы в схеме akulova_vs.";
                    case "42703":
                        return actionText + "\n\nВ базе данных не найден нужный столбец. Проверьте, что структура таблиц соответствует ТЗ.";
                    case "42501":
                        return actionText + "\n\nНедостаточно прав для выполнения операции в базе данных.";
                    case "3D000":
                        return actionText + "\n\nБаза данных не найдена. Проверьте имя базы в App.config.";
                    case "28P01":
                        return actionText + "\n\nНеверный логин или пароль подключения к PostgreSQL. Проверьте Username и Password в App.config.";
                    default:
                        return actionText + "\n\nБаза данных вернула ошибку. Код: " + pg.SqlState + ".";
                }
            }

            if (ex is NpgsqlException)
            {
                return actionText + "\n\nНе удалось подключиться к PostgreSQL. Проверьте сервер, порт, базу данных и строку подключения в App.config.";
            }

            if (ex is FormatException || ex is InvalidCastException)
            {
                return actionText + "\n\nНекорректный формат данных. Проверьте, что числовые поля заполнены числами, а даты выбраны через календарь.";
            }

            return actionText + "\n\nПроизошла ошибка при выполнении операции. Проверьте введённые данные и попробуйте ещё раз.";
        }
    }
}
