import { Injectable, HttpException, HttpStatus, OnModuleInit } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import axios from 'axios';
import { channel } from 'diagnostics_channel';
const moment = require('moment');
import { env } from 'process';
import { Constants } from 'src/common/constants';
import { KeyType, Settings } from 'src/entities/settings.entity';
import { SocialMediaPosts } from 'src/entities/social-media-posts.entity';
import { SocialMedias, SocialType } from 'src/entities/social-medias.entity';
import { TikTokProfiles } from 'src/entities/tiktok-profile.entity';
import { TikTokVideos } from 'src/entities/tiktok-videos.entity';
import { Users } from 'src/entities/users.entity';
import { YoutubeChannels } from 'src/entities/youtube-channel.entity';
import { YoutubeVideos } from 'src/entities/youtube-videos.entity';
import { responseMessages } from 'src/messages/response-messages';
import { IsNull, Not, Repository } from 'typeorm';
import { EncryptionService } from '../encryption/encryption.service';
import { uploadFileToS3 } from 'src/common/config/upload-s3-file-using-link';

@Injectable()
export class TiktokServices implements OnModuleInit {
    public s3Config = {};
    constructor(
        private encryptService: EncryptionService,
        @InjectRepository(Users)
        private Users: Repository<Users>,

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

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

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

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

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

    ) { }
    async onModuleInit() {
        this.s3Config = {
            accessKeyId: await this.encryptService.decrypt(process.env.AWS_ACCESS_KEY_ID),
            secretAccessKey: await this.encryptService.decrypt(process.env.AWS_SECRET_ACCESS_KEY),
            region: process.env.AWS_DEFAULT_REGION,
        }
    }
    /**
     * Links a TikTok account to the user's profile and retrieves the user's basic information and video list.
     * 
     * @param request - An object containing the TikTok access token.
     * @param authUser - The authenticated user object performing the link action.
     * 
     * @returns A boolean indicating whether the TikTok account was successfully linked and data retrieved.
     * 
     * @throws HttpException - Throws a 400 error if the TikTok user does not exist.
     * @throws HttpException - Throws a 206 error if the TikTok account is not eligible for linking.
     * 
     * After linking, the user's total followers, following counts, and post count are updated accordingly.
     */
    async tiktok(request: any, authUser: any) {

        try {
            const channelUrl = `${process.env.TIKTOK_BASE_URL}user/info/`;
            const headers = {
                Authorization: `Bearer ${request.access_token}`,
                "Content-Type": 'application/json'
            };
            const params = {
                fields: 'open_id,union_id,avatar_url,avatar_url_100,avatar_large_url,display_name,bio_description,profile_deep_link,is_verified,username,follower_count,following_count,likes_count,video_count'
            };
            // GET THE CHANNEL LIST
            const response = await this.getAxios(channelUrl, headers, params);
            let isEligible = 0;
            let socialMedia = null;
            let total_followers = 0;
            let total_following = 0;
            let post_count = 0;
            let about_me = null;
            let profileImage = "";
            if (response && response.data.user) {
                console.log("response", response)
                // SAVE SOCIAL MEDIA ACCOUNT 
                const setting = await this.Settings.findOne({ where: { type: KeyType.TIKTOK_FOLLOWERS_COUNT } });
                // CHECK THE PLATFORM LINK ELIGIBILITY 

                socialMedia = await this.saveSocialMedia(request, authUser);

                if (socialMedia) {
                    const tiktokUser = response.data.user;
                    if (tiktokUser.follower_count >= parseInt(setting.value)) {
                        isEligible = 1;
                        const newTiktokUser = {
                            users: authUser,
                            socialMedia: socialMedia,
                            avatar_url: tiktokUser.avatar_url || "",
                            bio_description: tiktokUser.bio_description || "",
                            open_id: tiktokUser.open_id || "",
                            username: tiktokUser.username || "",
                            display_name: tiktokUser.display_name || "",
                            is_verified: tiktokUser.is_verified || false,
                            profile_deep_link: tiktokUser.profile_deep_link || "",
                            union_id: tiktokUser.union_id || "",
                            likes_count: tiktokUser.likes_count || 0,
                            video_count: tiktokUser.video_count || 0,
                            following_count: tiktokUser.following_count || 0,
                            follower_count: tiktokUser.follower_count || 0,
                            url: tiktokUser.profile_deep_link
                        };
                        total_followers += tiktokUser.follower_count;
                        total_following += tiktokUser.following_count;
                        post_count += tiktokUser.video_count;
                        about_me = tiktokUser.bio_description
                        // SAVE TIKTOK USER PROFILE
                        const tiktokProfile = await this.TikTokProfiles.save(newTiktokUser);
                        if (tiktokProfile) {
                            // if (!authUser.is_profile_pic_updated && authUser.image == "") {
                            profileImage = tiktokUser.avatar_url;
                            const s3Response = await uploadFileToS3(profileImage, `images/profile/`, this.s3Config);
                            if (s3Response) {
                                profileImage = s3Response.path;
                            }
                            //}
                            await this.getVideoList(tiktokProfile, socialMedia, authUser);
                        }
                    }
                }
            } else {
                throw new HttpException(responseMessages.en.user.social_media.tiktok_profile_not_exists, HttpStatus.BAD_REQUEST);
            }
            if (!isEligible) {
                this.unlinkTiktok(socialMedia)
                throw new HttpException(responseMessages.en.user.social_media.not_eligible, HttpStatus.PARTIAL_CONTENT);
            }
            authUser['image'] = profileImage;
            authUser['about_me'] = authUser['about_me'] ? authUser['about_me'] : about_me;
            if (!authUser['platforms']) {
                authUser['platforms'] = [];
            }
            if (!authUser['platforms'].includes('tiktok')) {
                authUser['platforms'].push('tiktok');
            }
            await this.Users.save(authUser);

            socialMedia = await this.SocialMedias.findOne({ where: { id: socialMedia.id } });
            socialMedia['follower_count'] = Number(total_followers);
            socialMedia['following_count'] = Number(total_following);
            socialMedia['post_count'] = Number(post_count);
            await this.SocialMedias.save(socialMedia);
            this.refreshToken(socialMedia)
            return true;
        } catch (error) {
            throw new HttpException(error.message, error.status);
        }
    }

