using ComputerSalesApp.Models;

namespace ComputerSalesApp.Data;

internal static class TableCatalog
{
    private static string S => ConnectionSettings.Schema;

    public static List<TableDefinition> CreateTables(UserSession session)
    {
        if (!session.IsAdmin)
        {
            return new List<TableDefinition>
            {
                CustomerProfile(session),
                Products(),
                Orders(session),
                OrderItems(session)
            };
        }

        return new List<TableDefinition>
        {
            Warehouses(),
            Employees(),
            Customers(),
            AppUsers(),
            EmployeeWarehouses(),
            Products(),
            Inventory(),
            Orders(session),
            OrderItems(session),
            AuditLog()
        };
    }

    private static TableDefinition Warehouses() => new()
    {
        TableName = "warehouses",
        DisplayName = "Склады",
        IdColumn = "warehouse_id",
        SelectSql = $@"
select warehouse_id, warehouse_number, address, capacity
from {S}.warehouses",
        OrderBy = "warehouse_number",
        Fields = new()
        {
            new() { ColumnName = "warehouse_number", Header = "Номер склада", Kind = FieldKind.Text, Required = true, MaxLength = 30 },
            new() { ColumnName = "address", Header = "Адрес", Kind = FieldKind.Text, Required = true },
            new() { ColumnName = "capacity", Header = "Вместимость", Kind = FieldKind.Integer, Required = true, MinValue = 0 }
        },
        Headers = new()
        {
            ["warehouse_number"] = "Номер склада",
            ["address"] = "Адрес",
            ["capacity"] = "Вместимость"
        },
        HiddenColumns = new() { "warehouse_id" }
    };

    private static TableDefinition Employees() => new()
    {
        TableName = "employees",
        DisplayName = "Сотрудники",
        IdColumn = "employee_id",
        SelectSql = $@"
select employee_id, last_name, first_name, middle_name, position_name, birth_date
from {S}.employees",
        OrderBy = "last_name, first_name",
        Fields = new()
        {
            new() { ColumnName = "last_name", Header = "Фамилия", Kind = FieldKind.Text, Required = true, MaxLength = 80 },
            new() { ColumnName = "first_name", Header = "Имя", Kind = FieldKind.Text, Required = true, MaxLength = 80 },
            new() { ColumnName = "middle_name", Header = "Отчество", Kind = FieldKind.Text, MaxLength = 80, Nullable = true },
            new() { ColumnName = "position_name", Header = "Должность", Kind = FieldKind.Text, Required = true, MaxLength = 100 },
            new() { ColumnName = "birth_date", Header = "Дата рождения", Kind = FieldKind.Date, Required = true }
        },
        Headers = new()
        {
            ["last_name"] = "Фамилия",
            ["first_name"] = "Имя",
            ["middle_name"] = "Отчество",
            ["position_name"] = "Должность",
            ["birth_date"] = "Дата рождения"
        },
        HiddenColumns = new() { "employee_id" }
    };

    private static TableDefinition AppUsers() => new()
    {
        TableName = "app_users",
        DisplayName = "Пользователи",
        IdColumn = "user_id",
        AdminOnly = true,
        SelectSql = $@"
select
    au.user_id,
    au.login,
    au.role::text as role,
    au.is_active,
    au.employee_id,
    concat_ws(' ', e.last_name, e.first_name, e.middle_name) as employee_name,
    au.customer_id,
    concat_ws(' ', c.last_name, c.first_name, c.middle_name) as customer_name
from {S}.app_users au
left join {S}.employees e on e.employee_id = au.employee_id
left join {S}.customers c on c.customer_id = au.customer_id",
        OrderBy = "login",
        Fields = new()
        {
            new() { ColumnName = "login", Header = "Логин", Kind = FieldKind.Text, Required = true, MaxLength = 80 },
            new() { ColumnName = "password", Header = "Пароль", Kind = FieldKind.Password, Required = true, MaxLength = 100 },
            new() { ColumnName = "role", Header = "Роль", Kind = FieldKind.Role, Required = true, Items = new[] { "admin", "user" } },
            new() { ColumnName = "is_active", Header = "Активен", Kind = FieldKind.Boolean, Required = true },
            new()
            {
                ColumnName = "employee_id",
                Header = "Сотрудник",
                Kind = FieldKind.Combo,
                Nullable = true,
                DisplayColumnName = "employee_name",
                ReferenceSql = $"select employee_id as id, concat_ws(' ', last_name, first_name, middle_name) as text from {S}.employees order by text"
            },
            new()
            {
                ColumnName = "customer_id",
                Header = "Покупатель",
                Kind = FieldKind.Combo,
                Nullable = true,
                DisplayColumnName = "customer_name",
                ReferenceSql = $"select customer_id as id, concat_ws(' ', last_name, first_name, middle_name) as text from {S}.customers order by text"
            }
        },
        Headers = new()
        {
            ["login"] = "Логин",
            ["role"] = "Роль",
            ["is_active"] = "Активен",
            ["employee_name"] = "Сотрудник",
            ["customer_name"] = "Покупатель"
        },
        HiddenColumns = new() { "user_id", "employee_id", "customer_id" }
    };

