using Ease.NetCore.DataAccess;
using Ease.NetCore.DataAccess.SQL;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
using OnlineSalesAutoCrop.CoreAPI.Models;
using OnlineSalesAutoCrop.CoreAPI.Models.Global;
using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses;
using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.MobileApp;
using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.MobileApp;

public class OrderService : IOrderService
{
    private readonly AppSettings _settings;
    private readonly IUserService _userService;
    private readonly IMobileMasterDataService _masterService;

    public OrderService(IOptions<AppSettings> options, IUserService userService, IMobileMasterDataService masterService)
    {
        _settings = options.Value;
        _userService = userService;
        _masterService = masterService;
    }

    public async Task<SaveOrderResponse> SaveOrderAsync(SaveOrderRequest request, int userId)
    {
        SaveOrderResponse response = new();

        try
        {
            using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
            try
            {
                // 1. Role - fetched using the claims-resolved UserId
                UserRoleDto? userRole = await GetUserRoleAsync(tc, userId);
                if (userRole is null)
                    throw new Exception($"User id '{userId}' was not found.");

                if (!IsPermissiontoModify(request.OrderStatus ,userRole.RoleId  ))
                {
                    throw new Exception("You are not permitted to submit of modify this order");
                }

                var existingOrder = await GetExistingOrderIdAsync(tc, request.UUID);
                request.OrderId = (existingOrder != null && existingOrder.OrderId > 0) ? existingOrder.OrderId : 0;



                if (existingOrder is not null && existingOrder.OrderId > 0 &&
                    (existingOrder.OrderStatus == OrderStatusEnum.Approved ||
                    existingOrder.OrderStatus == OrderStatusEnum.CancelledByApprover ||
                    existingOrder.OrderStatus == OrderStatusEnum.CancelledByValidator))
                {
                    throw new Exception($"Order with UUID '{request.UUID}' has already been {existingOrder.OrderStatus.ToString()} and cannot be modified.");
                }


                foreach (var item in request.Items)
                {
                    item.ItemId = 0;
                    item.OrderId = request.OrderId;
                }


                // 2. Insert vs update decision
                bool isUpdate = existingOrder != null && existingOrder.OrderId > 0 ;

                // 3. Reference data - SPs only fetch, every pass/fail call happens here
                CustomerLookupDto? customer = await GetCustomerAsync(tc, request.CustomerCode);
                if (customer is null)
                    throw new Exception($"Customer code '{request.CustomerCode}' does not exist.");

                request.CustomerId = customer.CustomerId;

                PaymentTermLookupDto? paymentTerm = await GetPaymentTermAsync(tc, request.PaymentTermCode, request.SalesOrgCode);
                if (paymentTerm is null)
                    throw new Exception($"Payment term code '{request.PaymentTermCode}' does not exist for sales org '{request.SalesOrgCode}'.");


                DiscountLookupDto? discount = await GetDiscountAsync(tc, request.DiscountCode, request.SalesOrgCode);
                if (discount is null)
                    throw new Exception($"Discount code '{request.DiscountCode}' does not exist.");

                bool focCodeExists = await CheckFocCodeExistsAsync(tc, request.FocCode, request.SalesOrgCode);
                if (!focCodeExists)
                    throw new Exception($"FOC code '{request.FocCode}' does not exist.");

                if (request.Items is null || request.Items.Count == 0)
                    throw new Exception("Order must contain at least one item.");

                Dictionary<string, MaterialLookupDto> materialsByCode = new(StringComparer.OrdinalIgnoreCase);
                if (request.Items is { Count: > 0 })
                {
                    List<string> distinctCodes = request.Items
                        .Select(i => i.MaterialCode)
                        .Distinct(StringComparer.OrdinalIgnoreCase)
                        .ToList();

                    materialsByCode = await GetMaterialsAsync(tc, distinctCodes);

                    foreach (string code in distinctCodes)
                    {
                        if (!materialsByCode.ContainsKey(code))
                            throw new Exception($"Material code '{code}' does not exist.");
                    }
                }

                var focItem = request.Items.FirstOrDefault(x => x.IsFocItem == true);
                var dicountItem = request.Items.FirstOrDefault(x => x.DiscountAmount > 0);
                List<SaveOrderPromotionRequest> promotions = new List<SaveOrderPromotionRequest>();

                request.FocValue = 0;

                if(dicountItem!= null)
                {
                    promotions.Add(new SaveOrderPromotionRequest()
                    {
                        OrderId = isUpdate ? existingOrder.OrderId : 0,
                        MaterialId = materialsByCode[dicountItem.MaterialCode].MaterialId,
                        PromotionCode = request.DiscountCode,
                        DiscountPercentage = dicountItem.DiscountPercentage,
                        DiscountValue = dicountItem.DiscountAmount,
                        ForQuantity = dicountItem.PromoQuantity,
                        OrderFocQty = 0,
                        ValidatedFocQty = 0,
                        ApprovedFocQty = 0,
                        PromotionType = 1,
                    });
                }
              
                if(focItem != null)
                {
                    promotions.Add(new SaveOrderPromotionRequest()
                    {
                        OrderId = isUpdate ? existingOrder.OrderId : 0,
                        MaterialId = materialsByCode[focItem.MaterialCode].MaterialId,
                        PromotionCode = request.FocCode,
                        DiscountPercentage = 0,
                        DiscountValue = 0,
                        ForQuantity = focItem.PromoQuantity,
                        OrderFocQty = (request.OrderStatus == OrderStatusEnum.Draft || request.OrderStatus == OrderStatusEnum.InOrderValidation) ? focItem.OrderQuantity : 0,
                        ValidatedFocQty = (request.OrderStatus == OrderStatusEnum.CancelledByValidator || request.OrderStatus == OrderStatusEnum.InOrderApproval) ? focItem.OrderQuantity : 0,
                        ApprovedFocQty = (request.OrderStatus == OrderStatusEnum.CancelledByApprover || request.OrderStatus == OrderStatusEnum.Approved) ? focItem.OrderQuantity : 0,
                        PromotionType = 2,
                    });
                }              

                // 5. Insert or update
                int orderId;
                if (isUpdate)
                {
                    orderId = existingOrder.OrderId;
                    await UpdateOrderAsync(tc, orderId, request, customer!, paymentTerm!, discount!, materialsByCode, promotions, userId,(int) userRole!.RoleId);
                    response.IsNewOrder = !isUpdate;
                    response.UUID = request.UUID;
                    response.OrderId = orderId;
                    response.OrderDate = existingOrder.OrderDate;
                }
                else
                {
                    response = await InsertOrderAsync(tc, request, customer!, paymentTerm!, discount!, materialsByCode, promotions, userId,(int) userRole!.RoleId);
                }

                tc.End();
            }
            catch (Exception ie)
            {
                tc?.HandleError();
                throw DBCustomError.GenerateCustomError(ie);
            }
        }
        catch (Exception)
        {
            throw;
        }

        return response;
    }

