Error fetching products: TypeError: data.pagination is undefined
instead of pagination
the json will return a result like this:
{"products":[{"id":6,"name":"‏Anua Heartleaf Pore Clay Pack","description":"يقوم هذا القناع الطيني الكريمي بأمتصاص الزهم الزائد و إزالة الرؤوس السوداء بشكل فعال داخل المسام بسبب احتوائه Houttuynia Cordata الذي يتحكم في الدهون\nيحتوي على الكاولين لامتصاص الدهون\nيحتوي على مادة البنتونيت لتضييق المسام\nيحتوي على كيرسيتينول و هو المكون الرئيسي لأوراق القلب يوفر تهدئة البشرة ويتحكم في الدهون الزائدة لإزالة مشاكل المسام\nيعمل ايضا على إذابة خلايا الجلد الميتة القديمة على الجلد بلطف دون أي تهيج إضافة إلى تنعيم البشرة","brand_id":117,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-23T21:51:27.907Z","updated_at":"2024-09-10T13:58:17.276Z","categories":[{"id":175,"name":"Masks","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":1418,"name":"100 ML","price":22000,"qty":118,"sold":0,"color_id":null,"images":["1048.webp"]}],"images":[{"id":1588,"url":"1048.webp","color_id":null,"size_id":1418}]},{"id":7,"name":"‏Anua Heartleaf Pore Control CleanSsing Oil","description":"غسول منظف زيتي بالهارت ليف للمسامات من انوا\nمنظف زيتي بتركيبة لا تسبب انسداد المسامات ويحتوي على زيوت نباتية (بما في ذلك زيت الجوجوبا وفاكهة الزيتون وبذور العنب)، ويخلص البشرة من الشوائب بشكل فعال مع ترك لمسة نهائية منتعشة ورطبة. مناسب للبشرة المعرضة للحبوب.\nمميزاته وفوائده:\n‏* non-comedogenic تركيبة لا تسد المسامات مناسبة للبشرة الدهنية والمعرضة للحبوب\n* مستخلص هارتليف (houttuynia cordata) معروف جيدًا بتأثيره المضاد للالتهابات والمهدئ للبشرة.\n* مزيل مكياج خفيف يساعد على إزالة الشوائب في المسامات.\n* لا يحتوي على أي مواد مهيجة.\n* يحافظ على رطوبة بشرتك بعد التنظيف.\n* يناسب جميع انواع البشرات حتى الحساسة.","brand_id":117,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-23T21:54:49.385Z","updated_at":"2024-09-23T15:58:27.136Z","categories":[{"id":168,"name":"Face Cleansers","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":1428,"name":"200 ML","price":25000,"qty":100,"sold":0,"color_id":null,"images":["1049.webp"]}],"images":[{"id":24,"url":"1049.webp","color_id":null,"size_id":1428}]},{"id":8,"name":"‏ANUA Heartleaf Quercerinol Pore Deep Cleansing Foam","description":"غسول رغوي بالهارت ليف كيرسيتينول للتنظيف العميق للمسام من انوا \nتم تصميم رغوة التنظيف هذه خصيصا لأنواع البشرة المختلطة والدهنية ، واستهداف الدهون الزائدة وتنظيف المسام بشكل فعال عن طريق إزالة خلايا الجلد الميتة المتراكمة والشوائب\nمميزات وفوائد:\nيحتوي على مسحوق أوراق القلب المطحون ناعما ، مما يساعد على تطهير الأوساخ وخلايا الجلد الميتة من زوايا المسام.\nغني بالمكون المضاد للدهون الحاصل على براءة اختراع ، فهو يحافظ على شعور البشرة بالانتعاش بعد التنظيف ويساعد على معالجة مخاوف الدهون الزائدة.","brand_id":117,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-23T21:59:34.747Z","updated_at":"2024-09-24T10:58:39.386Z","categories":[{"id":168,"name":"Face Cleansers","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":1429,"name":"150 ML","price":25000,"qty":126,"sold":0,"color_id":null,"images":["1050.webp"]}],"images":[{"id":59,"url":"1050.webp","color_id":null,"size_id":1429}]},{"id":9,"name":"‏La Roche Effaclar Astringent Lotion","description":"تونر للبشرة الدهنية و المعرضة للحبوب من la roche \n-يعمل على تقليل حجم المسام بشكل ملحوظ بعد إستخدامه بفضل مكوناته الفعٌالة التي تعمل على تنظيم الإفراز الدهني للوجه و تقشيره بلطف ويقلل من انسداد المسام المسببة لحب الشباب.\n-تلطيف البشرة وموازنة درجة الحموضة بعد خطوة التنظيف مما يعمل معادلة للبشره ولا يسبب جفاف للبشره \n-تنظيف عميق للمسام وإزالة بقايا الجلد الميت من مناطق الجلد التي غالبًا ما تُترك عند غسل وجهك.\n-تساعد المياه المعدنية La Roche-Posay على تهدئة البشرة وحمايتها ومضادات الأكسدة.","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T12:29:54.092Z","updated_at":"2024-09-18T06:42:49.283Z","categories":[{"id":169,"name":"Toners","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":1431,"name":"200 ml","price":25000,"qty":19,"sold":0,"color_id":null,"images":["1052.jpeg"]}],"images":[{"id":3283,"url":"1052.jpeg","color_id":null,"size_id":1431}]},{"id":11,"name":"Aveeno Daily Moisturising Hand Cream","description":"مرطب يد يومي من أفينو\nيتكون من دقيق الشوفان، تعمل هذه التركيبة سريعة الامتصاص على ترطيب البشرة لمدة 24 ساعة، فهو يساعد على حماية وتغذية اليد العادية إلى الجافة. يمنحك بشرة صحية وأكثر مرونة من الاستخدام الأول، لا يحتوي عطور و مناسب لبشرة اليد الحساسة.","brand_id":86,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T12:43:56.173Z","updated_at":"2024-09-10T18:04:29.869Z","categories":[{"id":163,"name":"Hand moisturiser","main_category_id":6,"main_category_name":"Hand Care"}],"colors":[],"sizes":[{"id":1430,"name":"75 ml","price":12000,"qty":10,"sold":0,"color_id":null,"images":["1056.jpeg"]}],"images":[{"id":1600,"url":"1056.jpeg","color_id":null,"size_id":1430}]},{"id":12,"name":"La Roche Posay Thermal Spring Water","description":"‏سبري ملطف ومرطب للبشره الحساسة المتهيجة بسبب العوامل الخارجية (الشمس ووسائل العناية بالبشرة وغيرها...) ويساعد على منع تقدم عمر الجلد. مضاد طبيعي للأكسدة.يمنح المزيج الفريد من المعادن والعناصر النادرة مثل السيلينيوم، ومياه الينابيع الحرارية من لاروش بوزيه خصائص مثبتة علميًا في الترطيب والنعومة ومضاد للأكسدة. تركيبة دقيقة. مناسب للكبار والأطفال.","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T12:50:28.619Z","updated_at":"2024-09-17T18:17:11.779Z","categories":[{"id":193,"name":"Mist","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":899,"name":"150 ml","price":15000,"qty":51,"sold":0,"color_id":null,"images":["1057.jpeg"]}],"images":[{"id":3232,"url":"1057.jpeg","color_id":null,"size_id":899}]},{"id":14,"name":"La Roche Posay Effaclar Medicated Acne Face Wash","description":"غسول منظف لحب الشباب من la roche posay.\nيزيل الزيوت الزائدة ويعمل على إزالة حب الشباب. \nيحتوي على:\n2٪ حمض الساليسيليك: معروف بخصائصه الفعالة في التقشير للمساعدة في تنظيف وتنقية ظهور المسام.\nحمض ليبو هيدروكسي: المعروف أيضًا باسم LHA ، وهو مشتق من حمض الساليسيليك ، وله خصائص تقشير وتجديد البشرة.\nالجلسرين: مشتق من مصادر نباتية ، وهو مرطب ممتاز. يساعد على ترطيب البشرة عن طريق امتصاص الماء من البيئة المحيطة.\nمناسب لجميع انواع البشرة","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T13:02:33.887Z","updated_at":"2024-09-17T18:29:30.996Z","categories":[{"id":168,"name":"Face Cleansers","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":1433,"name":"200 ml","price":30000,"qty":12,"sold":0,"color_id":null,"images":["1062.jpeg"]}],"images":[{"id":3234,"url":"1062.jpeg","color_id":null,"size_id":1433}]},{"id":21,"name":"La Roche Posay Anthelios Anti Shine Fresh Mist Spf50 Facial Sunscreen","description":"واقي شمس سبري شفاف يعطي أقصى حماية للوجه من الأشعة فوق البنفسجية الضارة, بعامل وقايه spf 50 يثبت المكياج و يقاوم المياه, يمتص في ثلاث ثواني و لا يترك علامات بيضاء ومضاد للدهون والتعرق \nيناسب جميع أنواع البشرة والبشرة الحساسة والبشرة التي لا تحتمل التعرض للشمس والمعروفة باسم حساسية الشمس.","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T13:49:25.525Z","updated_at":"2024-09-23T12:12:41.239Z","categories":[{"id":173,"name":"Face Sunscreens","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":586,"name":"75 ml","price":25000,"qty":73,"sold":0,"color_id":null,"images":["1069.jpeg"]}],"images":[{"id":7,"url":"1069.jpeg","color_id":null,"size_id":586}]},{"id":24,"name":"La Roche Posay Cicaplast Balm B5","description":"كريم سيكابلاست من لاروش بوساي\n-تهدئة البشرة المتهيجة خاصة بعد جلسات الديرما بن الديرما رولر  وجلسات ليزر \n-تعمل على الاصلاح  والحماية\nويساعد البشرة على التعافي من (التهيج ،التشقق ، والبقع الخشنه )\nمميزات المنتج :\nيعرف بانه كريم معالج سريع عند حدوث اي شي يسبب في تهيج او احمرار للبشره.. \nمناسب للحروق ولتحسس.","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T13:58:32.728Z","updated_at":"2024-09-17T18:19:42.086Z","categories":[{"id":172,"name":"Face moisturiser","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":587,"name":"40 ml","price":25000,"qty":141,"sold":-6,"color_id":null,"images":["1071.jpeg"]}],"images":[{"id":3233,"url":"1071.jpeg","color_id":null,"size_id":587}]},{"id":25,"name":"La Roche posay Cicaplast levres","description":"بلسم ملطف للشفاه من الجفاف ويحميها ويلطفها\nلعلاج التهاب و تشقق الشفتين الناتج من الجفاف حيث يقوم بترطيب واعادة نمو الطبقة الواقية للشفتين وإزالة الإسمرار واللون الغامق للشفتين\nحيث أنه يعمل على تكوين حاجز بتركيبته الفريدة المحتوية على 5% بانثينول لمنحك إنتعاش سريع، و البروتين الدهني (Mp_lipids) الذي يساعد على ترميم الحاجز الحامي للبشرة\nيمكن استخدامها للاطفال والكبار","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T14:01:07.105Z","updated_at":"2024-09-17T07:47:45.902Z","categories":[{"id":153,"name":"Lip Balm","main_category_id":5,"main_category_name":"Lip Care"}],"colors":[],"sizes":[{"id":588,"name":"7.5 ml","price":15000,"qty":24,"sold":0,"color_id":null,"images":["1072.jpeg"]}],"images":[{"id":2914,"url":"1072.jpeg","color_id":null,"size_id":588}]},{"id":26,"name":"La Roche Posay Pure Vitamin C10 Serum","description":"سيروم فيتامين بتركيزC10 من الفيتامين النقي، محلول مضاد للتجاعيد ، يحتوي على فيتامين C  النقي ، وحمض الساليسيليك والنيوسينسين للكشف عن النضارة الكاملة للبشرة الحساسة .\nيعالج فقدان النضارة ، والبشرة الغير ،مستوية التجاعيد.\nسيروم فريد من نوعه مضاد لعلامات تقدم سن البشرة ومركّز بمكونات عالية من فيتامين C النقي ، مع حمض الساليسيليك والنسوسينسين الملطف ، مع درجة حموضة فسيولوجية ، للكشف عن النضارة الكاملة للبشرة الحساسة.\nعلى الفور، تبدو البشرة أكثر نعومة، نضارة و ترطيب.\nتقل التجاعيد والخطوط الدقيقة. كما لو تحولت، يبدو ملمس البشرة ولونها أكثر تناسقًا و تستعيد البشرة إشراقتها الكاملة.\nيطبق في الصباح على الوجه والعنق. تجنب منطقة العين. يستخدم مع واقي من الشمس يومي.","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T14:04:30.261Z","updated_at":"2024-09-17T18:43:24.198Z","categories":[{"id":170,"name":"Serums","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":1653,"name":"30 ml","price":55000,"qty":23,"sold":0,"color_id":null,"images":["1968.jpeg"]}],"images":[{"id":3241,"url":"1968.jpeg","color_id":null,"size_id":1653}]},{"id":27,"name":"La Roche Posay Hyalu B5 Serum","description":"سيروم الهيالورونيك أسيد وفيتامين B5 من لاروش \nمضاد للتجاعيد، وفقدان الحجم والمرونة، للعناية بالبشرة الضعيفة والمتعبة\n⁣\nيتعلق الأمر بوصفة فريدة لإعادة الحيوية للبشرة تمزج بين نوعين من حمض الهياليرونيك الخالص، وفيتامين B5، ومادة الماديكاسوزيد، لإعادة إشراق البشرة، وإصلاح حاجزها الجلدي، وبالتالي تحسين عملية تجديد ⁣خلايا البشرة\n⁣يحفز انتاج الكولاجين ويحافظ على رطوبة ومرونة البشرة من اول استعمال⁣","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T14:08:42.412Z","updated_at":"2024-09-17T18:33:03.986Z","categories":[{"id":170,"name":"Serums","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":31,"name":"30 ml","price":55000,"qty":32,"sold":0,"color_id":null,"images":["1074.jpeg"]}],"images":[{"id":3236,"url":"1074.jpeg","color_id":null,"size_id":31}]},{"id":28,"name":"La Roche Posay Retinol B3 Serum","description":"سيروم الريتنول من La roche \n-يعمل الريتنول على منع ظهور علامات التقدم بالسن حيث يعمل فيتامين  A على اختراق طبقات الجلد  ويعمل على تحفيز الجلد والكولاجين وبالتالي يقلل من ظهور التجاعيد والخطوط الدقيقه \n-يعمل الريتنول على البقع الداكنه حيث يعمل على تقليل التصبغات والبقع الداكنه وبذلك يعمل على توحيد لون البشره \n-يعمل على ترطيب الجلد ويقلل من تهيج البشره ويقلل من ظهور حب الشباب وكذلك يعمل على زيادة اشراقه البشره","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T14:11:37.469Z","updated_at":"2024-09-23T15:51:16.057Z","categories":[{"id":170,"name":"Serums","main_category_id":1,"main_category_name":"Face Care"}],"colors":[],"sizes":[{"id":1652,"name":"30 ml","price":55000,"qty":1,"sold":0,"color_id":null,"images":["1967.jpeg"]}],"images":[{"id":21,"url":"1967.jpeg","color_id":null,"size_id":1652}]},{"id":31,"name":"La Roche Posay Nutritic Lips","description":"مرطب الشفاه من لاروش \n-مرطب للشفاه حيث يمتاز بفعاليته الكبرى على ترطيب الشفاه وتخليصها من الجفاف والتشققات\n-يوفر مرطب الشفاه التغذيه اللازمه من الفيتامينات والعناصر اللازمه\n-يعطي شفاه جميله ورطبة ومحميه لمده ٢٤ساعه","brand_id":70,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T15:45:00.777Z","updated_at":"2024-09-17T18:44:29.075Z","categories":[{"id":153,"name":"Lip Balm","main_category_id":5,"main_category_name":"Lip Care"}],"colors":[],"sizes":[{"id":585,"name":"4.7","price":15000,"qty":3,"sold":0,"color_id":null,"images":["1084.jpeg"]}],"images":[{"id":3243,"url":"1084.jpeg","color_id":null,"size_id":585}]},{"id":34,"name":"Acm Depiwhite Eye Contour Gel","description":"جل يمتاز بفعاليته لمعالجة مشاكل حول العين من ACM\n-يعمل على التخلص من الهالات السوداء والانتفاخ تحت العين \n-يحتوي على عناصر طبيعية لإزالة البقع الداكنة \n-يقوم بأزالة الخطوط الرفيعه تحت العين التي تسبب مظهر التجاعيد والتقدم بالعمر عن طريق تقويه الشعيرات الدمويه \n- يحتوي على احماض البرتقال والجلسرين وزيت الخروع والمستخلصات الطبيعية","brand_id":73,"has_color":false,"has_size":true,"product_type":"size","price":0,"qty":0,"sold":0,"created_at":"2024-08-24T15:56:35.130Z","updated_at":"2024-09-17T18:37:17.704Z","categories":[{"id":159,"name":"Eye Cream","main_category_id":4,"main_category_name":"Eye Care"}],"colors":[],"sizes":[{"id":790,"name":"15 ml","price":22000,"qty":6,"sold":0,"color_id":null,"images":["1697.jpeg"]}],"images":[{"id":3239,"url":"1697.jpeg","color_id":null,"size_id":790}]}],"total":810,"page":1,"itemsPerPage":15,"totalPages":54,"hasMore":true,"minPrice":0,"maxPrice":250000}
there is no pagination...
import { Request, Response } from 'express';
import {query} from "../config/database";
import {brandsCache, productCache} from "../cache";
import {
    Brand,
    BrandsCacheData,
    CacheData, CreateProductInput,
    Image,
    ImageInput,
    Product, Size,
    UpdateProductInput
} from "product";
import {addProductToCache, deleteProductFromCache, updateProductInCache} from "../cacheHelper";

const fetchCategoryDetails = async (subcategoryIds: number[]) => {
    const categoriesResult = await query(
        `SELECT sc.id AS subcategory_id, sc.name AS subcategory_name,
                c.id AS main_category_id, c.name AS main_category_name
         FROM subcategories sc
         JOIN categories c ON sc.category_id = c.id
         WHERE sc.id = ANY($1::int[])`,
        [subcategoryIds]
    );

    return categoriesResult.rows.map((row: any) => ({
        id: row.subcategory_id,
        name: row.subcategory_name,
        main_category_id: row.main_category_id,
        main_category_name: row.main_category_name
    }));
};
export const loadAndCacheAllProducts = async (): Promise<CacheData> => {
    const q = `
        SELECT 
            p.id,
            p.name,
            p.description,
            p.brand_id,
            p.has_color,
            p.has_size,
            p.product_type,
            p.price,
            p.qty,
            p.sold,
            p.created_at,
            p.updated_at,
            sc.id AS subcategory_id,
            sc.name AS subcategory_name,
            c.id AS main_category_id,
            c.name AS main_category_name,
            s.id AS size_id,
            s.name AS size_name,
            s.price AS size_price,
            s.qty AS size_qty,
            s.sold AS size_sold,
            cl.id AS color_id,
            cl.name AS color_name,
            cl.price AS color_price,
            cl.qty AS color_qty,
            cl.sold AS color_sold,
            i.id AS image_id,
            i.url AS image_url,
            i.color_id AS image_color_id,
            i.size_id AS image_size_id
        FROM products p
        LEFT JOIN product_categories pc ON p.id = pc.product_id
        LEFT JOIN subcategories sc ON pc.category_id = sc.id
        LEFT JOIN categories c ON sc.category_id = c.id
        LEFT JOIN colors cl ON p.id = cl.product_id
        LEFT JOIN sizes s ON p.id = s.product_id
        LEFT JOIN images i ON (p.id = i.product_id AND (cl.id = i.color_id OR i.color_id IS NULL) AND (s.id = i.size_id OR i.size_id IS NULL))
    `;

    try {
        const result = await query(q, []);
        const productsMap: Record<number, Product> = {};

        // Temporary map to hold sizes that need to be assigned to colors
        const sizesMap: Record<number, Size> = {};

        // First pass: Initialize products and collect sizes
        result.rows.forEach((row: any) => {
            const productId = Number(row.id);

            // Initialize the product in the map if it doesn't exist
            if (!productsMap[productId]) {
                productsMap[productId] = {
                    id: productId,
                    name: row.name,
                    description: row.description,
                    brand_id: Number(row.brand_id),
                    has_color: Boolean(row.has_color),
                    has_size: Boolean(row.has_size),
                    product_type: row.product_type,
                    price: Number(row.price),
                    qty: Number(row.qty),
                    sold: Number(row.sold),
                    created_at: new Date(row.created_at).toISOString(),
                    updated_at: new Date(row.updated_at).toISOString(),
                    categories: [],
                    colors: [],
                    sizes: [], // Will be populated only if has_color is false
                    images: []
                };
            }

            const product = productsMap[productId];

            // Add category if present and not already added
            if (
                row.subcategory_id &&
                row.subcategory_name &&
                row.main_category_id &&
                row.main_category_name
            ) {
                const categoryId = Number(row.subcategory_id);
                const mainCategoryId = Number(row.main_category_id);
                const existingCategory = product.categories.find(cat => cat.id === categoryId);
                if (!existingCategory) {
                    product.categories.push({
                        id: categoryId,
                        name: row.subcategory_name,
                        main_category_id: mainCategoryId,
                        main_category_name: row.main_category_name,
                    });
                }
            }

            // Add color if present and not already added
            if (row.color_id && row.color_name && row.has_size !== undefined) {
                const colorId = Number(row.color_id);
                let color = product.colors.find(color => color.id === colorId);

                if (!color) {
                    color = {
                        id: colorId,
                        name: row.color_name,
                        price: Number(row.color_price),
                        qty: Number(row.color_qty),
                        sold: Number(row.color_sold),
                        has_size: Boolean(row.has_size),
                        sizes: [], // Will be populated if has_size is true
                        images: []
                    };
                    product.colors.push(color);
                }
            }

            // Collect sizes to assign later based on color association
            if (row.size_id && row.size_name) {
                const sizeId = Number(row.size_id);
                const colorId = row.color_id ? Number(row.color_id) : null;

                // If the size is associated with a color, store it for later assignment
                if (colorId && product.has_color) {
                    sizesMap[sizeId] = {
                        id: sizeId,
                        name: row.size_name,
                        price: Number(row.size_price),
                        qty: Number(row.size_qty),
                        sold: Number(row.size_sold),
                        color_id: colorId,
                        images: []
                    };
                } else if (!product.has_color && product.has_size) {
                    // If the product has sizes but no colors, assign size directly
                    if (!product.sizes.find(s => s.id === sizeId)) {
                        product.sizes.push({
                            id: sizeId,
                            name: row.size_name,
                            price: Number(row.size_price),
                            qty: Number(row.size_qty),
                            sold: Number(row.size_sold),
                            color_id: null,
                            images: []
                        });
                    }
                }
            }

            // Collect images to assign
            if (row.image_id && row.image_url) {
                const imageId = Number(row.image_id);
                // Avoid duplicate images
                if (!product.images.find(img => img.id === imageId)) {
                    product.images.push({
                        id: imageId,
                        url: row.image_url,
                        color_id: row.image_color_id ? Number(row.image_color_id) : null,
                        size_id: row.image_size_id ? Number(row.image_size_id) : null
                    });
                }

                // Associate image with color if applicable
                if (row.image_color_id) {
                    const colorId = Number(row.image_color_id);
                    const color = product.colors.find(c => c.id === colorId);
                    if (color && !color.images.includes(row.image_url)) {
                        color.images.push(row.image_url);
                    }
                }

                // Associate image with size if applicable and size is within a color
                if (row.image_size_id) {
                    const sizeId = Number(row.image_size_id);
                    const size = sizesMap[sizeId];
                    if (size) {
                        if (!size.images.includes(row.image_url)) {
                            size.images.push(row.image_url);
                        }
                    } else {
                        // If the product does not have colors, associate with product's sizes
                        const standaloneSize = product.sizes.find(s => s.id === Number(row.image_size_id));
                        if (standaloneSize && !standaloneSize.images.includes(row.image_url)) {
                            standaloneSize.images.push(row.image_url);
                        }
                    }
                }
            }
        });

        // Second pass: Assign sizes to their respective colors
        Object.values(productsMap).forEach(product => {
            if (product.has_color) {
                // Initialize all product.sizes as empty
                product.sizes = [];

                // Iterate over collected sizes and assign to colors
                Object.values(sizesMap).forEach(size => {
                    if (size.color_id === null) return; // Skip sizes without color_id

                    const color = product.colors.find(c => c.id === size.color_id);
                    if (color) {
                        color.sizes.push({
                            id: size.id,
                            name: size.name,
                            price: size.price,
                            qty: size.qty,
                            sold: size.sold,
                            color_id: size.color_id,
                            images: size.images
                        });
                    }
                });

                // Update has_size for each color based on whether it has sizes
                product.colors.forEach(color => {
                    color.has_size = color.sizes.length > 0;
                });
            }
            // If has_color is false, sizes are already assigned directly to product.sizes
        });

        const allProducts = Object.values(productsMap);

        // Calculate min and max prices across all products
        let minPrice = Infinity;
        let maxPrice = -Infinity;
        allProducts.forEach(product => {
            // Product price
            if (product.price < minPrice) minPrice = product.price;
            if (product.price > maxPrice) maxPrice = product.price;

            // Prices from colors and their sizes
            product.colors.forEach(color => {
                if (color.price < minPrice) minPrice = color.price;
                if (color.price > maxPrice) maxPrice = color.price;

                color.sizes.forEach(size => {
                    if (size.price < minPrice) minPrice = size.price;
                    if (size.price > maxPrice) maxPrice = size.price;
                });
            });

            // Prices from standalone sizes
            product.sizes.forEach(size => {
                if (size.price < minPrice) minPrice = size.price;
                if (size.price > maxPrice) maxPrice = size.price;
            });
        });

        // Fallback in case no prices are present
        if (minPrice === Infinity) minPrice = 0;
        if (maxPrice === -Infinity) maxPrice = 0;

        // Cache the data with a consistent key
        const cacheData: CacheData = { allProducts, minPrice, maxPrice };
        productCache.set('allProducts', cacheData);
        console.log(`Cached ${allProducts.length} products with minPrice: ${minPrice} and maxPrice: ${maxPrice}.`);

        return cacheData;
    } catch (error) {
        console.error('Error loading products from DB:', error);
        throw error; // Propagate error to be handled by caller
    }
};


/**
 * Fetches all brands from the database and caches them.
 * @returns {Promise<BrandsCacheData>} A promise that resolves to the cached brand data.
 */
const loadAndCacheAllBrands = async (): Promise<BrandsCacheData> => {
    const q = `
        SELECT s.*, c.name as category_name 
        FROM subcategories s 
        JOIN categories c ON s.category_id = c.id AND c.is_brand
        ORDER BY c.name, s.name
    `;

    try {
        const result = await query(q, []);
        const allBrands: Brand[] = result.rows.map((row: any) => ({
            id: Number(row.id),
            name: row.name,
            category_id: Number(row.category_id),
            category_name: row.category_name,
        }));

        // Cache the brands
        const brandsCacheData: BrandsCacheData = { allBrands };
        brandsCache.set('allBrands', brandsCacheData);
        console.log(`Cached ${allBrands.length} brands successfully.`);

        return brandsCacheData;
    } catch (error) {
        console.error('Error loading brands from DB:', error);
        throw error; // Propagate error to be handled by caller
    }
};

/**
 * Controller to handle fetching and responding with filtered products.
 * Caches all products and brands on first request and performs in-memory filtering thereafter.
 * @param {Request} req - Express request object containing query parameters.
 * @param {Response} res - Express response object to send JSON data.
 */
export const getProducts = async (req: Request, res: Response): Promise<void> => {
    try {
        // Retrieve cached products
        let cacheData: CacheData | undefined = productCache.get('cacheData');

        if (!cacheData) {
            // Cache miss: Load from DB and cache
            cacheData = await loadAndCacheAllProducts();
            console.log('Loaded products from database and cached them.');
        } else {
            console.log('Loaded products from cache.');
        }

        const { allProducts, minPrice, maxPrice } = cacheData;

        // Log total products before any filtering
        console.log(`Total products before filtering: ${allProducts.length}`);

        // Extract and sanitize query parameters
        let {
            search = '',
            c,
            sc,
            b,
            minPriceA = '0',
            maxPriceA = '',
            availability = 'all',
            sortBy = '',
            page = '1',
            itemsPerPage = '15',
        } = req.query;

        // Parse numerical values
        const pageNum = parseInt(page as string, 10) || 1;
        const itemsPerPageNum = parseInt(itemsPerPage as string, 10) || 15;
        const minPriceANum = parseFloat(minPriceA as string) || 0;
        let maxPriceANum = maxPriceA ? parseFloat(maxPriceA as string) : Infinity;

        // If maxPriceA is less than minPriceA, reset to Infinity
        if (!isNaN(maxPriceANum) && maxPriceANum < minPriceANum) {
            console.warn('Provided maxPriceA is less than minPriceA. Resetting maxPriceANum to Infinity.');
            maxPriceANum = Infinity;
        } else if (isNaN(maxPriceANum)) {
            maxPriceANum = Infinity;
        }

        // Parse c, sc, b as numbers if present
        const cNum = c ? parseInt(c as string, 10) : undefined;
        const scNum = sc ? parseInt(sc as string, 10) : undefined;
        const bNum = b ? parseInt(b as string, 10) : undefined;

        // Initialize brand-related variables if 'b' is provided
        let allBrands: Brand[] = [];
        if (bNum !== undefined && !isNaN(bNum)) {
            // Retrieve cached brands
            let brandsCacheData: BrandsCacheData | undefined = brandsCache.get('allBrands');

            if (!brandsCacheData) {
                // Cache miss: Load from DB and cache
                brandsCacheData = await loadAndCacheAllBrands();
                console.log('Loaded brands from database and cached them.');
            } else {
                console.log('Loaded brands from cache.');
            }
            console.log(brandsCacheData)
            allBrands = brandsCacheData.allBrands;

            // Check if 'bNum' is a valid brand ID
            const isValidBrand = allBrands.some(brand => brand.category_id === bNum);
            if (!isValidBrand) {
                console.warn(`Brand ID ${bNum} is invalid.`);
                // Return zero products since 'b' is invalid
                res.json({
                    products: [],
                    total: 0,
                    page: pageNum,
                    itemsPerPage: itemsPerPageNum,
                    totalPages: 0,
                    hasMore: false,
                    minPrice,
                    maxPrice,
                });
                return;
            }
        }

        // Start with all products
        let filteredProducts = [...allProducts];

        // Apply Search Filter
        if (search && typeof search === 'string') {
            const searchLower = search.toLowerCase();
            const searchWords = searchLower.split(/\s+/).filter(Boolean);
            filteredProducts = filteredProducts.filter(product =>
                searchWords.every(word => product.name.toLowerCase().includes(word))
            );
        }

        // Apply Category Filter (c)
        if (cNum !== undefined && !isNaN(cNum)) {
            filteredProducts = filteredProducts.filter(product =>
                product.categories.some(cat => cat.main_category_id === cNum)
            );
        }

        // Apply Subcategory Filter (sc)
        if (scNum !== undefined && !isNaN(scNum)) {
            filteredProducts = filteredProducts.filter(product =>
                product.brand_id === scNum ||
                product.categories.some(cat => cat.id === scNum)
            );
        }

        // Apply Brand Filter (b)
        if (bNum !== undefined && !isNaN(bNum)) {
            console.log(`Applying Brand Filter: brand_id === ${bNum}`);
            const preFilterCount = filteredProducts.length;
            allBrands = allBrands.filter(brand => brand.category_id === bNum)
            filteredProducts = filteredProducts.filter(product =>
                allBrands.some(brand => brand.id === product.brand_id)
            );
            console.log(`Filtered from ${preFilterCount} to ${filteredProducts.length} products after applying brand filter.`);
        }

        // Apply Price Range Filter
        if (minPriceANum > 0 || maxPriceANum < Infinity) {
            filteredProducts = filteredProducts.filter(product => {
                const productPrices = product.sizes.map(size => size.price);
                // If product has no sizes, exclude it
                if (productPrices.length === 0) return false;
                const productMinPrice = Math.min(...productPrices);
                const productMaxPrice = Math.max(...productPrices);
                return productMinPrice >= minPriceANum && productMaxPrice <= maxPriceANum;
            });
        }

        // Apply Availability Filter
        if (availability === 'in_stock') {
            filteredProducts = filteredProducts.filter(product =>
                product.sizes.some(size => size.qty > 0)
            );
        } else if (availability === 'out_of_stock') {
            filteredProducts = filteredProducts.filter(product =>
                product.sizes.every(size => size.qty === 0)
            );
        }

        // Log total after filtering
        console.log(`Total products after filtering: ${filteredProducts.length}`);

        // Apply Sorting
        if (sortBy && typeof sortBy === 'string') {
            switch (sortBy) {
                case 'Price: Low to High':
                    filteredProducts.sort((a, b) => {
                        const aMin = Math.min(...a.sizes.map(s => s.price));
                        const bMin = Math.min(...b.sizes.map(s => s.price));
                        return aMin - bMin;
                    });
                    break;
                case 'Price: High to Low':
                    filteredProducts.sort((a, b) => {
                        const aMax = Math.max(...a.sizes.map(s => s.price));
                        const bMax = Math.max(...b.sizes.map(s => s.price));
                        return bMax - aMax;
                    });
                    break;
                case 'Name: A to Z':
                    filteredProducts.sort((a, b) => a.name.localeCompare(b.name));
                    break;
                case 'Name: Z to A':
                    filteredProducts.sort((a, b) => b.name.localeCompare(a.name));
                    break;
                case 'Best Selling':
                    filteredProducts.sort((a, b) => b.sold - a.sold);
                    break;
                case 'Newest Arrivals':
                    filteredProducts.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
                    break;
                default:
                    // If sortBy parameter doesn't match any case, do not sort
                    break;
            }
        }

        // Apply Pagination
        const totalFilteredProducts = filteredProducts.length;
        const totalPages = Math.ceil(totalFilteredProducts / itemsPerPageNum);
        const startIdx = (pageNum - 1) * itemsPerPageNum;
        const endIdx = startIdx + itemsPerPageNum;
        const paginatedProducts = filteredProducts.slice(startIdx, endIdx);

        // Construct Response
        const response = {
            products: paginatedProducts,
            total: totalFilteredProducts,
            page: pageNum,
            itemsPerPage: itemsPerPageNum,
            totalPages,
            hasMore: endIdx < totalFilteredProducts,
            minPrice, // Global minPrice across all products
            maxPrice, // Global maxPrice across all products
        };

        // Send Response
        res.json(response);
    } catch (error) {
        console.error('Error fetching products:', error);
        res.status(500).json({ error: 'Internal server error' });
    } finally {
        // Optional: Any cleanup operations can be performed here
        // For example, monitoring memory usage or logging request completion
    }
};


export const getProductById = async (req: Request, res: Response) => {
    const { id } = req.params;
    try {
        const productQuery = `
            SELECT 
                p.id AS product_id,
                p.name AS product_name,
                p.description,
                p.brand_id,
                p.has_color,
                p.has_size,
                p.product_type,
                p.price AS product_price,
                p.qty AS product_qty,
                p.sold AS product_sold,
                p.created_at,
                p.updated_at,
                pc.category_id,
                sc.name AS category_name,
                cl.id AS color_id,
                cl.name AS color_name,
                cl.price AS color_price,
                cl.qty AS color_qty,
                cl.sold AS color_sold,
                s.id AS size_id,
                s.name AS size_name,
                s.price AS size_price,
                s.qty AS size_qty,
                s.sold AS size_sold,
                i.id AS image_id,
                i.url AS image_url
            FROM products p
            LEFT JOIN product_categories pc ON p.id = pc.product_id
            LEFT JOIN subcategories sc ON pc.category_id = sc.id
            LEFT JOIN colors cl ON p.id = cl.product_id
            LEFT JOIN sizes s ON (p.id = s.product_id AND (cl.id = s.color_id OR s.color_id IS NULL))
            LEFT JOIN images i ON (p.id = i.product_id AND (cl.id = i.color_id OR i.color_id IS NULL) AND (s.id = i.size_id OR i.size_id IS NULL))
            WHERE p.id = $1
            ORDER BY pc.category_id, cl.id, s.id, i.id
        `;

        const result = await query(productQuery, [id]);

        if (result.rows.length === 0) {
            return res.status(404).json({ error: 'Product not found' });
        }

        // Process the result to group related data
        let product: {
            sold: any;
            has_size: any;
            images: any[];
            description: any;
            created_at: any;
            colors: any[];
            brand_id: any;
            has_color: any;
            product_type: any;
            updated_at: any;
            sizes: any[];
            price: any;
            qty: any;
            name: any;
            id: number;
            categories: any[]
        };
        product = {
            id: result.rows[0].product_id,
            name: result.rows[0].product_name,
            description: result.rows[0].description,
            brand_id: result.rows[0].brand_id,
            has_color: result.rows[0].has_color,
            has_size: result.rows[0].has_size,
            product_type: result.rows[0].product_type,
            price: result.rows[0].product_price,
            qty: result.rows[0].product_qty,
            sold: result.rows[0].product_sold,
            created_at: result.rows[0].created_at,
            updated_at: result.rows[0].updated_at,
            categories: [],
            colors: [],
            sizes: [],
            images: []
        };

        for (const row of result.rows) {
            if (row.category_id && !product.categories.some(c => c.id === row.category_id)) {
                product.categories.push({
                    id: row.category_id,
                    name: row.category_name
                });
            }

            // Add color if not already added
            if (row.color_id && !product.colors.some(c => c.id === row.color_id)) {
                product.colors.push({
                    id: row.color_id,
                    name: row.color_name,
                    price: row.color_price,
                    qty: row.color_qty,
                    sold: row.color_sold,
                    images: [],
                    sizes: []
                });
            }

            // Add size if not already added
            if (row.size_id && !product.sizes.some(s => s.id === row.size_id)) {
                const newSize = {
                    id: row.size_id,
                    name: row.size_name,
                    price: row.size_price,
                    qty: row.size_qty,
                    sold: row.size_sold,
                    color_id: row.color_id,
                    // @ts-ignore
                    images: []
                };
                product.sizes.push(newSize);

                // Add size to the corresponding color
                if (row.color_id) {
                    const color = product.colors.find(c => c.id === row.color_id);
                    if (color && !color.sizes.some((s: { id: any; }) => s.id === row.size_id)) {
                        color.sizes.push(newSize);
                    }
                }
            }

            if (row.image_id && !product.images.some(i => i.id === row.image_id)) {
                const newImage = {
                    id: row.image_id,
                    url: row.image_url,
                    color_id: row.color_id,
                    size_id: row.size_id
                };
                product.images.push(newImage);

                if (row.color_id) {
                    const color = product.colors.find(c => c.id === row.color_id);
                    if (color && !color.images.includes(row.image_url)) {
                        color.images.push(row.image_url);
                    }
                }
                if (row.size_id) {
                    const size = product.sizes.find(s => s.id === row.size_id);
                    if (size && !size.images.includes(row.image_url)) {
                        size.images.push(row.image_url);
                    }
                }
            }
        }

        res.json({ product });
    } catch (error) {
        console.error('Error fetching product by ID:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
};
// Create a new product
export const createProduct = async (req: Request, res: Response) => {
    try {
        console.log("Received product data:", req.body); // Log incoming data
        await query('BEGIN');

        const productData: CreateProductInput = req.body;

        // Adjust price if necessary
        let adjustedPrice = productData.price;
        if (productData.price < 1000) {
            adjustedPrice = parseFloat((productData.price * 1000).toFixed(2));
        }

        console.log("Inserting product with adjusted price:", adjustedPrice);

        // Insert product
        const productResult = await query(
            `INSERT INTO products (name, description, brand_id, has_color, has_size, product_type, price, qty, sold, created_at, updated_at)
             VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
             RETURNING id, created_at, updated_at`,
            [
                productData.name,
                productData.description,
                productData.brand_id,
                Boolean(productData.has_color),
                Boolean(productData.has_size),
                productData.product_type,
                adjustedPrice,
                productData.qty
            ]
        );
        const productId = productResult.rows[0].id;
        console.log(`Inserted product ID: ${productId}`);

        // Insert category associations
        const categoryInsertPromises = productData.category_ids.map((categoryId: number) => {
            console.log(`Associating category ID ${categoryId} with product ID ${productId}`);
            return query(
                `INSERT INTO product_categories (product_id, category_id) 
                 VALUES ($1, $2)`,
                [productId, categoryId]
            );
        });
        await Promise.all(categoryInsertPromises);
        console.log('Inserted all category associations.');

        // Insert images
        const imagesInsertPromises = productData.images
            .filter((image: ImageInput) => image.url) // Ensure URL exists
            .map((image: ImageInput) => {
                console.log(`Inserting product image: ${image.url}`);
                return query(
                    `INSERT INTO images (product_id, url, color_id, size_id)
       VALUES ($1, $2, $3, $4)
       RETURNING id, color_id, size_id, url`,
                    [productId, image.url, image.color_id || null, image.size_id || null]
                );
            });
        const insertedImagesResults = await Promise.all(imagesInsertPromises);
        const insertedImages = insertedImagesResults.map((imgResult: any) => ({
            id: imgResult.rows[0].id,
            url: imgResult.rows[0].url,
            color_id: imgResult.rows[0].color_id,
            size_id: imgResult.rows[0].size_id
        }));
        console.log(`Inserted ${insertedImages.length} product images.`);

        // Handle colors and sizes based on product type
        const insertedColors: number[] = [];
        const insertedSizes: number[] = [];

        if (productData.has_color && productData.colors) {
            for (const color of productData.colors) {
                // Adjust color price if necessary
                let adjustedColorPrice = color.price;
                if (color.price < 1000) {
                    adjustedColorPrice = parseFloat((color.price * 1000).toFixed(2));
                }

                console.log(`Inserting color: ${color.name} with has_size: ${color.has_size}`);

                // Insert color with explicit boolean casting
                const colorResult = await query(
                    `INSERT INTO colors (product_id, name, price, qty, sold, has_size)
                     VALUES ($1, $2, $3, $4, 0, $5)
                     RETURNING id, has_size`,
                    [productId, color.name, adjustedColorPrice, color.qty, Boolean(color.has_size)]
                );
                const colorId = colorResult.rows[0].id;
                const colorHasSize = colorResult.rows[0].has_size;
                console.log(`Inserted color ID=${colorId}, has_size=${colorHasSize}`);

                insertedColors.push(colorId);

                // Insert color images
                const colorImagesInsertPromises = color.images.map((image: ImageInput) => {
                    console.log(`Inserting color image: ${image.url}`);
                    return query(
                        `INSERT INTO images (product_id, color_id, url, size_id)
                         VALUES ($1, $2, $3, $4)
                         RETURNING id, color_id, size_id, url`,
                        [productId, colorId, image.url, image.size_id || null]
                    );
                });
                const insertedColorImagesResults = await Promise.all(colorImagesInsertPromises);
                console.log(`Inserted ${insertedColorImagesResults.length} images for color ID=${colorId}`);

                if (color.has_size && color.sizes) {
                    for (const size of color.sizes) {
                        // Adjust size price if necessary
                        let adjustedSizePrice = size.price;
                        if (size.price < 1000) {
                            adjustedSizePrice = parseFloat((size.price * 1000).toFixed(2));
                        }

                        console.log(`Inserting size: ${size.name} with price: ${adjustedSizePrice}`);

                        // Insert size
                        const sizeResult = await query(
                            `INSERT INTO sizes (product_id, color_id, name, price, qty, sold)
                             VALUES ($1, $2, $3, $4, $5, 0)
                             RETURNING id`,
                            [productId, colorId, size.name, adjustedSizePrice, size.qty]
                        );
                        const sizeId = sizeResult.rows[0].id;
                        console.log(`Inserted size ID=${sizeId} for color ID=${colorId}`);
                        insertedSizes.push(sizeId);

                        // Insert size images
                        const sizeImagesInsertPromises = size.images.map((image: ImageInput) => {
                            console.log(`Inserting size image: ${image.url}`);
                            return query(
                                `INSERT INTO images (product_id, color_id, size_id, url)
                                 VALUES ($1, $2, $3, $4)
                                 RETURNING id, color_id, size_id, url`,
                                [productId, colorId, sizeId, image.url]
                            );
                        });
                        const insertedSizeImagesResults = await Promise.all(sizeImagesInsertPromises);
                        console.log(`Inserted ${insertedSizeImagesResults.length} images for size ID=${sizeId}`);
                    }
                }
            }
        } else if (productData.has_size && productData.sizes) {
            for (const size of productData.sizes) {
                // Adjust size price if necessary
                let adjustedSizePrice = size.price;
                if (size.price < 1000) {
                    adjustedSizePrice = parseFloat((size.price * 1000).toFixed(2));
                }

                console.log(`Inserting standalone size: ${size.name} with price: ${adjustedSizePrice}`);

                // Insert size
                const sizeResult = await query(
                    `INSERT INTO sizes (product_id, name, price, qty, sold)
                     VALUES ($1, $2, $3, $4, 0)
                     RETURNING id`,
                    [productId, size.name, adjustedSizePrice, size.qty]
                );
                const sizeId = sizeResult.rows[0].id;
                console.log(`Inserted standalone size ID=${sizeId}`);
                insertedSizes.push(sizeId);

                // Insert size images
                const sizeImagesInsertPromises = size.images.map((image: ImageInput) => {
                    console.log(`Inserting standalone size image: ${image.url}`);
                    return query(
                        `INSERT INTO images (product_id, size_id, url)
                         VALUES ($1, $2, $3)
                         RETURNING id, color_id, size_id, url`,
                        [productId, sizeId, image.url]
                    );
                });
                const insertedSizeImagesResults = await Promise.all(sizeImagesInsertPromises);
                console.log(`Inserted ${insertedSizeImagesResults.length} images for standalone size ID=${sizeId}`);
            }
        }

        // Construct the new product object as it would appear in the cache
        const newProduct: Product = {
            id: productId,
            name: productData.name,
            description: productData.description,
            brand_id: productData.brand_id,
            has_color: productData.has_color,
            has_size: productData.has_size,
            product_type: productData.product_type,
            price: adjustedPrice,
            qty: productData.qty,
            sold: 0,
            created_at: productResult.rows[0].created_at.toISOString(),
            updated_at: productResult.rows[0].updated_at.toISOString(),
            categories: productData.category_ids.map((cid: number) => ({
                id: cid,
                name: "", // Optional: Fetch and populate category names
                main_category_id: 0, // Optional: Set appropriately
                main_category_name: "" // Optional: Set appropriately
            })),
            colors: productData.has_color && productData.colors ? productData.colors.map((color, index) => ({
                id: insertedColors[index],
                name: color.name,
                price: color.price || 0,
                qty: color.qty,
                sold: 0,
                has_size: Boolean(color.has_size),
                sizes: Boolean(color.has_size) && color.sizes ? color.sizes.map((size, sIndex) => ({
                    id: insertedSizes[sIndex],
                    name: size.name,
                    price: size.price,
                    qty: size.qty,
                    sold: 0,
                    color_id: null, // Optional: Set if needed
                    images: size.images.map(img => img.url)
                })) : [],
                images: color.images.map(img => img.url)
            })) : [],
            sizes: productData.has_size && !productData.has_color && productData.sizes ? productData.sizes.map((size, index) => ({
                id: insertedSizes[index],
                name: size.name,
                price: size.price,
                qty: size.qty,
                sold: 0,
                color_id: null,
                images: size.images.map(img => img.url)
            })) : [],
            images: insertedImages
        };

        console.log("New product to cache:", newProduct); // Log new product

        // Update the cache
        addProductToCache(newProduct);

        await query('COMMIT');
        res.status(201).json({ message: 'Product added successfully!', productId });
    } catch (error) {
        await query('ROLLBACK');
        console.error('Error creating product:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
};
/**
 * Update an existing product
 */
// src/controllers/productController.ts

// src/controllers/productController.ts

export const updateProduct = async (req: Request, res: Response) => {
    const { id } = req.params;
    const productData: UpdateProductInput = req.body;
    const productId: number = parseInt(id, 10);

    if (isNaN(productId)) {
        return res.status(400).json({ error: 'Invalid product ID' });
    }

    try {
        await query('BEGIN');

        // Adjust product price if necessary
        let adjustedProductPrice: number = productData.price;
        if (adjustedProductPrice < 1000) {
            adjustedProductPrice = parseFloat((adjustedProductPrice * 1000).toFixed(2));
        }

        // Update product details
        const result = await query(
            `
            UPDATE products 
            SET 
                name = $1, 
                description = $2, 
                brand_id = $3, 
                has_color = $4, 
                has_size = $5, 
                product_type = $6, 
                price = $7, 
                qty = $8, 
                updated_at = CURRENT_TIMESTAMP 
            WHERE id = $9 
            RETURNING *
            `,
            [
                productData.name,
                productData.description,
                productData.brand_id,
                productData.has_color,
                productData.has_size,
                productData.product_type,
                adjustedProductPrice,
                productData.qty,
                productId
            ]
        );

        if (result.rows.length === 0) {
            await query('ROLLBACK');
            return res.status(404).json({ error: 'Product not found' });
        }

        // Delete existing category associations
        await query('DELETE FROM product_categories WHERE product_id = $1', [productId]);

        // Determine if images are provided in the update
        const hasMainImagesInUpdate = productData.images !== undefined && productData.images !== null;

        // Handle Images
        let insertedImages: Image[] = [];

        if (hasMainImagesInUpdate) {
            // Delete existing main images
            await query('DELETE FROM images WHERE product_id = $1 AND color_id IS NULL AND size_id IS NULL', [productId]);

            // Insert new main images if provided
            if (productData.images.length > 0) {
                const imagesInsertPromises = productData.images.map((image: ImageInput) =>
                    query(
                        `INSERT INTO images (product_id, url, color_id, size_id)
                         VALUES ($1, $2, $3, $4)
                         RETURNING id, url, color_id, size_id`,
                        [productId, image.url, image.color_id || null, image.size_id || null]
                    )
                );

                const insertedImagesResults = await Promise.all(imagesInsertPromises);
                insertedImages = insertedImagesResults.map((imgResult: any) => ({
                    id: imgResult.rows[0].id,
                    url: imgResult.rows[0].url,
                    color_id: imgResult.rows[0].color_id,
                    size_id: imgResult.rows[0].size_id
                }));
            }
        } else {
            // If images are not provided, fetch existing main images
            const existingImagesResult = await query(
                `SELECT id, url, color_id, size_id FROM images 
                 WHERE product_id = $1 AND color_id IS NULL AND size_id IS NULL`,
                [productId]
            );

            insertedImages = existingImagesResult.rows.map((row: any) => ({
                id: row.id,
                url: row.url,
                color_id: row.color_id,
                size_id: row.size_id
            }));
        }

        // Insert category associations
        if (productData.category_ids.length > 0) {
            const categoryInsertPromises = productData.category_ids.map((categoryId: number) =>
                query(
                    `INSERT INTO product_categories (product_id, category_id) 
                     VALUES ($1, $2)`,
                    [productId, categoryId]
                )
            );
            await Promise.all(categoryInsertPromises);
        }

        // Insert or Update Colors and Sizes
        let insertedColors: number[] = [];
        let insertedSizes: number[] = [];

        if (productData.has_color && productData.colors && productData.colors.length > 0) {
            for (const colorInput of productData.colors) {
                // Adjust color price if necessary
                let adjustedColorPrice: number = colorInput.price;
                if (adjustedColorPrice < 1000) {
                    adjustedColorPrice = parseFloat((adjustedColorPrice * 1000).toFixed(2));
                }

                // Insert or Update color
                let colorId: number;
                if (colorInput.id) {
                    // Update existing color
                    await query(
                        `
                        UPDATE colors 
                        SET 
                            name = $1, 
                            price = $2, 
                            qty = $3, 
                            sold = $4, 
                            has_size = $5 
                        WHERE id = $6
                        `,
                        [
                            colorInput.name,
                            adjustedColorPrice,
                            colorInput.qty,
                            colorInput.sold || 0,
                            colorInput.has_size,
                            colorInput.id
                        ]
                    );
                    colorId = colorInput.id;
                } else {
                    // Insert new color
                    const colorResult = await query(
                        `
                        INSERT INTO colors 
                            (product_id, name, price, qty, sold, has_size)
                        VALUES 
                            ($1, $2, $3, $4, 0, $5)
                        RETURNING id
                        `,
                        [productId, colorInput.name, adjustedColorPrice, colorInput.qty, colorInput.has_size]
                    );
                    colorId = colorResult.rows[0].id;
                    insertedColors.push(colorId);
                }

                // Handle Color Images
                const hasColorImagesInUpdate = colorInput.images !== undefined && colorInput.images !== null;

                if (hasColorImagesInUpdate) {
                    // Delete existing images for this color
                    await query('DELETE FROM images WHERE product_id = $1 AND color_id = $2 AND size_id IS NULL', [productId, colorId]);

                    // Insert new images if provided
                    if (colorInput.images.length > 0) {
                        const colorImagesInsertPromises = colorInput.images.map((image: ImageInput) =>
                            query(
                                `INSERT INTO images (product_id, color_id, url, size_id)
                                 VALUES ($1, $2, $3, $4)
                                 RETURNING id, url, color_id, size_id`,
                                [productId, colorId, image.url, image.size_id || null]
                            )
                        );
                        await Promise.all(colorImagesInsertPromises);
// Optionally, store or handle color images if needed
                    }
                } else {
                    // Fetch existing color images
                    await query(
                        `SELECT id, url, color_id, size_id FROM images 
                         WHERE product_id = $1 AND color_id = $2 AND size_id IS NULL`,
                        [productId, colorId]
                    );
                }

                // Handle Sizes within Color
                if (colorInput.has_size && colorInput.sizes && colorInput.sizes.length > 0) {
                    for (const sizeInput of colorInput.sizes) {
                        // Adjust size price if necessary
                        let adjustedSizePrice: number = sizeInput.price;
                        if (adjustedSizePrice < 1000) {
                            adjustedSizePrice = parseFloat((adjustedSizePrice * 1000).toFixed(2));
                        }

                        // Insert or Update size
                        let sizeId: number;
                        if (sizeInput.id) {
                            // Update existing size
                            await query(
                                `
                                UPDATE sizes 
                                SET 
                                    name = $1, 
                                    price = $2, 
                                    qty = $3, 
                                    sold = $4, 
                                    color_id = $5 
                                WHERE id = $6
                                `,
                                [
                                    sizeInput.name,
                                    adjustedSizePrice,
                                    sizeInput.qty,
                                    sizeInput.sold || 0,
                                    colorId,
                                    sizeInput.id
                                ]
                            );
                            sizeId = sizeInput.id;
                        } else {
                            // Insert new size
                            const sizeResult = await query(
                                `
                                INSERT INTO sizes 
                                    (product_id, color_id, name, price, qty, sold)
                                VALUES 
                                    ($1, $2, $3, $4, $5, 0)
                                RETURNING id
                                `,
                                [productId, colorId, sizeInput.name, adjustedSizePrice, sizeInput.qty]
                            );
                            sizeId = sizeResult.rows[0].id;
                            insertedSizes.push(sizeId);
                        }

                        // Handle Size Images
                        const hasSizeImagesInUpdate = sizeInput.images !== undefined && sizeInput.images !== null;

                        if (hasSizeImagesInUpdate) {
                            // Delete existing images for this size
                            await query('DELETE FROM images WHERE product_id = $1 AND size_id = $2', [productId, sizeId]);

                            // Insert new images if provided
                            if (sizeInput.images.length > 0) {
                                const sizeImagesInsertPromises = sizeInput.images.map((image: ImageInput) =>
                                    query(
                                        `INSERT INTO images (product_id, size_id, url)
                                         VALUES ($1, $2, $3)
                                         RETURNING id, url, color_id, size_id`,
                                        [productId, sizeId, image.url]
                                    )
                                );
                                await Promise.all(sizeImagesInsertPromises);
// Optionally, store or handle size images if needed
                            }
                        } else {
                            // Fetch existing size images
                            await query(
                                `SELECT id, url, color_id, size_id FROM images 
                                 WHERE product_id = $1 AND size_id = $2`,
                                [productId, sizeId]
                            );
                        }
                    }
                }
            }
        } else if (productData.has_size && productData.sizes && productData.sizes.length > 0) {
            for (const sizeInput of productData.sizes) {
                // Adjust size price if necessary
                let adjustedSizePrice: number = sizeInput.price;
                if (adjustedSizePrice < 1000) {
                    adjustedSizePrice = parseFloat((adjustedSizePrice * 1000).toFixed(2));
                }

                // Insert or Update size
                let sizeId: number;
                if (sizeInput.id) {
                    // Update existing size
                    await query(
                        `
                        UPDATE sizes 
                        SET 
                            name = $1, 
                            price = $2, 
                            qty = $3, 
                            sold = $4 
                        WHERE id = $5
                        `,
                        [
                            sizeInput.name,
                            adjustedSizePrice,
                            sizeInput.qty,
                            sizeInput.sold || 0,
                            sizeInput.id
                        ]
                    );
                    sizeId = sizeInput.id;
                } else {
                    // Insert new size
                    const sizeResult = await query(
                        `
                        INSERT INTO sizes 
                            (product_id, name, price, qty, sold)
                        VALUES 
                            ($1, $2, $3, $4, 0)
                        RETURNING id
                        `,
                        [productId, sizeInput.name, adjustedSizePrice, sizeInput.qty]
                    );
                    sizeId = sizeResult.rows[0].id;
                    insertedSizes.push(sizeId);
                }

                // Handle Size Images
                const hasSizeImagesInUpdate = sizeInput.images !== undefined && sizeInput.images !== null;

                if (hasSizeImagesInUpdate) {
                    // Delete existing images for this size
                    await query('DELETE FROM images WHERE product_id = $1 AND size_id = $2', [productId, sizeId]);

                    // Insert new images if provided
                    if (sizeInput.images.length > 0) {
                        const sizeImagesInsertPromises = sizeInput.images.map((image: ImageInput) =>
                            query(
                                `INSERT INTO images (product_id, size_id, url)
                                 VALUES ($1, $2, $3)
                                 RETURNING id, url, color_id, size_id`,
                                [productId, sizeId, image.url]
                            )
                        );
                        await Promise.all(sizeImagesInsertPromises);
// Optionally, store or handle size images if needed
                    }
                } else {
                    // Fetch existing size images

                }
            }
        }

        // Fetch and populate categories with details
        const categories = await fetchCategoryDetails(productData.category_ids);

        // Fetch existing images if not updated
        if (!hasMainImagesInUpdate) {
            const existingImagesResult = await query(
                `SELECT id, url, color_id, size_id FROM images 
                 WHERE product_id = $1 AND color_id IS NULL AND size_id IS NULL`,
                [productId]
            );

            insertedImages = existingImagesResult.rows.map((row: any) => ({
                id: row.id,
                url: row.url,
                color_id: row.color_id,
                size_id: row.size_id
            }));
        }

        // Construct the updated product object as it would appear in the cache
        const updatedProduct: Product = {
            id: productId,
            name: productData.name,
            description: productData.description,
            brand_id: productData.brand_id,
            has_color: productData.has_color,
            has_size: productData.has_size,
            product_type: productData.product_type,
            price: adjustedProductPrice,
            qty: productData.qty,
            sold: 0, // Assuming sold remains unchanged; adjust if necessary
            created_at: result.rows[0].created_at.toISOString(),
            updated_at: new Date().toISOString(),
            categories: categories,
            colors: productData.has_color && productData.colors
                ? productData.colors.map((colorInput, index) => ({
                    id: colorInput.id || insertedColors[index],
                    name: colorInput.name,
                    price: colorInput.price || 0,
                    qty: colorInput.qty,
                    sold: colorInput.sold || 0,
                    has_size: colorInput.has_size,
                    sizes: colorInput.has_size && colorInput.sizes
                        ? colorInput.sizes.map((sizeInput, sIndex) => ({
                            id: sizeInput.id || insertedSizes[sIndex],
                            name: sizeInput.name,
                            price: sizeInput.price,
                            qty: sizeInput.qty,
                            sold: sizeInput.sold || 0,
                            color_id: colorInput.id || insertedColors[index],
                            images: sizeInput.images ? sizeInput.images.map(img => img.url) : []
                        }))
                        : [],
                    images: colorInput.images ? colorInput.images.map(img => img.url) : []
                }))
                : [],
            sizes: productData.has_size && !productData.has_color && productData.sizes
                ? productData.sizes.map((sizeInput, index) => ({
                    id: sizeInput.id || insertedSizes[index],
                    name: sizeInput.name,
                    price: sizeInput.price,
                    qty: sizeInput.qty,
                    sold: sizeInput.sold || 0,
                    color_id: null,
                    images: sizeInput.images ? sizeInput.images.map(img => img.url) : []
                }))
                : [],
            images: insertedImages
        };

        // Update the cache
        updateProductInCache(updatedProduct);

        await query('COMMIT');
        res.json({ message: 'Product updated successfully', product: updatedProduct });
    } catch (error) {
        await query('ROLLBACK');
        console.error('Error updating product:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
};

export const deleteProduct = async (req: Request, res: Response) => {
    const { id } = req.params;
    const productId: number = parseInt(id, 10);

    if (isNaN(productId)) {
        return res.status(400).json({ error: 'Invalid product ID' });
    }

    try {
        await query('BEGIN');

        // Delete related data first
        await query('DELETE FROM product_categories WHERE product_id = $1', [productId]);
        await query('DELETE FROM images WHERE product_id = $1', [productId]);
        await query('DELETE FROM sizes WHERE product_id = $1', [productId]);
        await query('DELETE FROM colors WHERE product_id = $1', [productId]);

        // Delete the product
        const result = await query('DELETE FROM products WHERE id = $1 RETURNING *', [productId]);

        if (result.rows.length === 0) {
            await query('ROLLBACK');
            return res.status(404).json({ error: 'Product not found' });
        }

        // Delete the product from the cache
        deleteProductFromCache(productId);

        await query('COMMIT');
        res.json({ message: 'Product and related data deleted successfully' });
    } catch (error) {
        await query('ROLLBACK');
        console.error('Error deleting product:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
};