    private static TableDefinition EmployeeWarehouses() => new()
    {
        TableName = "employee_warehouses",
        DisplayName = "Сотрудники на складах",
        IdColumn = "employee_warehouse_id",
        SelectSql = $@"
select
    ew.employee_warehouse_id,
    ew.employee_id,
    concat_ws(' ', e.last_name, e.first_name, e.middle_name) as employee_name,
    ew.warehouse_id,
    w.warehouse_number || ' — ' || w.address as warehouse_name,
    ew.start_date
from {S}.employee_warehouses ew
join {S}.employees e on e.employee_id = ew.employee_id
join {S}.warehouses w on w.warehouse_id = ew.warehouse_id",
        OrderBy = "employee_name, warehouse_name",
        Fields = new()
        {
            new()
            {
                ColumnName = "employee_id",
                Header = "Сотрудник",
                Kind = FieldKind.Combo,
                Required = true,
                DisplayColumnName = "employee_name",
                ReferenceSql = $"select employee_id as id, concat_ws(' ', last_name, first_name, middle_name) as text from {S}.employees order by text"
            },
            new()
            {
                ColumnName = "warehouse_id",
                Header = "Склад",
                Kind = FieldKind.Combo,
                Required = true,
                DisplayColumnName = "warehouse_name",
                ReferenceSql = $"select warehouse_id as id, warehouse_number || ' — ' || address as text from {S}.warehouses order by text"
            },
            new() { ColumnName = "start_date", Header = "Дата начала работы", Kind = FieldKind.Date, Required = true }
        },
        Headers = new()
        {
            ["employee_name"] = "Сотрудник",
            ["warehouse_name"] = "Склад",
            ["start_date"] = "Дата начала работы"
        },
        HiddenColumns = new() { "employee_warehouse_id", "employee_id", "warehouse_id" }
    };

    private static TableDefinition Customers() => CustomerBase("Покупатели", null);

    private static TableDefinition CustomerProfile(UserSession session) => CustomerBase("Мои данные", session.CustomerId);

    private static TableDefinition CustomerBase(string displayName, int? customerId)
    {
        string where = customerId.HasValue ? $"\nwhere customer_id = {customerId.Value}" : string.Empty;
        return new TableDefinition
        {
            TableName = "customers",
            DisplayName = displayName,
            IdColumn = "customer_id",
            SelectSql = $@"
select customer_id, last_name, first_name, middle_name, address, phone
from {S}.customers{where}",
            OrderBy = "last_name, first_name",
            Fields = new()
            {
                new() { ColumnName = "last_name", Header = "Фамилия", Kind = FieldKind.Text, Required = true, MaxLength = 80 },
                new() { ColumnName = "first_name", Header = "Имя", Kind = FieldKind.Text, Required = true, MaxLength = 80 },
                new() { ColumnName = "middle_name", Header = "Отчество", Kind = FieldKind.Text, MaxLength = 80, Nullable = true },
                new() { ColumnName = "address", Header = "Адрес", Kind = FieldKind.Text, Nullable = true },
                new() { ColumnName = "phone", Header = "Телефон", Kind = FieldKind.Text, MaxLength = 20, Nullable = true }
            },
            Headers = new()
            {
                ["last_name"] = "Фамилия",
                ["first_name"] = "Имя",
                ["middle_name"] = "Отчество",
                ["address"] = "Адрес",
                ["phone"] = "Телефон"
            },
            HiddenColumns = new() { "customer_id" }
        };
    }

