import { Injectable, HttpException, HttpStatus, Res, OnModuleInit, Inject, forwardRef } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Like, Not, In, ILike } from 'typeorm';
import { responseMessages } from '../../messages/response-messages';
import { CommonRepository } from './common.repository';
// Entities Files
import { Users } from '../../entities/users.entity';
import { FileManagers } from '../../entities/file-managers.entity';
import { Categories } from '../../entities/categories.entity';
import { CategoryTranslation } from '../../entities/category-translations.entity';
import { InfluencerCategory } from '../../entities/influencer-caregories.entity';
import { SocialMedias } from '../../entities/social-medias.entity';
import { FacebookPages } from '../../entities/facebook-pages.entity';
import { YoutubeChannels } from '../../entities/youtube-channel.entity';
import { TikTokProfiles } from '../../entities/tiktok-profile.entity';
import { XiaohongshuProfiles } from '../../entities/xiaohongshu-profile.entity';
import { Keywords } from 'src/entities/keywords.entity';
import { KeywordTranslation } from 'src/entities/keyword-translations.entity';

// DTO Files
import { CommonUploadFileDto } from './dtos/common-upload-file.dto';
import { Countries } from 'src/entities/countries.entity';
import { CurrencyRate } from 'src/entities/currency_rates.entity';
import { NotificationService } from 'src/common/services/notification/notification.service';
import { WhatsAppServices } from 'src/common/services/whatsapp/whatsapp.service';
import { SocialMediaPosts } from 'src/entities/social-media-posts.entity';
import { InstagramMedias } from 'src/entities/instagram-medias.entity';
import { EncryptionService } from 'src/common/services/encryption/encryption.service';
import { UserPermissions } from 'src/entities/user-permissions.entity';
import { json } from 'body-parser';
import { ErrorReport } from 'src/entities/error-report.entity';
import { Constants } from 'src/common/constants';
import { redisClient } from 'src/common/services/redis/redis.provider';
import { env } from 'process';
import axios from 'axios';
import * as XLSX from 'xlsx';
import { PutObjectCommand, S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { CampaignRepository } from '../campaign/campaign.repository';

@Injectable()
export class CommonService implements OnModuleInit {
    private s3: S3Client;
    constructor(
        private commonRepository: CommonRepository,
        private notificationService: NotificationService,
        private whatsappService: WhatsAppServices,
        private encryptionService: EncryptionService,
        @Inject(forwardRef(() => CampaignRepository))
        private campaignRepository: CampaignRepository,
        @InjectRepository(Users)
        private Users: Repository<Users>,

        @InjectRepository(FileManagers)
        private FileManagers: Repository<FileManagers>,

        @InjectRepository(Categories)
        private Categories: Repository<Categories>,

        @InjectRepository(CategoryTranslation)
        private CategoryTranslation: Repository<CategoryTranslation>,

        @InjectRepository(Keywords)
        private Keywords: Repository<Keywords>,

        @InjectRepository(KeywordTranslation)
        private KeywordTranslation: Repository<KeywordTranslation>,

        @InjectRepository(Countries)
        private Countries: Repository<Countries>,

        @InjectRepository(CurrencyRate)
        private CurrencyRate: Repository<CurrencyRate>,

        @InjectRepository(UserPermissions)
        private UserPermissions: Repository<UserPermissions>,

        @InjectRepository(ErrorReport)
        private ErrorReport: Repository<ErrorReport>,

    ) {

    }

    async onModuleInit() {
        const AWS_ACCESS_KEY_ID = await this.encryptionService.decrypt(process.env.AWS_ACCESS_KEY_ID);
        const AWS_SECRET_ACCESS_KEY = await this.encryptionService.decrypt(process.env.AWS_SECRET_ACCESS_KEY);
        this.s3 = new S3Client({
            region: process.env.AWS_DEFAULT_REGION,
            credentials: {
                accessKeyId: AWS_ACCESS_KEY_ID,
                secretAccessKey: AWS_SECRET_ACCESS_KEY,
            },
        });
    }

    async countries(query) {
        const { search } = query;
        const countries = await this.Countries.find({ select: ['id', 'country_name', 'three_digit_code', 'two_digit_code', 'country_code'], order: { order_by: 'ASC' } });
        return { data: countries }
    }

    /**
     * Upload file in AWS Bucket.
     *
     * @param request - An instance of CommonUploadFileDto containing the AWS Bucket directory details.
     * root : Main folder directory name in AWS Bucket.
     * type : Sub folder under to root directory in AWS Bucket.
     * @param response - The response object for sending the HTTP response.
     * @param req - The request object, containing the authenticated user information.
     * Responds with a 201 status if the update is successful.
     * In case of an error, logs the error and responds with the respective error status and message.
    */
    async commonUploadFile(request: CommonUploadFileDto, fileData: any, authUser: any) {
        const allowedMimeTypes = [
            "image/jpeg",
            "image/png",
            "application/pdf",
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            "application/vnd.ms-excel",
            "application/octet-stream",
        ];

        // if (!allowedMimeTypes.includes(fileData.mimetype)) {
        //     throw new HttpException(responseMessages.en.user.file_allowed_exist, HttpStatus.BAD_REQUEST);
        // }

        const IMAGE_MAX = 10 * 1024 * 1024;       // 10 MB
        const OTHER_MAX = 500 * 1024 * 1024;      // 500 MB

        // Some setups don’t set "size"; always fall back to buffer length
        const size = typeof fileData.size === 'number'
            ? fileData.size
            : fileData.buffer?.length ?? 0;

        // Consider both mimetype and file extension (octet-stream fallback)
        const ext = (fileData.originalname.split('.').pop() || '').toLowerCase();
        const isImage = /^image\//.test(fileData.mimetype) || ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(ext);

        const maxBytes = isImage ? IMAGE_MAX : OTHER_MAX;

        if (!size || size <= 0) {
            throw new HttpException('Empty file.', HttpStatus.BAD_REQUEST);
        }
        if (size > maxBytes) {
            throw new HttpException(isImage ? 'Image too large. Max 10MB.' : 'File too large. Max 500MB.', HttpStatus.BAD_REQUEST);
        }

        const parts = fileData.originalname.split('.');

        const nameWithoutExtension = parts.slice(0, -1).join('.').replaceAll(" ", "_");
        const extension = parts.pop();
        const uniqueSuffix = nameWithoutExtension + '_' + Math.round(Math.random() * 1e9);


        const fileName = `${uniqueSuffix}.${extension}`;

        // const filePath = this.STORAGE_FILE_PATH + request.type + '/' + fileName;

        const bucketName = process.env.AWS_BUCKET;
        let key = `${request.root}/${request.type}/${fileName}`;

        const params = {
            Bucket: bucketName,
            Key: key,
            Body: fileData.buffer,
            ContentType: fileData.mimetype,
            // ACL: 'public-read',
        };

        try {
            const data = await this.s3.send(new PutObjectCommand(params));
            if (request.type == 'profile') {
                const filePath = `${process.env.AWS_FILE_PATH}${request.root}/${request.type}/${fileName}`;
                await this.Users.update({ id: authUser.id }, { image: filePath, is_profile_pic_updated: true });
                this.campaignRepository.updateCometChatInfluencerGroup(authUser.id);
            }
            return {
                fileName: fileName,
                // filepath : data.Location,
                filepath: `${process.env.AWS_FILE_PATH}${request.root}/${request.type}/${fileName}`,
                fileSize: fileData.size,
            };
            // console.log('Upload successful:', data);
        } catch (err: any) {
            console.log(err);
            throw new HttpException(err.message, HttpStatus.BAD_REQUEST);
        }
    }

    async uploadFile(params) {
        const data = await this.s3.send(new PutObjectCommand(params));
        return data;
    }

    /**
     * Common function : Request file name and return buffer data of existing file in AWS Bucket
     * @param request - Request of file name.
     * @param response - Return the requested file buffer data.
     * In case of an error, logs the error and responds with the respective error status and message.
    */
    async getAWSFile(key): Promise<Buffer> {
        try {
            const params = {
                Bucket: process.env.AWS_BUCKET,
                Key: key,
            };

            const command = new GetObjectCommand(params);
            const data: any = await this.s3.send(command);
            const chunks: Uint8Array[] = [];
            if (data?.Body) {
                for await (const chunk of data?.Body as any) {
                    chunks.push(chunk);
                }
            }
            return Buffer.concat(chunks);
        } catch (error: any) {
            throw new HttpException(error.message, HttpStatus.BAD_REQUEST);
        }
    }

    async commonUploadProfilePic(request: CommonUploadFileDto, fileData: any, authUser: any) {
        const allowedMimeTypes = [
            "image/jpeg",
            "image/png"
        ];

        if (!allowedMimeTypes.includes(fileData.mimetype)) {
            throw new HttpException(responseMessages.en.user.file_allowed_exist, HttpStatus.BAD_REQUEST);
        }

        const parts = fileData.originalname.split('.');

        const nameWithoutExtension = parts.slice(0, -1).join('.').replaceAll(" ", "_");
        const extension = parts.pop();
        const uniqueSuffix = nameWithoutExtension + '_' + Math.round(Math.random() * 1e9);


        const fileName = `${uniqueSuffix}.${extension}`;

        // const filePath = this.STORAGE_FILE_PATH + request.type + '/' + fileName;

        const bucketName = process.env.AWS_BUCKET;
        let key = `${request.root}/${request.type}/${fileName}`;

        const params = {
            Bucket: bucketName,
            Key: key,
            Body: fileData.buffer,
            ContentType: fileData.mimetype,
            // ACL: 'public-read',
        };

        try {
            const data = await this.s3.send(new PutObjectCommand(params));
            return {
                fileName: fileName,
                // filepath : data.Location,
                filepath: `${process.env.AWS_FILE_PATH}${request.root}/${request.type}/${fileName}`,
                fileSize: fileData.size,
            };
            // console.log('Upload successful:', data);
        } catch (err: any) {
            console.log(err);
            throw new HttpException(err.message, HttpStatus.BAD_REQUEST);
        }
    }

    async convertCurrencyRate(currency: string, rate: number) {
        const conversionRate = await this.CurrencyRate.findOne({ where: { currency } });
        if (conversionRate) {
            return rate / conversionRate.rate;
        }
        return rate;
    }

    async notification(param: any, auth: any) {
        return await this.notificationService.get(param, auth);
    }

    async whatsappMessageList(param: any) {
        return await this.whatsappService.listMessages(param);
    }

    async readNotification(auth: any, query: any) {
        return await this.notificationService.readNotification(auth.id, query);
    }

    async errorReport(body: any, authUser: any) {
        await this.ErrorReport.save({
            platform: body.platform,
            handle: body.handle,
            description: body.description,
            users: { id: authUser.id },
            attachments: body.attachments,
            metadata: {
                user_id: authUser.id,
                user_role: authUser.role,
                user_email: authUser.email,
                user_name: authUser.first_name + ' ' + authUser.last_name,
            }
        });

        return;
    }

    async assignPermissions(page = 1, pageSize = 100) {

        const users = await this.Users.find({ relations: ['subscription', 'subscription.plan', 'subscription.plan.permission'], where: { role: 'brand', is_active: true, subscription: { is_active: true } }, order: { subscription: { created_at: 'DESC' } } });

        if (users) {
            for (const user of users) {
                let userPermissions = [];
                const subscription = user.subscription[0];
                await this.UserPermissions.delete({ users: { id: user.id }, type: In(['export_influencer_time_limit_time_period', 'export_influencer_limit', 'barter_allowed']) });
                for (const permission of subscription.plan.permission) {
                    const assignPermissions = ['export_influencer_limit', 'export_influencer_time_limit', 'barter_allowed'];
                    if (assignPermissions.includes(permission.type)) {
                        userPermissions.push({
                            users: { id: user.id },
                            subscription: { id: subscription.id },
                            type: permission.time_period ? `${permission.type}_time_period` : permission.type,
                            limit: permission.limits,
                            used: 0,
                            remaining: permission.limits,
                            time_period: permission.time_period
                        })
                    }
                }
                console.log(userPermissions);
                await this.UserPermissions.save(userPermissions);
            }
        }
    }
    async sitemap() {
        const count = await this.Users.count({ where: { is_active: true, role: 'influencer' } });
        const pageSize = Constants.SITEMAP_LIMIT;
        const totalPages = Math.ceil(count / pageSize);
        console.log(`Total Influencer: ${count}, Total Pages: ${totalPages}`);
        const baseUrl = process.env.FRONTEND_URL;
        const urls = [];

        for (let i = 1; i <= totalPages; i++) {
            let linkObj = {
                url: `${baseUrl}sitemap/${i}.xml`,
                changefreq: 'weekly',
                priority: 0.8,
                lastmod: new Date().toISOString(),
            };
            urls.push(linkObj);
        }
        urls.push({
            url: `${baseUrl}sitemap/categories.xml`,
            changefreq: 'weekly',
            priority: 0.8,
            lastmod: new Date().toISOString(),
        });
        return urls;
    }
    async sitemapCategories() {
        // Get categories with translations (default to 'en')
        const languageCode = 'en';
        const cacheKey = `${process.env.NODE_ENV}:sitemap:categories`;

        // 1️⃣ Check Redis cache
        const cachedData = await redisClient.get(cacheKey);
        if (cachedData) {
            return JSON.parse(cachedData);
        }


        const categories = await this.Categories.createQueryBuilder("categories")
            .leftJoinAndSelect("categories.translations", "translation", "translation.language_code = :languageCode AND translation.deleted_at IS NULL", { languageCode })
            .where("categories.is_active = :is_active", { is_active: true })
            .andWhere("categories.deleted_at IS NULL")
            .select([
                "categories.id",
                "categories.updated_at",
                "translation.slug"
            ])
            .orderBy("categories.updated_at", "DESC")
            .getMany();

        const baseUrl = process.env.FRONTEND_URL;
        const urls = [];
        for (const category of categories) {
            const translation = category.translations && category.translations.length > 0 ? category.translations[0] : null;
            if (translation && translation.slug) {
                let linkObj = {
                    url: `${baseUrl}influencers/category/${translation.slug}`,
                    changefreq: 'weekly',
                    priority: 0.8,
                    lastmod: category.updated_at.toISOString(),
                };
                urls.push(linkObj);
            }
        }
        // 3️⃣ Store in Redis with 7-day TTL (in seconds)
        await redisClient.setex(cacheKey, 7 * 24 * 60 * 60, JSON.stringify(urls));
        return urls;
    }

    async sitemapPage(page: number) {
        if (page < 1) {
            throw new HttpException('Page number must be greater than 0', HttpStatus.BAD_REQUEST);
        }
        const cacheKey = `${process.env.NODE_ENV}:sitemap:page:${page}`;

        // 1️⃣ Check Redis cache
        const cachedData = await redisClient.get(cacheKey);
        if (cachedData) {
            return JSON.parse(cachedData);
        }

        const pageSize = Constants.SITEMAP_LIMIT;
        const offset = (page - 1) * pageSize;
        const influencers = await this.Users.find({
            where: { is_active: true, role: 'influencer' },
            skip: offset,
            take: pageSize,
            order: { created_at: 'DESC' },
            select: ['id', 'slug', 'updated_at', 'display_name']
        });
        const baseUrl = process.env.FRONTEND_URL;
        const urls = [];

        for (const influencer of influencers) {
            let linkObj = {
                url: `${baseUrl}influencers/${influencer.slug}/${influencer.display_name}`,
                changefreq: 'weekly',
                priority: 0.8,
                lastmod: influencer.updated_at.toISOString(),
            };
            urls.push(linkObj);
        }

        // 3️⃣ Store in Redis with 7-day TTL (in seconds)
        await redisClient.setex(cacheKey, 7 * 24 * 60 * 60, JSON.stringify(urls));
        console.log(`Sitemap page ${page} generated and cached.`);
        return urls;
    }

    async cometChatEmailNotification(userId = null) {
        return true;
        const COMET_CHAT_API_KEY = await this.encryptionService.decrypt(process.env.COMET_CHAT_API_KEY);
        // CREATE COMATE CHAT EMAIL NOTIFICATION
        const users = await this.Users.find({ where: { is_verify_otp: true, id: userId } });
        for (const user of users) {
            const headers = {
                'accept': 'application/json',
                'content-type': 'application/json',
                'apikey': COMET_CHAT_API_KEY,
                'onBehalfOf': user.id
            };
            const obj = {
                email: user.email,
                phno: user.country_code + user.phone_number,
            };
            try {
                await axios.patch(process.env.COMET_CHAT_URL + 'notifications/v1/contact-details', obj, { headers });
            } catch (error: any) {
                console.log(error.response.data);
            }
        }
        return { message: 'Comet chat email notification updated successfully' };
    }

    async generatePresignedUrl(query) {
        const bucketName = process.env.AWS_BUCKET;

        let key = `${query.root}/${query.type}/${query.fileName}`;
        const params = {
            Bucket: bucketName,
            Key: key,
            ContentType: query.contentType,
        };
        try {
            const command = new PutObjectCommand(params);
            const url = await getSignedUrl(this.s3, command, { expiresIn: 60 * 2 }); // 2 minutes
            return { url };
        } catch (err: any) {
            throw new HttpException(err.message, HttpStatus.BAD_REQUEST);
        }
    }

    async importCategoryChineseTranslations(fileData: any) {
        if (!fileData || !fileData.buffer) {
            throw new HttpException('No file uploaded', HttpStatus.BAD_REQUEST);
        }

        const workbook = XLSX.read(fileData.buffer, { type: 'buffer' });
        const firstSheet = workbook.SheetNames[0];
        const sheet = workbook.Sheets[firstSheet];
        const rows: any[] = XLSX.utils.sheet_to_json(sheet, { defval: '' });
        console.log(rows);
        let inserted = 0;
        let updated = 0;
        let skipped = 0;
        let notFound = 0;

        const slugify = (input: string) => {
            if (!input) return '';
            return input.toString().trim().toLowerCase()
                .replace(/\s+/g, '-')
                .replace(/[^\w\-\u4e00-\u9fff]+/g, '-')
                .replace(/--+/g, '-')
                .replace(/^-+|-+$/g, '');
        };

        for (const row of rows) {
            const eng = (row['english_categories'] || row['english_category'] || row['English'] || row['english'] || row['B'])?.toString().trim();
            const zh = (row['chinese_categories'] || row['chinese_category'] || row['Chinese'] || row['C'])?.toString().trim();

            if (!eng) {
                skipped++;
                continue;
            }

            let category = await this.Categories.findOne({ where: { title: eng } });
            if (!category) {
                category = await this.Categories.createQueryBuilder('c')
                    .where('LOWER(c.title) = LOWER(:t)', { t: eng })
                    .getOne();
            }

            if (!category) {
                notFound++;
                continue;
            }

            if (!zh) {
                skipped++;
                continue;
            }

            const existing = await this.CategoryTranslation.findOne({ where: { category: { id: category.id }, language_code: 'hk' } });
            const newSlug = slugify(zh || eng);
            if (existing) {
                existing.title = zh;
                existing.slug = newSlug;
                await this.CategoryTranslation.save(existing);
                updated++;
            } else {
                await this.CategoryTranslation.save({ category: { id: category.id }, language_code: 'hk', title: zh, slug: newSlug });
                inserted++;
            }
        }

        return { inserted, updated, skipped, notFound };
    }

    async importKeywordChineseTranslations(fileData: any) {
        if (!fileData || !fileData.buffer) {
            throw new HttpException('No file uploaded', HttpStatus.BAD_REQUEST);
        }

        const workbook = XLSX.read(fileData.buffer, { type: 'buffer' });
        const firstSheet = workbook.SheetNames[0];
        const sheet = workbook.Sheets[firstSheet];
        const rows: any[] = XLSX.utils.sheet_to_json(sheet, { defval: '' });

        let inserted = 0;
        let updated = 0;
        let skipped = 0;
        let notFound = 0;

        for (const row of rows) {
            const eng = (row['english_keywords'] || row['english_keyword'] || row['English'] || row['english'] || row['B'])?.toString().trim();
            const zh = (row['chinese_keywords'] || row['chinese_keyword'] || row['Chinese'] || row['C'])?.toString().trim();

            if (!eng) {
                skipped++;
                continue;
            }

            let keyword = await this.Keywords.findOne({ where: { keyword: eng } });
            if (!keyword) {
                keyword = await this.Keywords.createQueryBuilder('k')
                    .where('LOWER(k.keyword) = LOWER(:t)', { t: eng })
                    .getOne();
            }

            if (!keyword) {
                notFound++;
                continue;
            }

            if (!zh) {
                skipped++;
                continue;
            }
            console.log('Keyword:', keyword.keyword, '->', zh);
            const existing = await this.KeywordTranslation.findOne({ where: { keywordRef: { id: keyword.id }, language_code: 'hk' } });
            if (existing) {
                existing.keyword = zh;
                await this.KeywordTranslation.save(existing);
                updated++;
            } else {
                await this.KeywordTranslation.save({ keywordRef: { id: keyword.id }, language_code: 'hk', keyword: zh });
                inserted++;
            }
        }

        return { inserted, updated, skipped, notFound };
    }
}
