
const db = require('../models');
const FileDBApi = require('./file');
const Utils = require('../utils');



const Sequelize = db.Sequelize;
const Op = Sequelize.Op;


const hasOwn = (data, key) => Object.prototype.hasOwnProperty.call(data || {}, key);

const firstProvided = (data, keys) => {
  for (const key of keys) {
    if (hasOwn(data, key) && data[key] !== undefined) {
      return data[key];
    }
  }
  return undefined;
};

const buildPlacesPayload = (data = {}, includeDefaults = false) => {
  const payload = {};
  const set = (field, keys, defaultValue) => {
    const value = firstProvided(data, keys);
    if (value !== undefined) {
      payload[field] = value;
    } else if (includeDefaults) {
      payload[field] = defaultValue;
    }
  };

  set('name', ['name', 'nama'], null);
  set('nama', ['nama', 'name'], null);
  set('kategori', ['kategori'], null);
  set('subkategori', ['subkategori'], null);
  set('short_description', ['short_description'], null);
  set('full_description', ['full_description'], null);
  set('address', ['address', 'alamat'], null);
  set('alamat', ['alamat', 'address'], null);
  set('kelurahan', ['kelurahan'], null);
  set('kecamatan', ['kecamatan'], null);
  set('city', ['city', 'kota'], null);
  set('kota', ['kota', 'city'], null);
  set('province', ['province', 'provinsi'], null);
  set('provinsi', ['provinsi', 'province'], null);
  set('postal_code', ['postal_code', 'kode_pos'], null);
  set('kode_pos', ['kode_pos', 'postal_code'], null);
  set('latitude', ['latitude'], null);
  set('longitude', ['longitude'], null);
  set('phone_number', ['phone_number', 'telepon'], null);
  set('telepon', ['telepon', 'phone_number'], null);
  set('whatsapp_number', ['whatsapp_number', 'whatsapp'], null);
  set('whatsapp', ['whatsapp', 'whatsapp_number'], null);
  set('email', ['email'], null);
  set('website_url', ['website_url', 'website'], null);
  set('website', ['website', 'website_url'], null);
  set('google_maps_url', ['google_maps_url'], null);
  set('jam_buka', ['jam_buka'], null);
  set('price_level', ['price_level'], null);
  set('average_price', ['average_price'], null);
  set('rating_average', ['rating_average', 'rating'], null);
  set('rating', ['rating', 'rating_average'], 0);
  set('rating_count', ['rating_count', 'review_count'], null);
  set('review_count', ['review_count', 'rating_count'], 0);
  set('status', ['status'], 'aktif');
  set('is_verified', ['is_verified', 'verified'], false);
  set('verified', ['verified', 'is_verified'], false);
  set('featured', ['featured'], false);
  set('popularitas', ['popularitas'], 0);
  set('created_at', ['created_at'], undefined);
  set('updated_at', ['updated_at'], undefined);
  set('sumber', ['sumber', 'source'], null);
  set('external_id', ['external_id', 'externalId'], null);
  set('external_type', ['external_type', 'externalType'], null);
  set('raw_source_data', ['raw_source_data', 'rawSourceData'], null);
  set('last_synced_at', ['last_synced_at', 'lastSyncedAt'], undefined);

  if (!includeDefaults) {
    return payload;
  }

  Object.keys(payload).forEach((field) => {
    if (payload[field] === undefined) {
      delete payload[field];
    }
  });

  return payload;
};