    private static TableDefinition Products() => new()
    {
        TableName = "products",
        DisplayName = "Товары",
        IdColumn = "product_id",
        SelectSql = $@"
select product_id, product_name, manufacturer, characteristics, price
from {S}.products",
        OrderBy = "product_name",
        Fields = new()
        {
            new() { ColumnName = "product_name", Header = "Наименование", Kind = FieldKind.Text, Required = true, MaxLength = 150 },
            new() { ColumnName = "manufacturer", Header = "Изготовитель", Kind = FieldKind.Text, Required = true, MaxLength = 120 },
            new() { ColumnName = "characteristics", Header = "Характеристики", Kind = FieldKind.Text, Nullable = true },
            new() { ColumnName = "price", Header = "Цена", Kind = FieldKind.Decimal, Required = true, MinValue = 0 }
        },
        Headers = new()
        {
            ["product_name"] = "Наименование",
            ["manufacturer"] = "Изготовитель",
            ["characteristics"] = "Характеристики",
            ["price"] = "Цена"
        },
        HiddenColumns = new() { "product_id" }
    };

    private static TableDefinition Inventory() => new()
    {
        TableName = "inventory",
        DisplayName = "Остатки на складах",
        IdColumn = "inventory_id",
        SelectSql = $@"
select
    i.inventory_id,
    i.warehouse_id,
    w.warehouse_number || ' — ' || w.address as warehouse_name,
    i.product_id,
    p.product_name || ' — ' || p.manufacturer as product_name,
    i.quantity
from {S}.inventory i
join {S}.warehouses w on w.warehouse_id = i.warehouse_id
join {S}.products p on p.product_id = i.product_id",
        OrderBy = "warehouse_name, product_name",
        Fields = new()
        {
            new()
            {
                ColumnName = "warehouse_id",
                Header = "Склад",
                Kind = FieldKind.Combo,
                Required = true,
                DisplayColumnName = "warehouse_name",
                ReferenceSql = $"select warehouse_id as id, warehouse_number || ' — ' || address as text from {S}.warehouses order by text"
            },
            new()
            {
                ColumnName = "product_id",
                Header = "Товар",
                Kind = FieldKind.Combo,
                Required = true,
                DisplayColumnName = "product_name",
                ReferenceSql = $"select product_id as id, product_name || ' — ' || manufacturer as text from {S}.products order by text"
            },
            new() { ColumnName = "quantity", Header = "Количество", Kind = FieldKind.Integer, Required = true, MinValue = 0 }
        },
        Headers = new()
        {
            ["warehouse_name"] = "Склад",
            ["product_name"] = "Товар",
            ["quantity"] = "Количество"
        },
        HiddenColumns = new() { "inventory_id", "warehouse_id", "product_id" }
    };

    private static TableDefinition Orders(UserSession session)
    {
        string where = session.IsAdmin ? string.Empty : $"\nwhere o.customer_id = {session.CustomerId ?? -1}";
        return new TableDefinition
        {
            TableName = "orders",
            DisplayName = session.IsAdmin ? "Заказы" : "Мои заказы",
            IdColumn = "order_id",
            SelectSql = $@"
select
    o.order_id,
    o.customer_id,
    concat_ws(' ', c.last_name, c.first_name, c.middle_name) as customer_name,
    o.employee_id,
    concat_ws(' ', e.last_name, e.first_name, e.middle_name) as employee_name,
    o.warehouse_id,
    w.warehouse_number || ' — ' || w.address as warehouse_name,
    o.order_date,
    o.status,
    coalesce(sum(oi.quantity * oi.unit_price), 0)::numeric(12,2) as order_total
from {S}.orders o
join {S}.customers c on c.customer_id = o.customer_id
join {S}.employees e on e.employee_id = o.employee_id
join {S}.warehouses w on w.warehouse_id = o.warehouse_id
left join {S}.order_items oi on oi.order_id = o.order_id{where}
group by
    o.order_id,
    o.customer_id,
    c.last_name, c.first_name, c.middle_name,
    o.employee_id,
    e.last_name, e.first_name, e.middle_name,
    o.warehouse_id,
    w.warehouse_number, w.address,
    o.order_date,
    o.status",
            OrderBy = "order_date desc, order_id desc",
            Fields = new()
            {
                new()
                {
                    ColumnName = "customer_id",
                    Header = "Покупатель",
                    Kind = FieldKind.Combo,
                    Required = true,
                    DisplayColumnName = "customer_name",
                    ReferenceSql = $"select customer_id as id, concat_ws(' ', last_name, first_name, middle_name) as text from {S}.customers order by text"
                },
                new()
                {
                    ColumnName = "employee_id",
                    Header = "Сотрудник",
                    Kind = FieldKind.Combo,
                    Required = true,
                    DisplayColumnName = "employee_name",
                    ReferenceSql = $"select employee_id as id, concat_ws(' ', last_name, first_name, middle_name) as text from {S}.employees order by text"
                },
                new()
                {
                    ColumnName = "warehouse_id",
                    Header = "Склад",
                    Kind = FieldKind.Combo,
                    Required = true,
                    DisplayColumnName = "warehouse_name",
                    ReferenceSql = $"select warehouse_id as id, warehouse_number || ' — ' || address as text from {S}.warehouses order by text"
                },
                new() { ColumnName = "order_date", Header = "Дата заказа", Kind = FieldKind.Date, Required = true },
                new() { ColumnName = "status", Header = "Статус", Kind = FieldKind.Status, Required = true, Items = new[] { "создан", "в работе", "выполнен", "отменён" } }
            },
            Headers = new()
            {
                ["customer_name"] = "Покупатель",
                ["employee_name"] = "Сотрудник",
                ["warehouse_name"] = "Склад",
                ["order_date"] = "Дата заказа",
                ["status"] = "Статус",
                ["order_total"] = "Итоговая стоимость"
            },
            HiddenColumns = new() { "order_id", "customer_id", "employee_id", "warehouse_id" }
        };
    }