    /**
     * This function fetches the list of videos for a given TikTok user profile
     * and saves them in the database.
     * @param tiktokProfile The TikTok user profile object
     * @param socialMedia The social media profile object
     * @param authUser The authenticated user object
     * @returns void
     */
    async getVideoList(tiktokProfile, socialMedia, authUser) {
        const pageUrl = process.env.TIKTOK_BASE_URL + 'video/list/';
        const headers = {
            Authorization: `Bearer ${socialMedia.social_access_token}`,
            "Content-Type": 'application/json'
        };
        const params = {
            fields: 'id,title',
        };
        // GET THE VIDEO LIST
        const data = {
            "max_count": Constants.POST_LIMIT
        }
        const videosResponse = await this.postAxios(pageUrl, headers, data, params);
        console.log("videosResponseLIST", videosResponse)
        if (videosResponse.data.videos.length) {
            const videoIdArray = videosResponse.data.videos.map(item => item.id);
            await this.getUserVideos(videoIdArray, tiktokProfile, socialMedia, authUser);
        }
    }

    async getLivePost(socialMedia, nextPageToken = 0) {
        const pageUrl = process.env.TIKTOK_BASE_URL + 'video/list/';
        const headers = {
            Authorization: `Bearer ${socialMedia.social_access_token}`,
            "Content-Type": 'application/json'
        };
        const params = { fields: 'id,title' };
        // GET THE VIDEO LIST
        const data = { "max_count": 12, cursor: nextPageToken ? nextPageToken : 0 }
        const videosResponse = await this.postAxios(pageUrl, headers, data, params);
        if (videosResponse?.data?.videos?.length) {
            const videoIdArray = videosResponse.data.videos.map(item => item.id);
            const videoUrl = process.env.TIKTOK_BASE_URL + 'video/query/';

            const params = {
                fields: 'id, create_time, cover_image_url, share_url, video_description, duration, height, width, title, embed_html, embed_link, like_count, comment_count, share_count, view_count',
            };
            const data = {
                "filters": {
                    "video_ids": videoIdArray
                }
            }
            // GET THE VIDEO LIST
            const videoArr = await this.postAxios(videoUrl, headers, data, params);
            return { data: videoArr, nextPageToken: videosResponse.data.cursor }
        }
        return { data: [], nextPageToken: 0 }
    }
    async getSingleVideo(socialMedia, videoId) {
        const videoUrl = process.env.TIKTOK_BASE_URL + 'video/query/';
        const headers = {
            Authorization: `Bearer ${socialMedia.social_access_token}`,
            "Content-Type": 'application/json'
        };
        const params = { fields: 'id, create_time, cover_image_url, share_url, video_description, duration, height, width, title, embed_html, embed_link, like_count, comment_count, share_count, view_count' };
        const data = { "filters": { "video_ids": [videoId] } };
        const videoResponse = await this.postAxios(videoUrl, headers, data, params);
        if (videoResponse) {
            return videoResponse?.data?.videos?.[0];
        }
        return false;
    }