module.exports = class PlacesDBApi {
    

    
    static async create(data, options) {
        const currentUser = (options && options.currentUser) || { id: null };
        const transaction = (options && options.transaction) || undefined;

        const places = await db.places.create(
            {
                id: data.id || undefined,
        
        name: data.name
        ||
        null
            ,
            
        short_description: data.short_description
        ||
        null
            ,
            
        full_description: data.full_description
        ||
        null
            ,
            
        address: data.address
        ||
        null
            ,
            
        city: data.city
        ||
        null
            ,
            
        province: data.province
        ||
        null
            ,
            
        postal_code: data.postal_code
        ||
        null
            ,
            
        latitude: data.latitude
        ||
        null
            ,
            
        longitude: data.longitude
        ||
        null
            ,
            
        phone_number: data.phone_number
        ||
        null
            ,
            
        whatsapp_number: data.whatsapp_number
        ||
        null
            ,
            
        email: data.email
        ||
        null
            ,
            
        website_url: data.website_url
        ||
        null
            ,
            
        google_maps_url: data.google_maps_url
        ||
        null
            ,
            
        price_level: data.price_level
        ||
        null
            ,
            
        average_price: data.average_price
        ||
        null
            ,
            
        rating_average: data.rating_average
        ||
        null
            ,
            
        rating_count: data.rating_count
        ||
        null
            ,
            
        status: data.status
        ||
        null
            ,
            
        is_verified: data.is_verified
        ||
        false
        
            ,
            
            ...buildPlacesPayload(data, true),
            importHash: data.importHash || null,
            createdById: currentUser.id,
            updatedById: currentUser.id,
    },
        { transaction },
    );

        
        await places.setCategory( data.category || null, {
            transaction,
        });
        
        await places.setOwner( data.owner || null, {
            transaction,
        });
        

        

        
        await FileDBApi.replaceRelationFiles(
            {
                belongsTo: db.places.getTableName(),
                belongsToColumn: 'photos',
                belongsToId: places.id,
            },
            data.photos,
            options,
        );
        

        return places;
    }
    

    static async bulkImport(data, options) {
        const currentUser = (options && options.currentUser) || { id: null };
        const transaction = (options && options.transaction) || undefined;

        // Prepare data - wrapping individual data transformations in a map() method
        const placesData = data.map((item, index) => ({
                id: item.id || undefined,
                
                name: item.name
            ||
            null
            ,
            
                short_description: item.short_description
            ||
            null
            ,
            
                full_description: item.full_description
            ||
            null
            ,
            
                address: item.address
            ||
            null
            ,
            
                city: item.city
            ||
            null
            ,
            
                province: item.province
            ||
            null
            ,
            
                postal_code: item.postal_code
            ||
            null
            ,
            
                latitude: item.latitude
            ||
            null
            ,
            
                longitude: item.longitude
            ||
            null
            ,
            
                phone_number: item.phone_number
            ||
            null
            ,
            
                whatsapp_number: item.whatsapp_number
            ||
            null
            ,
            
                email: item.email
            ||
            null
            ,
            
                website_url: item.website_url
            ||
            null
            ,
            
                google_maps_url: item.google_maps_url
            ||
            null
            ,
            
                price_level: item.price_level
            ||
            null
            ,
            
                average_price: item.average_price
            ||
            null
            ,
            
                rating_average: item.rating_average
            ||
            null
            ,
            
                rating_count: item.rating_count
            ||
            null
            ,
            
                status: item.status
            ||
            null
            ,
            
                is_verified: item.is_verified
            ||
            false
        
            ,
            
            ...buildPlacesPayload(item, true),
            importHash: item.importHash || null,
            createdById: currentUser.id,
            updatedById: currentUser.id,
            createdAt: new Date(Date.now() + index * 1000),
    }));

        // Bulk create items
        const places = await db.places.bulkCreate(placesData, { transaction });

        // For each item created, replace relation files
        
        for (let i = 0; i < places.length; i++) {
            await FileDBApi.replaceRelationFiles(
                {
                    belongsTo: db.places.getTableName(),
                    belongsToColumn: 'photos',
                    belongsToId: places[i].id,
                },
                data[i].photos,
                options,
            );
        }
        

        return places;
    }

    static async update(id, data,  options) {
        const currentUser = (options && options.currentUser) || {id: null};
        const transaction = (options && options.transaction) || undefined;
        

        const places = await db.places.findByPk(id, {}, {transaction});


        

        const updatePayload = {};
        
        if (data.name !== undefined) updatePayload.name = data.name;
        
        
        if (data.short_description !== undefined) updatePayload.short_description = data.short_description;
        
        
        if (data.full_description !== undefined) updatePayload.full_description = data.full_description;
        
        
        if (data.address !== undefined) updatePayload.address = data.address;
        
        
        if (data.city !== undefined) updatePayload.city = data.city;
        
        
        if (data.province !== undefined) updatePayload.province = data.province;
        
        
        if (data.postal_code !== undefined) updatePayload.postal_code = data.postal_code;
        
        
        if (data.latitude !== undefined) updatePayload.latitude = data.latitude;
        
        
        if (data.longitude !== undefined) updatePayload.longitude = data.longitude;
        
        
        if (data.phone_number !== undefined) updatePayload.phone_number = data.phone_number;
        
        
        if (data.whatsapp_number !== undefined) updatePayload.whatsapp_number = data.whatsapp_number;
        
        
        if (data.email !== undefined) updatePayload.email = data.email;
        
        
        if (data.website_url !== undefined) updatePayload.website_url = data.website_url;
        
        
        if (data.google_maps_url !== undefined) updatePayload.google_maps_url = data.google_maps_url;
        
        
        if (data.price_level !== undefined) updatePayload.price_level = data.price_level;
        
        
        if (data.average_price !== undefined) updatePayload.average_price = data.average_price;
        
        
        if (data.rating_average !== undefined) updatePayload.rating_average = data.rating_average;
        
        
        if (data.rating_count !== undefined) updatePayload.rating_count = data.rating_count;
        
        
        if (data.status !== undefined) updatePayload.status = data.status;
        
        
        if (data.is_verified !== undefined) updatePayload.is_verified = data.is_verified;
        
        
        Object.assign(updatePayload, buildPlacesPayload(data, false));
        updatePayload.updated_at = new Date();
        updatePayload.updatedById = currentUser.id;

        await places.update(updatePayload, {transaction});

        
        
        if (data.category !== undefined) {
            await places.setCategory(
              
              data.category,
              
              { transaction }
            );
        }
        
        if (data.owner !== undefined) {
            await places.setOwner(
              
              data.owner,
              
              { transaction }
            );
        }
        

        
        

        
        await FileDBApi.replaceRelationFiles(
            {
                belongsTo: db.places.getTableName(),
                belongsToColumn: 'photos',
                belongsToId: places.id,
            },
            data.photos,
            options,
        );
        

        return places;
    }

    static async deleteByIds(ids, options) {
        const currentUser = (options && options.currentUser) || { id: null };
        const transaction = (options && options.transaction) || undefined;

        const places = await db.places.findAll({
            where: {
                id: {
                    [Op.in]: ids,
                },
            },
            transaction,
        });

        await db.sequelize.transaction(async (transaction) => {
            for (const record of places) {
                await record.update(
                    {deletedBy: currentUser.id},
                    {transaction}
                );
            }
            for (const record of places) {
                await record.destroy({transaction});
            }
        });


        return places;
    }

    static async remove(id, options) {
        const currentUser = (options && options.currentUser) || {id: null};
        const transaction = (options && options.transaction) || undefined;

        const places = await db.places.findByPk(id, options);

        await places.update({
            deletedBy: currentUser.id
        }, {
            transaction,
        });

        await places.destroy({
            transaction
        });

        return places;
    }

    static async findBy(where, options) {
        const transaction = (options && options.transaction) || undefined;

        const places = await db.places.findOne(
            { where },
            { transaction },
        );

        if (!places) {
            return places;
        }

        const output = places.get({plain: true});

        
        
        
        
        
        
        output.place_opening_hours_place = await places.getPlace_opening_hours_place({
            transaction
        });
        
        
        
        output.place_feature_links_place = await places.getPlace_feature_links_place({
            transaction
        });
        
        
        output.reviews_place = await places.getReviews_place({
            transaction
        });
        
        
        output.favorites_place = await places.getFavorites_place({
            transaction
        });
        
        
        
        output.reports_place = await places.getReports_place({
            transaction
        });
        
        
        
        output.category = await places.getCategory({
            transaction
        });
        
        
        output.photos = await places.getPhotos({
            transaction
        });
        
        
        output.owner = await places.getOwner({
            transaction
        });
        
        

        return output;
    }

    static async findAll(
          filter,
           options
        ) {
        const limit = filter.limit || 0;
        let offset = 0;
        let where = {};
        const currentPage = +filter.page;

        

        

        offset = currentPage * limit;


    let include = [

      {
        model: db.place_categories,
        as: 'category',
        
        where: filter.category ? {
          [Op.or]: [
            { id: { [Op.in]: filter.category.split('|').map(term => Utils.uuid(term)) } },
            {
              name: {
                [Op.or]: filter.category.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
              }
            },
          ]
        } : undefined,
        required: Boolean(filter.category),
        
      },

      {
        model: db.users,
        as: 'owner',
        
        where: filter.owner ? {
          [Op.or]: [
            { id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } },
            {
              firstName: {
                [Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
              }
            },
          ]
        } : undefined,
        required: Boolean(filter.owner),
        
      },



      {
        model: db.file,
        as: 'photos',
      },

    ];

        if (filter) {
            if (filter.id) {
                where = {
                    ...where,
                    ['id']: Utils.uuid(filter.id),
                };
            }

            
            if (filter.name) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'name',
                        filter.name,
                    ),
                };
            }
            
            if (filter.short_description) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'short_description',
                        filter.short_description,
                    ),
                };
            }
            
            if (filter.full_description) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'full_description',
                        filter.full_description,
                    ),
                };
            }
            
            if (filter.address) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'address',
                        filter.address,
                    ),
                };
            }
            
            if (filter.city) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'city',
                        filter.city,
                    ),
                };
            }
            
            if (filter.province) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'province',
                        filter.province,
                    ),
                };
            }
            
            if (filter.postal_code) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'postal_code',
                        filter.postal_code,
                    ),
                };
            }
            
            if (filter.phone_number) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'phone_number',
                        filter.phone_number,
                    ),
                };
            }
            
            if (filter.whatsapp_number) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'whatsapp_number',
                        filter.whatsapp_number,
                    ),
                };
            }
            
            if (filter.email) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'email',
                        filter.email,
                    ),
                };
            }
            
            if (filter.website_url) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'website_url',
                        filter.website_url,
                    ),
                };
            }
            
            if (filter.google_maps_url) {
                where = {
                    ...where,
                    [Op.and]: Utils.ilike(
                        'places',
                        'google_maps_url',
                        filter.google_maps_url,
                    ),
                };
            }
            
            const indonesianTextFilters = [
                'nama',
                'kategori',
                'subkategori',
                'alamat',
                'kelurahan',
                'kecamatan',
                'kota',
                'provinsi',
                'kode_pos',
                'telepon',
                'whatsapp',
                'website',
                'jam_buka',
            ];

            for (const field of indonesianTextFilters) {
                if (filter[field]) {
                    where = {
                        ...where,
                        [Op.and]: Utils.ilike(
                            'places',
                            field,
                            filter[field],
                        ),
                    };
                }
            }
            

            
            

            
            if (filter.latitudeRange) {
                const [start, end] = filter.latitudeRange;

                if (start !== undefined && start !== null && start !== '') {
                    where = {
                        ...where,
                    latitude: {
                    ...where.latitude,
                            [Op.gte]: start,
                    },
                };
                }

                if (end !== undefined && end !== null && end !== '') {
                    where = {
                        ...where,
                    latitude: {
                    ...where.latitude,
                            [Op.lte]: end,
                    },
                };
                }
            }
            
            if (filter.longitudeRange) {
                const [start, end] = filter.longitudeRange;

                if (start !== undefined && start !== null && start !== '') {
                    where = {
                        ...where,
                    longitude: {
                    ...where.longitude,
                            [Op.gte]: start,
                    },
                };
                }

                if (end !== undefined && end !== null && end !== '') {
                    where = {
                        ...where,
                    longitude: {
                    ...where.longitude,
                            [Op.lte]: end,
                    },
                };
                }
            }
            
            if (filter.average_priceRange) {
                const [start, end] = filter.average_priceRange;

                if (start !== undefined && start !== null && start !== '') {
                    where = {
                        ...where,
                    average_price: {
                    ...where.average_price,
                            [Op.gte]: start,
                    },
                };
                }

                if (end !== undefined && end !== null && end !== '') {
                    where = {
                        ...where,
                    average_price: {
                    ...where.average_price,
                            [Op.lte]: end,
                    },
                };
                }
            }
            
            if (filter.rating_averageRange) {
                const [start, end] = filter.rating_averageRange;

                if (start !== undefined && start !== null && start !== '') {
                    where = {
                        ...where,
                    rating_average: {
                    ...where.rating_average,
                            [Op.gte]: start,
                    },
                };
                }

                if (end !== undefined && end !== null && end !== '') {
                    where = {
                        ...where,
                    rating_average: {
                    ...where.rating_average,
                            [Op.lte]: end,
                    },
                };
                }
            }
            
            if (filter.rating_countRange) {
                const [start, end] = filter.rating_countRange;

                if (start !== undefined && start !== null && start !== '') {
                    where = {
                        ...where,
                    rating_count: {
                    ...where.rating_count,
                            [Op.gte]: start,
                    },
                };
                }

                if (end !== undefined && end !== null && end !== '') {
                    where = {
                        ...where,
                    rating_count: {
                    ...where.rating_count,
                            [Op.lte]: end,
                    },
                };
                }
            }
            
            const applyRangeFilter = (field, range) => {
                if (!range) {
                    return;
                }

                const [start, end] = range;

                if (start !== undefined && start !== null && start !== '') {
                    where = {
                        ...where,
                        [field]: {
                            ...where[field],
                            [Op.gte]: start,
                        },
                    };
                }

                if (end !== undefined && end !== null && end !== '') {
                    where = {
                        ...where,
                        [field]: {
                            ...where[field],
                            [Op.lte]: end,
                        },
                    };
                }
            };

            applyRangeFilter('rating', filter.ratingRange);
            applyRangeFilter('review_count', filter.review_countRange);
            applyRangeFilter('popularitas', filter.popularitasRange);
            

            if (filter.active !== undefined) {
                where = {
                    ...where,
                    active: filter.active === true || filter.active === 'true'
                };
            }

            
            if (filter.price_level) {
                where = {
                    ...where,
                price_level: filter.price_level,
            };
            }
            
            if (filter.status) {
                where = {
                    ...where,
                status: filter.status,
            };
            }
            
            if (filter.is_verified) {
                where = {
                    ...where,
                is_verified: filter.is_verified,
            };
            }
            
            if (filter.verified !== undefined) {
                where = {
                    ...where,
                    verified: filter.verified === true || filter.verified === 'true',
                };
            }

            if (filter.featured !== undefined) {
                where = {
                    ...where,
                    featured: filter.featured === true || filter.featured === 'true',
                };
            }
            


      

      



            if (filter.createdAtRange) {
                const [start, end] = filter.createdAtRange;

                if (start !== undefined && start !== null && start !== '') {
                    where = {
                        ...where,
                        ['createdAt']: {
                            ...where.createdAt,
                            [Op.gte]: start,
                        },
                    };
                }

                if (end !== undefined && end !== null && end !== '') {
                    where = {
                        ...where,
                        ['createdAt']: {
                            ...where.createdAt,
                            [Op.lte]: end,
                        },
                    };
                }
            }
        }
        

        

        const queryOptions = {
            where,
            include,
            distinct: true,
            order: filter.field && filter.sort
                ? [[filter.field, filter.sort]]
                : [['createdAt', 'desc']],
            transaction: options?.transaction,
            logging: console.log
        };

        if (!options?.countOnly) {
            queryOptions.limit = limit ? Number(limit) : undefined;
            queryOptions.offset = offset ? Number(offset) : undefined;
        }

        try {
            const { rows, count } = await db.places.findAndCountAll(queryOptions);

            return {
                rows: options?.countOnly ? [] : rows,
                count: count
            };
        } catch (error) {
            console.error('Error executing query:', error);
            throw error;
        }
    }

    static async findAllAutocomplete(query, limit, offset, ) {
        let where = {};
        
        

        if (query) {
            where = {
                [Op.or]: [
                    { ['id']: Utils.uuid(query) },
                    Utils.ilike(
                        'places',
                        'name',
                        query,
                    ),
                    Utils.ilike(
                        'places',
                        'nama',
                        query,
                    ),
                ],
            };
        }

        const records = await db.places.findAll({
            attributes: [ 'id', 'name', 'nama' ],
            where,
            limit: limit ? Number(limit) : undefined,
            offset: offset ? Number(offset) : undefined,
            orderBy: [['name', 'ASC']],
        });

        return records.map((record) => ({
            id: record.id,
            label: record.name || record.nama,
        }));
    }

    
};