    private static TableDefinition OrderItems(UserSession session)
    {
        string where = session.IsAdmin ? string.Empty : $"\nwhere o.customer_id = {session.CustomerId ?? -1}";
        return new TableDefinition
        {
            TableName = "order_items",
            DisplayName = session.IsAdmin ? "Товары в заказе" : "Товары в моих заказах",
            IdColumn = "order_item_id",
            SelectSql = $@"
select
    oi.order_item_id,
    oi.order_id,
    'Заказ №' || oi.order_id || ' от ' || to_char(o.order_date, 'DD.MM.YYYY') || ', склад ' || w.warehouse_number as order_name,
    oi.product_id,
    p.product_name || ' — ' || p.manufacturer as product_name,
    oi.quantity,
    oi.unit_price,
    oi.quantity * oi.unit_price as item_total
from {S}.order_items oi
join {S}.orders o on o.order_id = oi.order_id
join {S}.warehouses w on w.warehouse_id = o.warehouse_id
join {S}.products p on p.product_id = oi.product_id{where}",
            OrderBy = "order_id desc, product_name",
            Fields = new()
            {
                new()
                {
                    ColumnName = "order_id",
                    Header = "Заказ",
                    Kind = FieldKind.Combo,
                    Required = true,
                    DisplayColumnName = "order_name",
                    ReferenceSql = $"select o.order_id as id, 'Заказ №' || o.order_id || ' от ' || to_char(o.order_date, 'DD.MM.YYYY') || ', склад ' || w.warehouse_number as text from {S}.orders o join {S}.warehouses w on w.warehouse_id = o.warehouse_id order by o.order_id desc"
                },
                new()
                {
                    ColumnName = "product_id",
                    Header = "Товар",
                    Kind = FieldKind.Combo,
                    Required = true,
                    DisplayColumnName = "product_name",
                    ReferenceSql = $"select product_id as id, product_name || ' — ' || manufacturer as text from {S}.products order by text"
                },
                new() { ColumnName = "quantity", Header = "Количество", Kind = FieldKind.Integer, Required = true, MinValue = 1 },
                new() { ColumnName = "unit_price", Header = "Цена за единицу", Kind = FieldKind.Decimal, Required = true, MinValue = 0 }
            },
            Headers = new()
            {
                ["order_name"] = "Заказ",
                ["product_name"] = "Товар",
                ["quantity"] = "Количество",
                ["unit_price"] = "Цена за единицу",
                ["item_total"] = "Сумма"
            },
            HiddenColumns = new() { "order_item_id", "order_id", "product_id" }
        };
    }


    private static TableDefinition AuditLog() => new()
    {
        TableName = "audit_log",
        DisplayName = "Аудит действий",
        IdColumn = "audit_id",
        AdminOnly = true,
        ReadOnly = true,
        SelectSql = $@"
select
    audit_id,
    changed_at,
    db_user,
    table_name,
    operation,
    record_id,
    old_data::text as old_data,
    new_data::text as new_data
from {S}.audit_log",
        OrderBy = "changed_at desc, audit_id desc",
        Fields = new(),
        Headers = new()
        {
            ["changed_at"] = "Дата и время",
            ["db_user"] = "Пользователь БД",
            ["table_name"] = "Таблица",
            ["operation"] = "Операция",
            ["record_id"] = "ID записи",
            ["old_data"] = "Было",
            ["new_data"] = "Стало"
        },
        HiddenColumns = new() { "audit_id" }
    };

}