    /**
     * Retrieves detailed information about a list of TikTok videos and saves the data in the database.
     *
     * @param videoIdArray - An array of video IDs to be queried for detailed information.
     * @param tiktokProfile - The TikTok user profile associated with the videos.
     * @param socialMedia - The social media profile object containing access credentials.
     * @param authUser - The authenticated user requesting the video details.
     * 
     * @returns A boolean indicating whether the operation was successful.
     * 
     * @throws HttpException - Throws an internal server error exception if there's an error during processing.
     */
    async getUserVideos(videoIdArray, tiktokProfile, socialMedia, authUser) {
        try {

            const pageUrl = process.env.TIKTOK_BASE_URL + 'video/query/';
            const headers = {
                Authorization: `Bearer ${socialMedia.social_access_token}`,
                "Content-Type": 'application/json'
            };
            const params = {
                fields: 'id, create_time, cover_image_url, share_url, video_description, duration, height, width, title, embed_html, embed_link, like_count, comment_count, share_count, view_count',
            };
            const data = {
                "filters": {
                    "video_ids": videoIdArray
                }
            }
            let totalLikes: bigint = BigInt(0);
            let totalComments: bigint = BigInt(0);
            let totalViews: bigint = BigInt(0);
            let followers: bigint = BigInt(0);
            // GET THE VIDEO LIST
            const videosResponse = await this.postAxios(pageUrl, headers, data, params);
            console.log("videosResponse", JSON.stringify(videosResponse))
            if (videosResponse && videosResponse.data.videos.length) {
                let newVideo = [];
                let socialPosts = [];
                for (const video of videosResponse.data.videos) {
                    totalLikes += BigInt(video.like_count || 0);
                    totalComments += BigInt(video.comment_count || 0);
                    totalViews += BigInt(video.view_count || 0);

                    newVideo.push({
                        users: authUser,
                        socialMedia: socialMedia,
                        tikTokProfiles: tiktokProfile,
                        video_id: video.id || "",
                        create_time: moment.unix(video.create_time).format('YYYY-MM-DD HH:mm:ss') || "",
                        title: video.title || "",
                        cover_image_url: video.cover_image_url || "",
                        embed_html: video.embed_html || "",
                        share_url: video.share_url || "",
                        embed_link: video.embed_link || "",
                        height: video.height || 0,
                        width: video.width || 0,
                        like_count: video.like_count || 0,
                        share_count: video.share_count || 0,
                        comment_count: video.comment_count || 0,
                        duration: video.duration || 0,
                        view_count: video.view_count || 0
                    });

                    socialPosts.push({
                        users: authUser,
                        socialMedia: socialMedia,
                        title: video.title || "",
                        description: "",
                        like_count: video.like_count || 0,
                        comment_count: video.comment_count || 0,
                        post_id: video.id,
                        image_url: video.cover_image_url || "",
                        social_type: SocialType.TIKTOK,
                        created_time: moment.unix(video.create_time).format('YYYY-MM-DD HH:mm:ss') || ""
                    })

                }

                await this.TikTokVideos.save(newVideo);
                await this.SocialMediaPosts.save(socialPosts)
            }
            if (tiktokProfile.follower_count > 0) {
                console.log("tiktokProfile.follower_count", tiktokProfile.follower_count)
                const engagementRate = parseFloat(
                    (((Number(totalLikes) + Number(totalComments) + Number(totalViews)) / Constants.POST_LIMIT) / Number(tiktokProfile.follower_count) * 100).toFixed(4)
                );
                console.log("engagementRate", engagementRate)
                await this.updateSocialMediaEngagementRate(socialMedia.id, engagementRate);
            }
            return true;
        } catch (error) {
            throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    async updateSocialMediaEngagementRate(id, engagementRate) {
        const socialMedia = await this.SocialMedias.findOne({ where: { id: id } });

        const oldEngagementRate = socialMedia['engagement_rate'] || 0;
        socialMedia['old_engagement_rate'] = oldEngagementRate;
        socialMedia['engagement_rate'] = engagementRate;
        if (oldEngagementRate == 0.0000) {
            socialMedia['engagement_rate_percentage'] = 100;  // or 100, depending on logic.
        } else {
            socialMedia['engagement_rate_percentage'] = Math.abs((engagementRate - oldEngagementRate) / oldEngagementRate * 100);
        }
        await this.SocialMedias.save(socialMedia);
    }

    /**
     * Saves a social media account to the user's profile.
     * 
     * @param request - An instance of SocialMediasDto containing the social media account's id and type.
     * @param authUser - The authenticated user object performing the link action.
     * 
     * @throws HttpException - Throws a 404 error if the user does not exist.
     * @throws HttpException - Throws a 400 error if the social media account already exists.
     * @throws HttpException - Throws a 400 error if the social media type is invalid.
     * 
     * After linking, the user's total followers and following counts are increased accordingly.
     */
    async saveSocialMedia(request, authUser) {
        const lang = authUser.language || 'en';
        const socialMediaExists = await this.SocialMedias.findOne({
            where: {
                users: { id: Not(authUser.id), deleted_at: IsNull() },
                social_user_name: request.social_user_name,
                social_type: request.social_media_type,
                is_verified: true
            }
        })

        if (socialMediaExists) {
            throw new HttpException(responseMessages[lang].user.social_media.arealy_exists, HttpStatus.RESET_CONTENT);
        }

        const save = await this.SocialMedias.save({
            users: authUser,
            social_id: request.social_id,
            social_type: request.social_media_type,
            social_access_token: request.access_token,
            social_user_name: request.social_user_name,
            refresh_token: request.refresh_token,
            source_posts_from: 'tiktok',
            is_verified: true,
            missing_permissions: request.missing_permissions ?? []
        });

        // UNLINK UNVERIFIED SOCIAL MEDIA
        const unlinkUnverifiedSocialMedia = await this.SocialMedias.findOne({ where: { users: { id: authUser.id }, social_type: request.social_media_type, is_verified: false } })
        if (unlinkUnverifiedSocialMedia) {
            this.unlinkTiktok(unlinkUnverifiedSocialMedia);
        }
        return save;
    }

    async tiktokEdit(request, id) {

        const tiktokProfiles = await this.TikTokProfiles.findOne({ where: { id: id } });
        if (!tiktokProfiles) {
            throw new HttpException(responseMessages.en.user.social_media.not_exists, HttpStatus.RESET_CONTENT);
        }

        tiktokProfiles['bio_description'] = request.bio;
        tiktokProfiles['display_name'] = request.name;
        tiktokProfiles['city_name'] = request.city;
        tiktokProfiles['country_name'] = request.country;
        tiktokProfiles['gender'] = request.gender;

        this.TikTokProfiles.save(tiktokProfiles)
        return;
    }

    /**
     * Unlinks a TikTok account from the user's profile.
     * 
     * @param socialMedia - The social media account object to be unlinked.
     * 
     * Deletes all related TikTok videos, profiles, social media posts and the social media account itself.
     */
    async unlinkTiktok(socialMedia) {
        await this.TikTokVideos.softDelete({ socialMedia: { id: socialMedia.id } });
        await this.TikTokProfiles.softDelete({ socialMedia: { id: socialMedia.id } });
        await this.SocialMediaPosts.softDelete({ socialMedia: { id: socialMedia.id } });
        await this.SocialMedias.softDelete({ id: socialMedia.id });
    }

    async gettikTokVideosFromBD(postId) {
        return this.TikTokVideos.find({ where: { tikTokProfiles: { id: postId } }, order: { create_time: "DESC" } });
    }

    /**
     * Refreshes the TikTok access token for a social media account.
    This function is a cron job that runs every *****.
     */
    async refreshToken(socialMedias) {
        try {
            const TIKTOK_CLIENT_ID = await this.encryptService.decrypt(process.env.TIKTOK_CLIENT_ID);
            const params = {
                "client_key": TIKTOK_CLIENT_ID,
                "grant_type": "refresh_token",
                "refresh_token": socialMedias.refresh_access_token
            }
            const response = await axios.post('https://open-api.tiktok.com/oauth/refresh_token/', { params });

            if (response.data) {
                socialMedias.social_access_token = response.data.data.access_token;
                socialMedias.access_token_expire_at = moment().add(1, 'days').format('YYYY-MM-DD');
                socialMedias.is_token_expire = false;
                console.log("REFRESH TOKEN SUCCESSFULLY", socialMedias.social_user_name)
                await this.SocialMedias.save(socialMedias);
                return true;
            }
            socialMedias.is_token_expire = true;
            await this.SocialMedias.save(socialMedias);
            return false;
        } catch (error) {
            console.log("REFRESH TOKEN ERROR", error.message)
            return false;
        }
    }

    /**
     * Syncs the TikTok page data of all social media accounts which are of type TikTok.
     * This function is a cron job that runs every ******.
     */
    async syncTiktokProfile(pageSize, social_id = null) {
        const daysAgo = moment().subtract(Constants.SOCIAL_MEDIA_CRON_DAY, 'days').toDate();

        let query = await this.SocialMedias.createQueryBuilder('sm')
            .select(['sm.id', 'sm.social_user_name', 'sm.social_access_token', 'sm.follower_count', 'sm.following_count', 'sm.post_count', 'sm.created_at', 'sm.apify_data_updated_attempts'])
            .where('social_type = :social_type', { social_type: SocialType.TIKTOK })
            .andWhere('(sm.updated_by_cron <= :dateLimit OR sm.updated_by_cron IS NULL)', {
                dateLimit: daysAgo
            })
            .andWhere('is_verified = true').andWhere('is_token_expire = false');

        // UPDATE SINGLE RECORD
        if (social_id) {
            query.andWhere('sm.id = :social_id', { social_id })
        }

        const [result, total] = await query.orderBy('sm.created_at', 'DESC').take(pageSize).getManyAndCount();
        if (result.length) {
            for await (const socialMedia of result) {
                try {
                    const tiktokUrl = `${process.env.TIKTOK_BASE_URL}user/info/`;
                    const headers = {
                        Authorization: `Bearer ${socialMedia.social_access_token}`,
                        "Content-Type": 'application/json'
                    };
                    const params = {
                        fields: 'open_id,union_id,avatar_url,avatar_url_100,avatar_large_url,display_name,bio_description,profile_deep_link,is_verified,username,follower_count,following_count,likes_count,video_count'
                    };
                    // GET THE CHANNEL LIST
                    const response = await this.getAxios(tiktokUrl, headers, params);
                    console.log("TIKTOK USER NAME", socialMedia?.social_user_name)
                    if (response && response.data.user) {
                        if (socialMedia) {
                            const tiktokUser = response.data.user;
                            let tiktokProfile = await this.TikTokProfiles.findOne({ relations: ['users'], where: { socialMedia: { id: socialMedia.id } } });

                            tiktokProfile.avatar_url = tiktokUser.avatar_url || "",
                                tiktokProfile.bio_description = tiktokUser.bio_description || "",
                                tiktokProfile.open_id = tiktokUser.open_id || "",
                                tiktokProfile.display_name = tiktokUser.display_name || "",
                                tiktokProfile.is_verified = tiktokUser.is_verified || false,
                                tiktokProfile.profile_deep_link = tiktokUser.profile_deep_link || "",
                                tiktokProfile.union_id = tiktokUser.union_id || "",
                                tiktokProfile.likes_count = tiktokUser.likes_count || 0,
                                tiktokProfile.video_count = tiktokUser.video_count || 0,
                                tiktokProfile.following_count = tiktokUser.following_count || 0,
                                tiktokProfile.follower_count = tiktokUser.follower_count || 0
                            // SAVE TIKTOK USER PROFILE
                            await this.TikTokProfiles.save(tiktokProfile);

                            socialMedia['follower_count'] = tiktokUser.follower_count || 0;
                            socialMedia['following_count'] = tiktokUser.following_count || 0;
                            socialMedia['post_count'] = tiktokUser.video_count || 0;
                            socialMedia['source_posts_from'] = 'tiktok';
                            socialMedia['updated_by_cron'] = moment().format('YYYY-MM-DD HH:mm:ss');
                            await this.SocialMedias.save(socialMedia);

                            await this.getVideoList(tiktokProfile, socialMedia, tiktokProfile.users);
                        }
                    } else {
                        this.SocialMedias.update({ id: socialMedia.id }, { source_posts_from: 'tiktok', apify_data_updated_attempts: socialMedia.apify_data_updated_attempts + 1 });
                        if (socialMedia.apify_data_updated_attempts >= 3) {
                            this.SocialMedias.update({ id: socialMedia.id }, { source_posts_from: 'tiktok', is_token_expire: true, updated_by_cron: moment().format('YYYY-MM-DD HH:mm:ss') });
                        }
                    }
                } catch (error) {
                    throw new HttpException(error.message, error.status);
                }
            }
            if (total) {
                await this.syncTiktokProfile(pageSize);
            }
        }
    }

    async getAxios(url, headers, params) {
        try {
            const response = await axios.get(url, { headers, params });
            // Handle the response data
            return response.data;

        } catch (error) {
            console.log(error.message);
            return false;
            //throw new HttpException(error.message, HttpStatus.BAD_REQUEST);
        }
    }

    async postAxios(url, headers, data, params = null) {
        try {
            const response = await axios.post(url, data, { headers, params });
            // Handle the response data
            return response.data;

        } catch (error) {
            console.log(error.message);
            return false;
            //throw new HttpException(error.message, HttpStatus.BAD_REQUEST);
        }
    }

}
