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

namespace ComputerSalesApp.Data;

internal sealed class TableRepository
{
    public Task<DataTable> LoadTableAsync(TableDefinition table)
    {
        string sql = table.SelectSql;
        if (!string.IsNullOrWhiteSpace(table.OrderBy))
        {
            sql += $" order by {table.OrderBy}";
        }

        return Db.QueryAsync(sql);
    }

    public async Task<List<ComboItem>> LoadComboItemsAsync(FieldDefinition field)
    {
        if (string.IsNullOrWhiteSpace(field.ReferenceSql))
        {
            return new List<ComboItem>();
        }

        DataTable table = await Db.QueryAsync(field.ReferenceSql);
        var items = new List<ComboItem>();

        if (field.Nullable)
        {
            items.Add(new ComboItem { Id = null, Text = "(не выбрано)" });
        }

        foreach (DataRow row in table.Rows)
        {
            items.Add(new ComboItem
            {
                Id = row["id"] == DBNull.Value ? null : Convert.ToInt32(row["id"]),
                Text = Convert.ToString(row["text"]) ?? string.Empty
            });
        }

        return items;
    }

    public async Task<List<ComboItem>> LoadProductsForOrderAsync(int orderId, int? selectedProductId)
    {
        DataTable table = await Db.QueryAsync($@"
select
    p.product_id as id,
    p.product_name || ' — ' || p.manufacturer || ' (в наличии: ' || i.quantity || ' шт.)' as text
from {ConnectionSettings.Schema}.orders o
join {ConnectionSettings.Schema}.inventory i on i.warehouse_id = o.warehouse_id
join {ConnectionSettings.Schema}.products p on p.product_id = i.product_id
where o.order_id = @order_id
  and (i.quantity > 0 or p.product_id = @selected_product_id)
order by p.product_name, p.manufacturer;", p =>
        {
            p.AddWithValue("order_id", orderId);
            p.AddWithValue("selected_product_id", selectedProductId ?? 0);
        });

        var items = new List<ComboItem>();
        foreach (DataRow row in table.Rows)
        {
            items.Add(new ComboItem
            {
                Id = row["id"] == DBNull.Value ? null : Convert.ToInt32(row["id"]),
                Text = Convert.ToString(row["text"]) ?? string.Empty
            });
        }

        return items;
    }

