import { Injectable, NotFoundException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as moment from 'moment';
import { ModelsService } from 'src/models/models.service';
import * as fcm_admin from "firebase-admin";
import * as path from 'path';
import * as fs from 'fs';
import * as apn from "node-apn-flitto";
import { ConfigService } from '@nestjs/config';
import { v4 as uuidv4 } from 'uuid';
import { Types } from 'mongoose';
import * as crypto from 'crypto';
import { I18nType } from './enums/i18n-type.enum';
// import { WalletService } from './services/wallet.service';

let apn_server = process.env.APN_SERVER;
let apn_production = false;
if (apn_server == "true") {
    apn_production = true;
}
@Injectable()
export class CommonService {
    private apnProvider: apn.Provider | null;
    private userTopic: string;
    private vendorTopic: string;

    constructor(
        private readonly JwtService: JwtService,
        private readonly model: ModelsService,
        private readonly configServices: ConfigService,
        // private readonly walletService: WalletService
    ) {
        if (!fcm_admin.apps.length) {
            // Try multiple paths to find the Firebase config file
            let file_path: string;
            const relativePath = 'public/firebase/help-module-firebase-adminsdk-fbsvc-58afefdf4f.json';
            
            // Check if running from dist (production)
            if (__dirname.includes('/dist/')) {
                file_path = path.resolve(__dirname, `../../${relativePath}`);
            } else {
                // Running from src (development)
                file_path = path.resolve(__dirname, `../${relativePath}`);
            }

            let parse_json;
            
            try {
                if (fs.existsSync(file_path)) {
                    const file = fs.readFileSync(file_path, 'utf8');
                    parse_json = JSON.parse(file);
                } else {
                    console.warn('Firebase config file not found at:', file_path);
                    console.warn('Firebase notifications will not work until the config file is provided');
                }
            } catch (fileError) {
                console.error('Error reading Firebase config file:', fileError.message);
            }

            if (parse_json) {
                fcm_admin.initializeApp({
                    credential: fcm_admin.credential.cert(parse_json)
                });
            }
        }

        this.userTopic = this.configServices.get<string>("APNS_TOPIC_USER") ?? "" // The Key ID of the .p8 file
        this.vendorTopic = this.configServices.get<string>("APNS_TOPIC_VENDOR") ?? "" // Vendor APNS topic

        // Setup APNS provider with error handling
        try {
            // Determine APNS key file path based on environment
            let apnsKeyPath: string;
            const apnsRelativePath = 'public/APNS/AuthKey_PZJJCHPDZT.p8';
            
            if (__dirname.includes('/dist/')) {
                apnsKeyPath = path.resolve(__dirname, `../../${apnsRelativePath}`);
            } else {
                apnsKeyPath = path.resolve(__dirname, `../${apnsRelativePath}`);
            }

            // Only initialize APNS if the key file exists
            if (fs.existsSync(apnsKeyPath)) {
                this.apnProvider = new apn.Provider({
                    token: {
                        key: apnsKeyPath,
                        keyId: this.configServices.get<string>("APN_KEY_ID") ?? "",
                        teamId: this.configServices.get<string>("APN_TEAM_ID") ?? "",
                    },
                    production: apn_production,
                    sandbox: false,
                });
                console.log('APNS provider initialized successfully');
            } else {
                console.warn('APNS key file not found at:', apnsKeyPath);
                console.warn('Apple Push Notifications will not work until the key file is provided');
                this.apnProvider = null;
            }
        } catch (apnsError) {
            console.error('Error initializing APNS provider:', apnsError.message);
            this.apnProvider = null;
        }
    }

    /**
     * Get user language from request or fallback
     * Priority: req.lang > userLanguage param > req.user_data.language > 'en'
     * @param req - Express request object
     * @param userLanguage - Optional user language from DTO or user object
     * @returns Language code (default: 'en')
     */
    getUserLanguage(req?: any, userLanguage?: string): string {
        if (req?.lang) return req.lang;
        if (userLanguage) return userLanguage;
        if (req?.user_data?.language) return req.user_data.language;
        return 'en';
    }

    async generate_token(token_data: any) {
        try {
            let token = await this.JwtService.signAsync(token_data, { expiresIn: "30d" })
            return token;
        }
        catch (error) {
            throw error;
        }
    }

    generateOTP(length: number = 4): string {
        try {
            const min = Math.pow(10, length - 1);
            const max = Math.pow(10, length) - 1;
            const otp = Math.floor(min + Math.random() * (max - min + 1)).toString();
            // return otp;
            return "1234"
        } catch (error) {
            throw error;
        }
    }

    isOTPExpired(expiresAt: number): boolean {
        try {
            return +new Date() > expiresAt;
        } catch (error) {
            throw error;
        }
    }

    getOTPExpiryTime(minutes: number = 5): number {
        try {
            return +new Date() + (minutes * 60 * 1000);
        } catch (error) {
            throw error;
        }
    }

    generateReferralCode(length: number = 8): string {
        try {
            const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
            let result = '';
            for (let i = 0; i < length; i++) {
                result += chars.charAt(Math.floor(Math.random() * chars.length));
            }
            return result;
        } catch (error) {
            throw error;
        }
    }

    async verify_token(token: any) {
        try {
            let payload = await this.JwtService.verify(token)
            return payload;
        }
        catch (error) {
            throw error;
        }
    }

    async decode_token(token: any) {
        try {
            let payload = await this.JwtService.decode(token)
            return payload;
        }
        catch (error) {
            throw error;
        }
    }

    decodeTokenFromRequest(req: any): any {
        try {
            const authHeader = req.headers.authorization;
            let token: string | undefined;

            if (authHeader && authHeader.startsWith('Bearer ')) {
                token = authHeader.split(' ')[1];
            }else if (req.query.token){
                token = req.query.token
            }else if (req.body.token){
                token = req.body.token
            }
            
            if (!token) {
                return null;
            }
            
            const decoded = this.JwtService.decode(token);
            return decoded;
        } catch (error) {
            return null;
        }
    }

    async create_user_session(body: any, token_data: any, access_token: string) {
        try {
            let { fcm_token, voip_token, device_type } = body;
            let { _id: user_id, token_gen_at } = token_data;
            
            // Remove previous sessions for this user
            await this.model.SessionModel.deleteMany({ user_id: user_id });
            
            let data_to_save: any = {
                user_id: user_id,
                type: 'USER',
                token_gen_at: token_gen_at,
                device_type: device_type ?? "WEB",
                access_token: access_token,
                fcm_token: fcm_token,
                voip_token: voip_token,
                created_at: moment().utc().valueOf()

            };
            if (!!fcm_token) {
                data_to_save.fcm_token = fcm_token;
            }
            return this.model.SessionModel.create(data_to_save);
        } catch (error) {
            throw error
        }
    }

    set_options = async (pagination: any, limit: any) => {
        try {
            console.log(pagination, limit, "pagination_limit");
            console.log(typeof pagination, typeof limit);

            let options: any = {
                lean: true,
                sort: { _id: -1 }
            }
            if (pagination == undefined && limit == undefined) {
                options = {
                    lean: true,
                    sort: { _id: -1 },
                    limit: 100,
                    pagination: 0,
                    skip: 0
                }
            }
            else if (pagination == undefined && typeof limit != undefined) {
                options = {
                    lean: true,
                    sort: { _id: -1 },
                    limit: parseInt(limit),
                    skip: 0,
                }
            }
            else if (typeof pagination != undefined && limit == undefined) {
                options = {
                    lean: true,
                    sort: { _id: -1 },
                    skip: parseInt(pagination) * parseInt('100'),
                    limit: parseInt('100')
                }
            }

            else if (typeof pagination != undefined && typeof limit != undefined) {
                options = {
                    lean: true,
                    sort: { _id: -1 },
                    limit: parseInt(limit),
                    skip: parseInt(pagination) * limit
                }
            }
            return options
        }
        catch (err) {
            throw err;
        }
    }

    sendPushNotification = async (fcm_tokens: any, data: any) => {
        try {
            const payload = {
                data: {
                    title: String(data?.title || ""), // Explicit string conversion
                    message: JSON.stringify(data)?.toString(),
                    data: JSON.stringify(data)?.toString(),
                    type: String(data?.type || ""),
                    messageId: String(data?.chat_id || ""), // Ensure this is a string
                    userId: String(data?.sent_to || ""), // Ensure this is a string
                    connection_id: String(data?.connection_id || ""), // Ensure this is a string
                    order_id: String(data?.order_id || ""), // Ensure this is a string
                },
                notification: {
                    title: data?.title,
                    body: data?.message,
                },
                tokens: fcm_tokens,
                android: {
                    priority: 'high'
                },
                apns: data?.apns || {
                    payload: {
                        aps: {
                            contentAvailable: true
                        }
                    }
                }
            }
        
            console.log("payload++++++++++++++++");
            console.dir(payload, { depth: null });


            fcm_admin.messaging().sendEachForMulticast({
                data: {
                    type: String(data?.type || ""),
                    message_id: String(data?.chat_id || ""),
                    message: String(data?.message || ""),
                    sent_by: String(data?.sent_by || ""),
                    sent_to: String(data?.sent_to || ""),
                    sent_by_name: String(data?.sent_by_name || ""),
                    sent_by_profile_pic: String(data?.sent_by_profile_pic || ""),
                    connection_id: String(data?.connection_id || ""),
                    connection_type: String(data?.connection_type || ""),
                    chat_id: String(data?.chat_id || ""),
                    is_muted: String(data?.is_muted || ""),
                    show_notification: String(data?.show_notification || ""),
                    order_id: String(data?.order_id || ""),
                },
                notification: {
                    title: data?.title,
                    body: data?.message,
                },
                tokens: fcm_tokens,
                android: {
                    priority: 'high'
                },
                apns: data?.apns || {
                    payload: {
                        aps: {
                            contentAvailable: true
                        }
                    }
                }
            }).then(response => {
                for (let i = 0; i < response?.responses?.length; i++) {
                    const element = response?.responses[i];
                    console.log(element);

                }
            }).catch(error => {
                console.error('Failed to send notification...', error);
            });

        } catch (err) {
            throw err;
        }
    };

    sendSilentPushNotification = async (fcm_tokens: any, data: any) => {
        try {
            fcm_admin.messaging().sendEachForMulticast({
                data: {
                    type: String(data?.type || ""),
                    message_id: String(data?.chat_id || ""),
                    message: String(data?.message || ""),
                    sent_by: String(data?.sent_by || ""),
                    sent_to: String(data?.sent_to || ""),
                    sent_by_name: String(data?.sent_by_name || ""),
                    sent_by_profile_pic: String(data?.sent_by_profile_pic || ""),
                    connection_id: String(data?.connection_id || ""),
                    connection_type: String(data?.connection_type || ""),
                    chat_id: String(data?.chat_id || ""),
                    is_muted: String(data?.is_muted || ""),
                    show_notification: String(data?.show_notification || ""),
                    order_id: String(data?.order_id || ""),
                    call_id: String(data?.call_id || ""),
                },
                tokens: fcm_tokens,
                android: {
                    priority: 'high'
                },
                apns: data?.apns || {
                    payload: {
                        aps: {
                            contentAvailable: true
                        }
                    }
                }
            }).then(response => {
                for (let i = 0; i < response?.responses?.length; i++) {
                    const element = response?.responses[i];
                    console.log(element);

                }
            }).catch(error => {
                console.error('Failed to send notification...', error);
            });

        } catch (err) {
            throw err;
        }
    };


    // the reason for making this function is->
    // this function is used to send high priority notification only on android devices
    sendHighPriorityNotificationOnANDROIDDevices = async (
        deviceTokens: any,
        notificationData: any
    ) => {
        // const message: any = {
        //     data: JSON.stringify(notificationData),
        //     tokens: deviceTokens,
        //     android: {
        //         priority: "high",
        //         extraData: JSON.stringify(notificationData.extraData || {})
        //     },
        // };

        // try {
        //     console.log(message, "+++++++++++++");

        //     const response: any = await fcm_admin.messaging().sendEachForMulticast(message);
        //     console.log("Successfully sent message:", response);
        // } catch (error) {
        //     console.error("Error sending message:", error);
        // }
        fcm_admin.messaging().sendEachForMulticast({
            data: {
                type: notificationData.type,
                call_mode: notificationData.call_mode,
                payload: JSON.stringify(notificationData.data) // 👈 nested object safely
            },
            tokens: deviceTokens,
            android: {
                priority: 'high'
            },
            apns: {
                payload: {
                    aps: {
                        contentAvailable: true
                    }
                }
            }
        }).then(response => {
            for (let i = 0; i < response?.responses?.length; i++) {
                const element = response?.responses[i];
                console.log(element, "call notificaitons")
            }
        }).catch(error => {
            console.error('Failed to send notification...', error);
        });

    };

    // the reason for making this function is->
    // this function is used to send high priority notification only on ios devices
    async sendPushNotificaitonOnIOSDevices(
        deviceToken: string,
        payload: any,
        userType?: string, // USER or VENDOR
    ) {
        payload.pushType = "voip";
        
        console.log("userType" , userType)
        // Select topic based on user type
        const selectedTopic = userType === 'VENDOR' ? this.vendorTopic : this.userTopic;
        console.log("selectedTopic" , selectedTopic)
        console.log(
        {
            aps: {
                alert: {
                    title: payload?.title,
                    body: payload.message
                },
                "badge": 1,
                "sound": "default",
                // incoming_caller_name: payload?.caller_name,
                "content-available": 1,
            },
            pushType: "voip",
            //  sound: "default",
            topic: selectedTopic,
            // badge: 3,
            // nameCaller: payload?.title,
            // contentAvailable: 1,
            handle: payload?.connection_id?.toString(),
        }, 
        "payload++++++++++++++++");

        const notification = new apn.Notification({
            aps: {
                alert: {
                    title: payload?.title,
                    body: payload.message
                },
                "badge": 1,
                "sound": "default",
                // incoming_caller_name: payload?.caller_name,
                "content-available": 1,
            },
            pushType: "voip",
            //  sound: "default",
            topic: selectedTopic,
            // badge: 3,
            // nameCaller: payload?.title,
            // contentAvailable: 1,
            handle: payload?.connection_id?.toString(),
        });
        // notification.topic = "com.io.connectverse.voip";
        notification.payload = { ...payload };
        notification.priority = 10;
        // notification["pushType"] = "voip";

        console.log(notification, "notification++++++++++++++++");
        
        // Check if APNS provider is initialized
        if (!this.apnProvider) {
            console.warn('APNS provider not initialized. Skipping iOS push notification.');
            return;
        }
        
        try {
            const result = await this.apnProvider.send(notification, deviceToken);
            console.dir(result, { depth: null });
            console.log("notification sent successfully.");

        } catch (error) {
            console.error("Error sending push notification:", error);
        }
    }


    createRandomId(): string {
    // Generate a unique UUID
    const uniqueId = uuidv4();

    // Extract the numeric part from the UUID and limit it to 7 digits
    const numericPart = parseInt(uniqueId.replace(/\D/g, ''), 10);
    const sevenDigitNumber = ('0000000' + (numericPart % 10000000)).slice(-7);

    // Concatenate the prefix "ct" with the unique UUID
    const customUniqueId = `HFFD${sevenDigitNumber}`;

    return customUniqueId;
  }

  validateEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }

  validatePhone(phone: string): boolean {
    const phoneRegex = /^\d{7,15}$/;
    return phoneRegex.test(phone);
  }

  /**
   * Common response for single data with message
   * @param message - Success message
   * @param data - Single data object
   * @param statusCode - HTTP status code (default: 200)
   */
  successResponse(message: string, data: any = null, statusCode: number = 200) {
    return {
      status: 'success',
      message,
      data
    };
  }

  /**
   * Common response for paginated data with message
   * @param message - Success message
   * @param data - Array of data
   * @param total - Total count of records
   * @param page - Current page number (default: 1)
   * @param limit - Items per page (default: 10)
   */
  paginatedResponse(message: string, data: any[], total: number, page: number = 1, limit: number = 10) {
    const totalPages = Math.ceil(total / limit);
    return {
      status: 'success',
      message,
      data,
      pagination: {
        total,
        page: parseInt(page as any),
        limit: parseInt(limit as any),
        totalPages,
        hasNextPage: page < totalPages,
        hasPrevPage: page > 1
      }
    };
  }


    /** Random URL-safe token to email; store only its hash. */
    generateSecureToken(): string {
        return crypto.randomBytes(32).toString('hex');
    }

    hashToken(token: string): string {
        return crypto.createHash('sha256').update(token).digest('hex');
    }

    async getSettings(){
        const systemSetting = await this.model.SettingsModel.findOne().lean();
        if(!systemSetting){
        throw new NotFoundException(`System setting not found`);
        }
        return systemSetting;
    }



    /**
     * Translate key to actual text based on user's language
     * @param languageCode - Language code (en, hi, etc.)
     * @param key - Translation key from i18n file
     * @param type - Type of translation: NOTIFICATION or API_RESPONSE
     * @returns Translated text
     */

    
    translateKey(languageCode: string, key: string, type: I18nType = I18nType.API_RESPONSE, params?: Record<string, any>): string {
        // Load i18n file based on language code and type
        const i18nPath = path.join(process.cwd(), 'i18n', type, `${languageCode}.json`);
        let i18nData: Record<string, string> = {};
        
        try {
            if (fs.existsSync(i18nPath)) {
                i18nData = JSON.parse(fs.readFileSync(i18nPath, 'utf8'));
            } else {
                // Fallback to English
                const enPath = path.join(process.cwd(), 'i18n', type, 'en.json');
                i18nData = JSON.parse(fs.readFileSync(enPath, 'utf8'));
            }
        } catch (error) {
            console.error('Error loading i18n file:', error);
        }
        
        let text = i18nData[key] || key; // Fallback to key if translation not found
        if (params) {
            Object.keys(params).forEach(k => { text = text.replaceAll(`{${k}}`, String(params[k] ?? '')); });
        }
        
        return text;
    }

    /**
     * Process notifications and add translated text
     * @param notifications - Array of notifications from DB
     * @param languageCode - User's language code
     * @returns Notifications with translated title and message
     */
    processNotificationsWithTranslation(notifications: any[], languageCode: string): any[] {
        return notifications.map(notification => {
            // Translate title and message keys to actual text from notification folder
            const title = this.translateKey(languageCode, notification.title, I18nType.NOTIFICATION, notification.meta);
            const message = this.translateKey(languageCode, notification.message, I18nType.NOTIFICATION, notification.meta);
            
            return {
                ...notification,
                title,
                message
            };
        });
    }

}