    private bool IsPermissiontoModify(OrderStatusEnum orderStatus, UserRoleTypeEnum roleId)
    {
        if( UserRoleTypeEnum.OrderCollector == roleId && ( OrderStatusEnum.Draft == orderStatus  || OrderStatusEnum.InOrderValidation == orderStatus))
        {
            return true;
        }

        if (UserRoleTypeEnum.OrderValidator == roleId && (OrderStatusEnum.CancelledByValidator == orderStatus || OrderStatusEnum.InOrderApproval == orderStatus))
        {
            return true;
        }

        if (UserRoleTypeEnum.OrderApprover == roleId && (OrderStatusEnum.InOrderApproval == orderStatus || OrderStatusEnum.CancelledByApprover == orderStatus || OrderStatusEnum.Approved == orderStatus))
        {
            return true;
        }

        return false;
    }


    private async Task<UserRoleDto?> GetUserRoleAsync(TransactionContext tc, int userId)
    {
        try
        {
            SqlParameter[] p =
            [
               SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId)
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetUserRoleByUserId", parameterValues: p);

            if (dr.Read())
            {
                return new UserRoleDto { UserId = dr.GetInt32(0), RoleId =(UserRoleTypeEnum) dr.GetInt32(1) };
            }

            return null;
        }
        catch (Exception)
        {
            throw;
        }

    }