    public async Task<decimal> LoadProductPriceAsync(int productId)
    {
        DataTable table = await Db.QueryAsync($@"
select price
from {ConnectionSettings.Schema}.products
where product_id = @product_id;", p => p.AddWithValue("product_id", productId));

        if (table.Rows.Count == 0 || table.Rows[0]["price"] == DBNull.Value)
        {
            return 0;
        }

        return Convert.ToDecimal(table.Rows[0]["price"]);
    }

    public async Task InsertAsync(TableDefinition table, Dictionary<string, object?> values)
    {
        if (table.IsUsersTable)
        {
            await InsertUserAsync(values);
            return;
        }

        if (table.TableName == "order_items")
        {
            await ValidateOrderItemAvailabilityAsync(values, null);
        }

        var fields = table.Fields.Where(f => !f.IsPassword).ToList();
        string columns = string.Join(", ", fields.Select(f => f.ColumnName));
        string parameters = string.Join(", ", fields.Select(f => "@" + f.ColumnName));

        string sql = $"insert into {ConnectionSettings.Schema}.{table.TableName} ({columns}) values ({parameters});";

        int affected = await Db.ExecuteAsync(sql, p => AddParameters(p, fields, values));
        if (table.TableName == "order_items" && affected == 0)
        {
            throw new UserFriendlyException("Позиция заказа не была добавлена. Проверьте наличие товара на выбранном складе.");
        }
    }

    public async Task UpdateAsync(TableDefinition table, int id, Dictionary<string, object?> values)
    {
        if (table.IsUsersTable)
        {
            await UpdateUserAsync(id, values);
            return;
        }

        if (table.TableName == "order_items")
        {
            await ValidateOrderItemAvailabilityAsync(values, id);
        }

        var fields = table.Fields.Where(f => !f.IsPassword).ToList();
        string setClause = string.Join(", ", fields.Select(f => $"{f.ColumnName} = @{f.ColumnName}"));
        string sql = $"update {ConnectionSettings.Schema}.{table.TableName} set {setClause} where {table.IdColumn} = @id;";

        int affected = await Db.ExecuteAsync(sql, p =>
        {
            AddParameters(p, fields, values);
            p.AddWithValue("id", id);
        });
        if (table.TableName == "order_items" && affected == 0)
        {
            throw new UserFriendlyException("Позиция заказа не была изменена. Проверьте наличие товара на выбранном складе.");
        }
    }

    public Task DeleteAsync(TableDefinition table, int id)
    {
        string sql = $"delete from {ConnectionSettings.Schema}.{table.TableName} where {table.IdColumn} = @id;";
        return Db.ExecuteAsync(sql, p => p.AddWithValue("id", id));
    }

    public Task<DataTable> LoadCustomersForReportAsync(UserSession session)
    {
        if (session.IsAdmin)
        {
            string adminSql = $@"
select customer_id as id, concat_ws(' ', last_name, first_name, middle_name) as text
from {ConnectionSettings.Schema}.customers
order by text;";
            return Db.QueryAsync(adminSql);
        }

        string userSql = $@"
select customer_id as id, concat_ws(' ', last_name, first_name, middle_name) as text
from {ConnectionSettings.Schema}.customers
where customer_id = @customer_id
order by text;";
        return Db.QueryAsync(userSql, p => p.AddWithValue("customer_id", session.CustomerId ?? -1));
    }

    public async Task<DateTime?> LoadLatestOrderDateAsync(int customerId)
    {
        string sql = $@"
select max(order_date) as order_date
from {ConnectionSettings.Schema}.orders
where customer_id = @customer_id;";

        DataTable table = await Db.QueryAsync(sql, p => p.AddWithValue("customer_id", customerId));
        if (table.Rows.Count == 0 || table.Rows[0]["order_date"] == DBNull.Value)
        {
            return null;
        }

        return Convert.ToDateTime(table.Rows[0]["order_date"]);
    }

    public Task<DataTable> LoadCustomerOrderItemsAsync(UserSession session, int requestedCustomerId, DateTime orderDate, bool descending)
    {
        int customerId = session.IsAdmin ? requestedCustomerId : session.CustomerId ?? -1;
        string direction = descending ? "desc" : "asc";
        string sql = $@"
select
    o.order_id,
    o.order_date,
    concat_ws(' ', c.last_name, c.first_name, c.middle_name) as customer_full_name,
    concat_ws(' ', e.last_name, e.first_name, e.middle_name) as employee_full_name,
    p.product_name,
    p.manufacturer,
    oi.quantity,
    oi.unit_price,
    (oi.quantity * oi.unit_price)::numeric(12,2) as item_total,
    o.status
from {ConnectionSettings.Schema}.orders o
join {ConnectionSettings.Schema}.customers c on c.customer_id = o.customer_id
join {ConnectionSettings.Schema}.employees e on e.employee_id = o.employee_id
join {ConnectionSettings.Schema}.order_items oi on oi.order_id = o.order_id
join {ConnectionSettings.Schema}.products p on p.product_id = oi.product_id
where o.customer_id = @customer_id
  and o.order_date = cast(@order_date as date)
order by oi.quantity {direction};";

        return Db.QueryAsync(sql, p =>
        {
            p.AddWithValue("customer_id", customerId);
            p.AddWithValue("order_date", orderDate.Date);
        });
    }


    private static async Task ValidateOrderItemAvailabilityAsync(Dictionary<string, object?> values, int? orderItemId)
    {
        int orderId = Convert.ToInt32(values["order_id"] ?? 0);
        int productId = Convert.ToInt32(values["product_id"] ?? 0);
        int quantity = Convert.ToInt32(values["quantity"] ?? 0);

        if (orderId <= 0 || productId <= 0 || quantity <= 0)
        {
            throw new UserFriendlyException("Выберите заказ, товар и укажите количество больше нуля.");
        }

        int warehouseId = await GetOrderWarehouseIdAsync(orderId);
        int available = await GetAvailableQuantityAsync(warehouseId, productId);

        if (orderItemId.HasValue)
        {
            DataTable oldItem = await Db.QueryAsync($@"
select
    oi.order_id,
    o.warehouse_id,
    oi.product_id,
    oi.quantity
from {ConnectionSettings.Schema}.order_items oi
join {ConnectionSettings.Schema}.orders o on o.order_id = oi.order_id
where oi.order_item_id = @order_item_id;", p => p.AddWithValue("order_item_id", orderItemId.Value));

            if (oldItem.Rows.Count == 0)
            {
                throw new UserFriendlyException("Выбранная позиция заказа не найдена. Обновите таблицу и попробуйте снова.");
            }

            DataRow row = oldItem.Rows[0];
            int oldWarehouseId = Convert.ToInt32(row["warehouse_id"]);
            int oldProductId = Convert.ToInt32(row["product_id"]);
            int oldQuantity = Convert.ToInt32(row["quantity"]);

            if (oldWarehouseId == warehouseId && oldProductId == productId)
            {
                available += oldQuantity;
            }
        }

        if (available <= 0)
        {
            throw new UserFriendlyException("На выбранном складе нет этого товара.");
        }

        if (quantity > available)
        {
            throw new UserFriendlyException($"Недостаточно товара на складе.\n\nДоступно: {available}\nЗапрошено: {quantity}");
        }
    }

    private static async Task<int> GetOrderWarehouseIdAsync(int orderId)
    {
        DataTable table = await Db.QueryAsync($@"
select warehouse_id
from {ConnectionSettings.Schema}.orders
where order_id = @order_id;", p => p.AddWithValue("order_id", orderId));

        if (table.Rows.Count == 0 || table.Rows[0]["warehouse_id"] == DBNull.Value)
        {
            throw new UserFriendlyException("У выбранного заказа не указан склад. Откройте заказ и выберите склад.");
        }

        return Convert.ToInt32(table.Rows[0]["warehouse_id"]);
    }

    private static async Task<int> GetAvailableQuantityAsync(int warehouseId, int productId)
    {
        DataTable table = await Db.QueryAsync($@"
select quantity
from {ConnectionSettings.Schema}.inventory
where warehouse_id = @warehouse_id
  and product_id = @product_id;", p =>
        {
            p.AddWithValue("warehouse_id", warehouseId);
            p.AddWithValue("product_id", productId);
        });

        if (table.Rows.Count == 0 || table.Rows[0]["quantity"] == DBNull.Value)
        {
            return 0;
        }

        return Convert.ToInt32(table.Rows[0]["quantity"]);
    }

    private static void AddParameters(NpgsqlParameterCollection parameters, IEnumerable<FieldDefinition> fields, Dictionary<string, object?> values)
    {
        foreach (FieldDefinition field in fields)
        {
            object? value = values.TryGetValue(field.ColumnName, out object? rawValue) ? rawValue : null;
            parameters.AddWithValue(field.ColumnName, value ?? DBNull.Value);
        }
    }

    private static async Task InsertUserAsync(Dictionary<string, object?> values)
    {
        string sql = $@"
insert into {ConnectionSettings.Schema}.app_users (login, password_hash, role, is_active, employee_id, customer_id)
values (@login, public.crypt(@password, public.gen_salt('bf')), @role::{ConnectionSettings.Schema}.app_role, @is_active, @employee_id, @customer_id);";

        await Db.ExecuteAsync(sql, p =>
        {
            p.AddWithValue("login", values["login"] ?? DBNull.Value);
            p.AddWithValue("password", values["password"] ?? DBNull.Value);
            p.AddWithValue("role", values["role"] ?? DBNull.Value);
            p.AddWithValue("is_active", values["is_active"] ?? true);
            p.AddWithValue("employee_id", values["employee_id"] ?? DBNull.Value);
            p.AddWithValue("customer_id", values["customer_id"] ?? DBNull.Value);
        });
    }

    private static async Task UpdateUserAsync(int id, Dictionary<string, object?> values)
    {
        string password = Convert.ToString(values.GetValueOrDefault("password")) ?? string.Empty;

        string sql;
        if (string.IsNullOrWhiteSpace(password))
        {
            sql = $@"
update {ConnectionSettings.Schema}.app_users
set login = @login,
    role = @role::{ConnectionSettings.Schema}.app_role,
    is_active = @is_active,
    employee_id = @employee_id,
    customer_id = @customer_id
where user_id = @id;";
        }
        else
        {
            sql = $@"
update {ConnectionSettings.Schema}.app_users
set login = @login,
    password_hash = public.crypt(@password, public.gen_salt('bf')),
    role = @role::{ConnectionSettings.Schema}.app_role,
    is_active = @is_active,
    employee_id = @employee_id,
    customer_id = @customer_id
where user_id = @id;";
        }

        await Db.ExecuteAsync(sql, p =>
        {
            p.AddWithValue("id", id);
            p.AddWithValue("login", values["login"] ?? DBNull.Value);
            p.AddWithValue("role", values["role"] ?? DBNull.Value);
            p.AddWithValue("is_active", values["is_active"] ?? true);
            p.AddWithValue("employee_id", values["employee_id"] ?? DBNull.Value);
            p.AddWithValue("customer_id", values["customer_id"] ?? DBNull.Value);
            if (!string.IsNullOrWhiteSpace(password))
            {
                p.AddWithValue("password", password);
            }
        });
    }
}