    private async Task<OrderBasicResponse?> GetExistingOrderIdAsync(TransactionContext tc, Guid uuid)
    {
        OrderBasicResponse response = null;
        try
        {
            SqlParameter[] p =
            [
                SqlHelperExtension.CreateInParam(pName: "@UUID", pType: SqlDbType.UniqueIdentifier, pValue: uuid)
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetOrderHeaderByUUID", parameterValues: p);

            if( dr.Read())
            {
                response = new OrderBasicResponse()
                {
                    OrderId = dr.GetInt32(0),
                    OrderNo = dr.GetString(1),
                    OrderDate = dr.GetDateTime(2),
                    UUID = uuid,
                    OrderStatus = (OrderStatusEnum)dr.GetInt32(3)
                };
            }
            dr.Close();
        }
        catch (Exception)
        {
            throw;
        }
        return response;
    }

    private async Task<CustomerLookupDto?> GetCustomerAsync(TransactionContext tc, string customerCode)
    {
        try
        {
            SqlParameter[] p =
            [
                SqlHelperExtension.CreateInParam(pName: "@CustomerCode", pType: SqlDbType.VarChar, pValue: customerCode)
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetCustomerByCode", parameterValues: p);

            if (dr.Read())
            {
                return new CustomerLookupDto
                {
                    CustomerId = dr.GetInt32(0),
                    CustomerCode = dr.GetString(1),
                    CustomerName = dr.GetString(2)
                };
            }

            return null;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private async Task<PaymentTermLookupDto?> GetPaymentTermAsync(TransactionContext tc, string paymentTermCode, string salesOrgCode)
    {
        try
        {
            SqlParameter[] p =
            [
                SqlHelperExtension.CreateInParam(pName: "@PaymentTermCode", pType: SqlDbType.VarChar, pValue: paymentTermCode),
                SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: salesOrgCode)
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetPaymentTermByCode", parameterValues: p);

            if (dr.Read())
            {
                return new PaymentTermLookupDto { PaymentTermId = dr.GetInt32(0), PaymentTermCode = dr.GetString(1) };
            }

            return null;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private async Task<DiscountLookupDto?> GetDiscountAsync(TransactionContext tc, string discountCode, string salesOrgCode)
    {
        try
        {
            SqlParameter[] p =
            [
                SqlHelperExtension.CreateInParam(pName: "@DiscountCode", pType: SqlDbType.VarChar, pValue: discountCode),
                SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: salesOrgCode)
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetDiscountByCode", parameterValues: p);

            if (dr.Read())
            {
                return new DiscountLookupDto { DiscountId = dr.GetInt32(0), DiscountCode = dr.GetString(1) };
            }

            return null;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private async Task<bool> CheckFocCodeExistsAsync(TransactionContext tc, string focCode, string salesOrgCode)
    {
        try
        {
            SqlParameter[] p =
            [
                SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: salesOrgCode),
                SqlHelperExtension.CreateInParam(pName: "@FocCode", pType: SqlDbType.VarChar, pValue: focCode)
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.CheckFocCodeExists", parameterValues: p);

            return dr.Read();
        }
        catch (Exception)
        {
            throw;
        }
    }

    private async Task<Dictionary<string, MaterialLookupDto>> GetMaterialsAsync(TransactionContext tc, IEnumerable<string> materialCodes)
    {
        try
        {
            Dictionary<string, MaterialLookupDto> result = new(StringComparer.OrdinalIgnoreCase);

            DataTable codesTable = new();
            codesTable.Columns.Add("Code", typeof(string));
            foreach (string code in materialCodes)
                codesTable.Rows.Add(code);

            SqlParameter codesParam = SqlHelperExtension.CreateInParam(pName: "@Codes", pType: SqlDbType.Structured, pValue: codesTable);
            codesParam.TypeName = "dbo.CodeListTableType"; // CreateInParam has no TypeName slot, so it's set separately

            SqlParameter[] p = [codesParam];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetMaterialsByCodes", parameterValues: p);

            while (dr.Read())
            {
                MaterialLookupDto dto = new()
                {
                    MaterialId = dr.GetInt32(0),
                    MaterialCode = dr.GetString(1),
                    MaterialName = dr.GetString(2)
                };

                result[dto.MaterialCode] = dto;
            }

            dr.Close();
            return result;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private async Task<List<FreeGoodsRuleDto>> GetFreeGoodsRulesAsync(TransactionContext tc, string salesOrgCode, string focCode, IEnumerable<int> materialIds, DateTime? asOfDate, int customerId)
    {
        try
        {
            List<FreeGoodsRuleDto> rules = new();

            DataTable idsTable = new();
            idsTable.Columns.Add("Id", typeof(int));
            foreach (int id in materialIds)
                idsTable.Rows.Add(id);

            SqlParameter idsParam = SqlHelperExtension.CreateInParam(pName: "@MaterialIds", pType: SqlDbType.Structured, pValue: idsTable);
            idsParam.TypeName = "dbo.IntListTableType";

            SqlParameter[] p =
            [
                SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: salesOrgCode),
                SqlHelperExtension.CreateInParam(pName: "@CustomerId", pType: SqlDbType.VarChar, pValue: customerId),
                SqlHelperExtension.CreateInParam(pName: "@FocCode", pType: SqlDbType.VarChar, pValue: focCode),idsParam,
                SqlHelperExtension.CreateInParam(pName: "@AsOfDate", pType: SqlDbType.DateTime, pValue: DateTime.Now)
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetFreeGoodsRules", parameterValues: p);

            while (dr.Read())
            {
                rules.Add(new FreeGoodsRuleDto
                {
                    FreeGoodsId = dr.GetInt32(0),
                    MaterialId = dr.GetInt32(1),
                    MinOrderQuantity = dr.GetDecimal(2),
                    ForQuantity = dr.GetDecimal(3),
                    FreeQuantity = dr.GetDecimal(4),
                    UnitOfMeasure = dr.IsDBNull(5) ? string.Empty : dr.GetString(5),
                    FocUnitOfMeasure = dr.IsDBNull(6) ? string.Empty : dr.GetString(6)
                });
            }

            dr.Close();
            return rules;
        }
        catch (Exception)
        {
            throw;
        }
    }

    // Once OrderQuantity >= MinOrderQuantity, the material earns FreeQuantity for every
    // ForQuantity units ordered (e.g. Min 10 / For 10 / Free 1 -> ordering 25 = floor(25/10)*1 = 2 free).
    // This is my read of "calculate based on order quantity" - swap the formula below if yours differs.
    private static List<SaveOrderPromotionRequest> BuildFocPromotions(SaveOrderRequest request,
        Dictionary<string, MaterialLookupDto> materialsByCode,List<FreeGoodsRuleDto> focRules)
    {
        try
        {
            List<SaveOrderPromotionRequest> promotions = new();

            Dictionary<int, FreeGoodsRuleDto> rulesByMaterialId = focRules
                .GroupBy(r => r.MaterialId)
                .ToDictionary(g => g.Key, g => g.First()); // first active rule wins if more than one overlaps

            foreach (SaveOrderItemRequest item in request.Items)
            {
                if (item.IsFocItem)
                    continue; // a free item doesn't itself trigger more free goods

                if (!materialsByCode.TryGetValue(item.MaterialCode, out MaterialLookupDto? material))
                    continue; // already caught by validation - guarded here defensively

                if (!rulesByMaterialId.TryGetValue(material.MaterialId, out FreeGoodsRuleDto? rule))
                    continue; // no free-goods rule configured for this material under this FOC code

                if (rule.ForQuantity <= 0 || item.OrderQuantity < rule.MinOrderQuantity)
                    continue;

                decimal eligibleSets = Math.Floor(item.OrderQuantity / rule.ForQuantity);
                decimal focQuantity = eligibleSets * rule.FreeQuantity;

                if (focQuantity <= 0)
                    continue;

                promotions.Add(new SaveOrderPromotionRequest
                {
                    MaterialId = material.MaterialId,
                    PromotionCode = request.FocCode,
                    DiscountPercentage = 0,
                    DiscountValue = 0,
                    ForQuantity = rule.ForQuantity,
                    OrderFocQty = focQuantity,
                    ValidatedFocQty = 0,
                    ApprovedFocQty = 0,
                    PromotionType = 1 // TODO: swap for your real "free goods" PromotionType constant
                });
            }

            return promotions;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private static DataTable ToOrderItemTable(IEnumerable<SaveOrderItemRequest> items, Dictionary<string, MaterialLookupDto> materialsByCode, int orderStatus )
    {
        try
        {
            DataTable table = new();
            table.Columns.Add("OrderId", typeof(int));
            table.Columns.Add("ItemId", typeof(int));
            table.Columns.Add("MaterialId", typeof(int));
            table.Columns.Add("MaterialCode", typeof(string));
            table.Columns.Add("MaterialName", typeof(string));
            table.Columns.Add("UnitPrice", typeof(decimal));
            table.Columns.Add("SalesUnit", typeof(string));
            table.Columns.Add("PromoQty", typeof(decimal));
            table.Columns.Add("OrderQuantity", typeof(decimal));
            table.Columns.Add("ValidatedQuantity", typeof(decimal));
            table.Columns.Add("ApprovedQuantity", typeof(decimal));
            table.Columns.Add("DiscountPercentage", typeof(decimal));
            table.Columns.Add("DiscountAmount", typeof(decimal));
            table.Columns.Add("IsFocItem", typeof(bool));
            table.Columns.Add("LineTotalValue", typeof(decimal));
            table.Columns.Add("OrderStatus", typeof(int));

            foreach (SaveOrderItemRequest item in items)
            {
                MaterialLookupDto material = materialsByCode[item.MaterialCode];

                table.Rows.Add(
                    item.ItemId,
                    item.OrderId,
                    material.MaterialId,
                    item.MaterialCode,
                    item.MaterialName ?? material.MaterialName,
                    item.UnitPrice,
                    item.SalesUnit,
                    item.PromoQuantity,
                    item.OrderQuantity,
                    item.ValidatedQuantity,
                    item.ApprovedQuantity,
                    item.DiscountPercentage,
                    item.DiscountAmount,
                    item.IsFocItem,
                    item.LineTotalValue,
                    orderStatus);
            }

            return table;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private static DataTable ToOrderPromotionTable(IEnumerable<SaveOrderPromotionRequest> promotions)
    {
        try
        {
            DataTable table = new();
            table.Columns.Add("ItemId", typeof(int));
            table.Columns.Add("OrderId", typeof(int));
            table.Columns.Add("MaterialId", typeof(int));
            table.Columns.Add("PromotionCode", typeof(string));
            table.Columns.Add("DiscountPercentage", typeof(decimal));
            table.Columns.Add("DiscountValue", typeof(decimal));
            table.Columns.Add("ForQuantity", typeof(decimal));
            table.Columns.Add("OrderFocQty", typeof(decimal));
            table.Columns.Add("ValidatedFocQty", typeof(decimal));
            table.Columns.Add("ApprovedFocQty", typeof(decimal));
            table.Columns.Add("PromotionType", typeof(int));

            foreach (SaveOrderPromotionRequest promo in promotions)
            {
                table.Rows.Add(
                    promo.ItemId,
                    promo.OrderId,
                    promo.MaterialId,
                    promo.PromotionCode ,
                    promo.DiscountPercentage,
                    promo.DiscountValue,
                    promo.ForQuantity,
                    promo.OrderFocQty,
                    promo.ValidatedFocQty,
                    promo.ApprovedFocQty,
                    promo.PromotionType);
            }

            return table;
        }
        catch (Exception)
        {
            throw;
        }
    }

    private async Task<SaveOrderResponse> InsertOrderAsync(TransactionContext tc, SaveOrderRequest request, CustomerLookupDto customer, PaymentTermLookupDto paymentTerm,
        DiscountLookupDto discount, Dictionary<string, MaterialLookupDto> materialsByCode,
        List<SaveOrderPromotionRequest> promotions, int userId, int role)
    {
        try
        {
            SqlParameter itemsParam = SqlHelperExtension.CreateInParam(pName: "@Items", pType: SqlDbType.Structured, pValue: ToOrderItemTable(request.Items, materialsByCode, (int)request.OrderStatus));
            itemsParam.TypeName = "dbo.OrderItemTableType";

            SqlParameter promotionsParam = SqlHelperExtension.CreateInParam(pName: "@Promotions", pType: SqlDbType.Structured, pValue: ToOrderPromotionTable(promotions));
            promotionsParam.TypeName = "dbo.OrderPromotionTableType";

            SqlParameter[] p =
            [
                SqlHelperExtension.CreateInParam(pName: "@UUID", pType: SqlDbType.UniqueIdentifier, pValue: request.UUID),
                SqlHelperExtension.CreateInParam(pName: "@OrderNo", pType: SqlDbType.VarChar, pValue: request.OrderNo),
                SqlHelperExtension.CreateInParam(pName: "@OrderDate", pType: SqlDbType.DateTime, pValue: request.OrderDate ?? DateTime.Now ),
                SqlHelperExtension.CreateInParam(pName: "@ExpectedDeliveryDate", pType: SqlDbType.DateTime, pValue: request.ExpectedDeliveryDate ),
                SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue: request.SalesOrgCode),
                SqlHelperExtension.CreateInParam(pName: "@CustomerId", pType: SqlDbType.Int, pValue: customer.CustomerId),
                SqlHelperExtension.CreateInParam(pName: "@CustomerCode", pType: SqlDbType.VarChar, pValue: request.CustomerCode),
                SqlHelperExtension.CreateInParam(pName: "@CustomerName", pType: SqlDbType.VarChar, pValue: request.CustomerName),
                SqlHelperExtension.CreateInParam(pName: "@EmployeeId", pType: SqlDbType.Int, pValue: request.EmployeeId ),
                SqlHelperExtension.CreateInParam(pName: "@EmployeeCode", pType: SqlDbType.VarChar, pValue: request.EmployeeCode ),
                SqlHelperExtension.CreateInParam(pName: "@EmployeeName", pType: SqlDbType.VarChar, pValue: request.EmployeeCode ),
                SqlHelperExtension.CreateInParam(pName: "@PaymentTermCode", pType: SqlDbType.VarChar, pValue: request.PaymentTermCode),
                SqlHelperExtension.CreateInParam(pName: "@DiscountCode", pType: SqlDbType.VarChar, pValue: request.DiscountCode),
                SqlHelperExtension.CreateInParam(pName: "@FOCCode", pType: SqlDbType.VarChar, pValue: request.FocCode),
                SqlHelperExtension.CreateInParam(pName: "@ReferenceTxtNo", pType: SqlDbType.VarChar, pValue: request.ReferenceTxtNo ),
                SqlHelperExtension.CreateInParam(pName: "@GrossValue", pType: SqlDbType.Decimal, pValue: request.GrossValue),
                SqlHelperExtension.CreateInParam(pName: "@DiscountValue", pType: SqlDbType.Decimal, pValue: request.DiscountValue),
                SqlHelperExtension.CreateInParam(pName: "@FOCValue", pType: SqlDbType.Decimal, pValue: request.FocCode),
                SqlHelperExtension.CreateInParam(pName: "@NetValue", pType: SqlDbType.Decimal, pValue: request.NetValue),
                SqlHelperExtension.CreateInParam(pName: "@OrderStatus", pType: SqlDbType.Int, pValue: (int)request.OrderStatus),
                SqlHelperExtension.CreateInParam(pName: "@SapRefferenceOrderNo", pType: SqlDbType.NVarChar, pValue: null),
                SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                itemsParam,
                promotionsParam
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.InsertOrder", parameterValues: p);

            int newOrderId = 0;
            string orderNo = string.Empty; 
            if (dr.Read())
            {
                newOrderId = dr.GetInt32(0);
                orderNo = dr.GetString(1);
            }

            dr.Close();
            return new SaveOrderResponse() { IsNewOrder=true, OrderDate= DateTime.Now, OrderId= newOrderId,OrderNo =orderNo,UUID = request.UUID};
        }
        catch (Exception)
        {
            throw;
        }
    }

    private async Task UpdateOrderAsync(TransactionContext tc, int orderId, SaveOrderRequest request, CustomerLookupDto customer, PaymentTermLookupDto paymentTerm,
        DiscountLookupDto discount, Dictionary<string, MaterialLookupDto> materialsByCode,
        List<SaveOrderPromotionRequest> promotions, int userId, int role)
    {
        try
        {
            SqlParameter itemsParam = SqlHelperExtension.CreateInParam(pName: "@Items", pType: SqlDbType.Structured, pValue: ToOrderItemTable(request.Items, materialsByCode, (int)request.OrderStatus));
            itemsParam.TypeName = "dbo.OrderItemTableType";

            SqlParameter promotionsParam = SqlHelperExtension.CreateInParam(pName: "@Promotions", pType: SqlDbType.Structured, pValue: ToOrderPromotionTable(promotions));
            promotionsParam.TypeName = "dbo.OrderPromotionTableType";

            SqlParameter[] p =
            [
                SqlHelperExtension.CreateInParam(pName: "@OrderId", pType: SqlDbType.Int, pValue: orderId),
                SqlHelperExtension.CreateInParam(pName: "@ExpectedDeliveryDate", pType: SqlDbType.DateTime, pValue: request.ExpectedDeliveryDate ),
                SqlHelperExtension.CreateInParam(pName: "@PaymentTermCode", pType: SqlDbType.VarChar, pValue: request.PaymentTermCode),
                SqlHelperExtension.CreateInParam(pName: "@DiscountCode", pType: SqlDbType.VarChar, pValue: request.DiscountCode),
                SqlHelperExtension.CreateInParam(pName: "@FOCCode", pType: SqlDbType.VarChar, pValue: request.FocCode),
                SqlHelperExtension.CreateInParam(pName: "@ReferenceTxtNo", pType: SqlDbType.VarChar, pValue: request.ReferenceTxtNo ),
                SqlHelperExtension.CreateInParam(pName: "@GrossValue", pType: SqlDbType.Decimal, pValue: request.GrossValue),
                SqlHelperExtension.CreateInParam(pName: "@DiscountValue", pType: SqlDbType.Decimal, pValue: request.DiscountValue),
                SqlHelperExtension.CreateInParam(pName: "@FOCValue", pType: SqlDbType.Decimal, pValue: request.FocCode),
                SqlHelperExtension.CreateInParam(pName: "@NetValue", pType: SqlDbType.Decimal, pValue: request.NetValue),
                SqlHelperExtension.CreateInParam(pName: "@OrderStatus", pType: SqlDbType.Int, pValue: (int)request.OrderStatus),
                SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: userId),
                itemsParam,
                promotionsParam
            ];

            using IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.UpdateOrder", parameterValues: p);
            dr.Close();
        }
        catch (Exception)
        {
            throw;
        }
    }

    public async Task<PagedResult<GetSalesOrderResponse>> GetSalesOrdersSummary(GetOrderSummaryRequest request, int userId)
    {
        PagedResult<GetSalesOrderResponse?> response = null;
        List<GetSalesOrderResponse> orders = new();
        List<GetSalesOrderItemResponse> orderItems = new();

        try
        {
            using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
            try
            {
                SqlParameter[] p =
                [
                    SqlHelperExtension.CreateInParam(pName: "@SalesOrgCode", pType: SqlDbType.VarChar, pValue:request.SalesOrgCode) ,
                    SqlHelperExtension.CreateInParam(pName: "@EmployeeCode", pType: SqlDbType.VarChar, pValue:request.EmployerCode) ,
                    SqlHelperExtension.CreateInParam(pName: "@OrderNo", pType: SqlDbType.VarChar, pValue: request.OrderNo  ),
                    SqlHelperExtension.CreateInParam(pName: "@UUID", pType: SqlDbType.VarChar, pValue: request.UUID  ),
                    SqlHelperExtension.CreateInParam(pName: "@CustomerCode", pType: SqlDbType.VarChar, pValue: request.CustomerCode  ),
                    SqlHelperExtension.CreateInParam(pName: "@OrderStatus", pType: SqlDbType.Int, pValue: (int?)request.OrderStatus  ),
                    SqlHelperExtension.CreateInParam(pName: "@StartDate", pType: SqlDbType.DateTime, pValue: request.StartDate  ),
                    SqlHelperExtension.CreateInParam(pName: "@EndDate", pType: SqlDbType.DateTime, pValue: request.EndDate  ),
                    SqlHelperExtension.CreateInParam(pName: "@PageNumber", pType: SqlDbType.Int, pValue: request.PageNumber  ),
                    SqlHelperExtension.CreateInParam(pName: "@PageSize", pType: SqlDbType.Int, pValue: request.PageSize  )
                ];

                using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetOrderSummaryPaginated", parameterValues: p))
                {
                    // --- Result set 1: order header ---
                    while (dr.Read())
                    {
                        orders.Add( new GetSalesOrderResponse
                        {
                            OrderId = dr.GetInt32(16),
                            Uuid = dr.GetGuid(0),
                            OrderNo = dr.GetString(1),
                            OrderDate = dr.GetDateTime(2),
                            ExpectedDeliveryDate = dr.IsDBNull(3) ? null : dr.GetDateTime(3),
                            SalesOrgCode = dr.GetString(4),
                            CustomerCode = dr.GetString(5),
                            CustomerName = dr.GetString(6),
                            ReferenceTxtNo = dr.IsDBNull(7) ? null : dr.GetString(7),
                            EmployeeCode = dr.GetString(8),
                            PaymentTermCode = dr.GetString(9),
                            DiscountCode = dr.IsDBNull(10) ? string.Empty : dr.GetString(10),
                            FocCode = dr.IsDBNull(11) ? string.Empty : dr.GetString(11),
                            DiscountValue = dr.GetDecimal(12),
                            GrossValue = dr.GetDecimal(13),
                            NetValue = dr.GetDecimal(14),
                            OrderStatus = dr.GetInt32(15)
                        });
                    }

                    // --- Result set 2: order items ---
                    if (orders.Count >0  && dr.NextResult())
                    {
                        while (dr.Read())
                        {
                            orderItems.Add(new GetSalesOrderItemResponse
                            {
                                OrderId = dr.GetInt32(0),
                                UUID = dr.GetGuid(1),
                                MaterialCode = dr.GetString(2),
                                MaterialName = dr.GetString(3),
                                UnitPrice = dr.GetDecimal(4),
                                SalesUnit = dr.GetString(5),
                                PromoQuantity =dr.IsDBNull(6)? null : dr.GetDecimal(6),
                                OrderQuantity =  dr.IsDBNull(7)? null : dr.GetDecimal(7), 
                                ValidatedQuantity = dr.IsDBNull(8) ? null : dr.GetDecimal(8),
                                ApprovedQuantity = dr.IsDBNull(9) ? null : dr.GetDecimal(9),
                                DiscountPercentage = dr.IsDBNull(10) ? null : dr.GetDecimal(10),
                                DiscountAmount = dr.IsDBNull(11) ? null : dr.GetDecimal(11),
                                IsFocItem = dr.GetInt32(12) > 0,
                                LineTotalValue = dr.GetDecimal(13)
                            });
                        }
                    }

                    dr.Close();
                }
                tc.End();


                foreach (var order in orders)
                {
                    order.Items = orderItems.Where(i => i.OrderId == order.OrderId).ToList();
                }

                response = new PagedResult<GetSalesOrderResponse>
                {
                    Items = orders,
                    TotalCount = orders.Count,
                    PageNumber = request.PageNumber,
                    PageSize = request.PageSize
                };
            }
            catch (Exception ie)
            {
                tc?.HandleError();

                throw DBCustomError.GenerateCustomError(ie);
            }
        }
        catch (Exception ex)
        {
            throw;
        }

        return response;

    }

    public async Task<IList<int>> GetAvailableOrderIdsForSAPAsync(SAPOrderStatusEnum orderStaus, CancellationToken cancellationToken = default)
    {
        List<int> response = new List<int>();

        try
        {
            using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
            try
            {
                SqlParameter[] p =
                [
                    SqlHelperExtension.CreateInParam(pName: "@OrderStatus", pType: SqlDbType.Int, pValue: (int?)orderStaus  ),  
                ];

                using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetAvailableOrderIdsByStatus", parameterValues: p))
                {
                    while (dr.Read())
                    {
                        response.Add(dr.GetInt32(0));
                    }

                    dr.Close();
                }
                tc.End();
            }
            catch (Exception ie)
            {
                tc?.HandleError();

                throw DBCustomError.GenerateCustomError(ie);
            }
        }
        catch (Exception ex)
        {
            throw;
        }

        return response;
    }

    public async Task<GetOrderForSAPResponse?> GetOrderDetailsForSapByIdAsync(int orderId, CancellationToken cancellationToken = default)
    {
        GetOrderForSAPResponse response = null;

        try
        {
            using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode);
            try
            {
                SqlParameter[] p =
                [
                    SqlHelperExtension.CreateInParam(pName: "@OrderId", pType: SqlDbType.Int, pValue: orderId  ),
                ];

                using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetOrderDetailsById", parameterValues: p))
                {
                    while (dr.Read())
                    {
                        response = new GetOrderForSAPResponse()
                        {
                            SalesOrderType = dr.GetString(0),
                            SalesOrganization = dr.GetString(1),
                            DistributionChannel = dr.GetString(2),
                            OrganizationDivision = dr.GetString(3),
                            SoldToParty = dr.GetString(4),
                            PurchaseOrderByCustomer = dr.GetString(5),
                            CustomerPurchaseOrderDate = ConvertOrderDateIntoMilisec( dr.GetDateTime(6)),
                            PurchaseOrderByShipToParty =dr.IsDBNull(7) ? null : dr.GetString(7),
                            CustomerPaymentTerms = dr.IsDBNull(8) ? null : dr.GetString(8),
                            CustomerGroup = dr.GetString(9),
                            CustomerPriceGroup = dr.GetString(10)
                        };
                    }

                    if (response  is not null && dr.NextResult())
                    {
                        while (dr.Read())
                        {
                            response.to_Item.Add(new GetOrderItemForSAPResponse
                            {
                                Material = dr.GetString(0),
                                RequestedQuantity =Convert.ToString( dr.IsDBNull(1)? 0 : dr.GetDecimal(1))
                            });
                        }
                    }

                    dr.Close();
                }

                tc.End();
            }
            catch (Exception ie)
            {
                tc?.HandleError();

                throw DBCustomError.GenerateCustomError(ie);
            }
        }
        catch (Exception ex)
        {
            throw;
        }

        return response;
    }

    private string ConvertOrderDateIntoMilisec(DateTime orderDate)
    {
        long milliseconds = new DateTimeOffset(orderDate).ToUnixTimeMilliseconds();
        string sapDate = $"/Date({milliseconds})/";
        return sapDate;
    }

    public async Task<bool> UpdateSendingSapDetailsAsync(UpdateSapDetailsRequest request,  CancellationToken cancellationToken = default)
    {
        bool response = false;

        try
        {
            using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true);
            try
            {
                SqlParameter[] p =
                [
                    SqlHelperExtension.CreateInParam(pName: "@OrderId", pType: SqlDbType.Int, pValue: request.OrderId  ),
                    SqlHelperExtension.CreateInParam(pName: "@Status", pType: SqlDbType.Int, pValue: request.Status  ),
                    SqlHelperExtension.CreateInParam(pName: "@SentAt", pType: SqlDbType.DateTime, pValue: DateTime.Now  ),
                    SqlHelperExtension.CreateInParam(pName: "@RetryCount", pType: SqlDbType.Int, pValue: request.RetryCount  ),
                    SqlHelperExtension.CreateInParam(pName: "@LastError", pType: SqlDbType.Int, pValue: request.LastError  ),
                    SqlHelperExtension.CreateInParam(pName: "@ReturnMessage", pType: SqlDbType.Int, pValue: request.ReturnMessage  ),
                ];

                await tc.ExecuteNonQuerySpAsync("dbo.UpdateSendingSapDetails", parameterValues: p);

                tc.End();
            }
            catch (Exception ie)
            {
                tc?.HandleError();

                throw DBCustomError.GenerateCustomError(ie);
            }
        }
        catch (Exception ex)
        {
            throw;
        }

        return response;
    }

}
