import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ModelService } from '../model/model.service';
import { CommonService } from '../common/common.service';
import { Types } from 'mongoose';
import * as dto from './dto/index';
import { admin_ai_agent_aggrigation } from './admin_ai_agent.aggrigation';
import { AgentStatus } from '../model/schema/agent.schema';


@Injectable()
/**
 * Admin AI Service
 */
export class AdminAiAgentService {

    /**
     * Initializes the AdminAiAgentService with required dependencies.
     * 
     * @param {ModelService} Model - Service to interact with the database models.
     * @param {CommonService} CommonService - Utility service for common operations.
     */
    constructor(
        private readonly Model: ModelService,
        private readonly CommonService: CommonService
    ) { }

    /**
     * Creates a new AI agent and stores it in the database.
     * 
     * @param {dto.AIAgent.agentCreate} dto - Data Transfer Object containing agent details.
     * @param {any} req - Request object containing user data.
     * @returns {Promise<any>} - The created agent object.
     * @throws {Error} - Throws an error if the agent creation fails.
     */
    async createAgent(dto: dto.AIAgent.agentCreate, req: any): Promise<any> {
        try {
            const { _id: user_id } = req.user_data;
            const {
                phone_no, country_code, knowledge_base_id, name, image,
                call_prompt, call_first_message, chat_prompt, chat_first_message,
                type, voice, ai_model, ai_model_version, idle_reminder,
                call_cut_ability, call_transfer, send_message, real_time_booking,
                twilio_config
            } = dto;

            return await this.Model.AgentModel.create({
                created_by: new Types.ObjectId(user_id),
                agent_type: "ADMIN_AGENT",
                phone_no,
                country_code,
                knowledge_base_id,
                name,
                image,
                call_prompt,
                call_first_message,
                chat_prompt,
                chat_first_message,
                type,
                voice,
                ai_model,
                ai_model_version,
                idle_reminder,
                call_cut_ability,
                call_transfer,
                send_message,
                real_time_booking,
                twilio_config: new Types.ObjectId(twilio_config),
            });
        } catch (error) {
            console.error("Error creating agent:", error);
            throw error;
        }
    }

    /**
     * Updates an existing AI agent with new details.
     * 
     * @param {string} agent_id - The ID of the agent to be updated.
     * @param {dto.AIAgent.agentEdit} dto - Data Transfer Object containing updated agent details.
     * @returns {Promise<any>} - The updated agent object.
     * @throws {Error} - Throws an error if the update operation fails.
     */
    async updateAgent(agent_id: string, dto: dto.AIAgent.agentEdit): Promise<any> {
        try {
            const { name, image, call_prompt, call_first_message, chat_prompt, chat_first_message, knowledge_base_id, ai_model, voice, twilio_config } = dto;
            const knowledge_base_ids = knowledge_base_id?.map((ele) => new Types.ObjectId(ele));


            return await this.Model.AgentModel.findOneAndUpdate(
                { _id: new Types.ObjectId(agent_id) },
                {
                    ...(call_prompt && { call_prompt }),
                    ...(call_first_message && { call_first_message }),
                    ...(chat_prompt && { chat_prompt }),
                    ...(chat_first_message && { chat_first_message }),
                    ...(name && { name }),
                    ...(image && { image }),
                    ...(knowledge_base_id?.length && { knowledge_base_id: knowledge_base_ids }),
                    ...(ai_model && { ai_model }),
                    ...(voice && { voice }),
                    ...(twilio_config && { twilio_config: new Types.ObjectId(twilio_config) }),
                },
                { lean: true, new: true }
            );
        } catch (error) {
            console.error("Error updating admin ai agent:", error);
            throw error;
        }
    }

    /**
     * Soft deletes an AI agent by setting `is_deleted` to `true`.
     * 
     * @param {string} agent_id - The ID of the agent to be deleted.
     * @returns {Promise<any>} - The updated agent object with `is_deleted: true`.
     * @throws {Error} - Throws an error if the deletion operation fails.
     */
    async deleteAgent(agent_id: string): Promise<any> {
        try {
            await this.Model.AgentModel.findOneAndUpdate(
                { _id: new Types.ObjectId(agent_id) },
                { is_deleted: true },
                { new: true }
            );
            return true
        } catch (error) {
            console.error("Error deleting AI agent:", error);
            throw error;
        }
    }

    /**
     * Retrieves details of a specific AI agent.
     * 
     * @param {string} agent_id - The ID of the agent to retrieve.
     * @param {any} req - Request object containing user data.
     * @returns {Promise<any>} - The agent details including populated `knowledge_base_id`.
     * @throws {Error} - Throws an error if retrieval fails.
     */
    async getAgentDetail(agent_id: string, req: any): Promise<any> {
        try {
            const { _id: user_id } = req.user_data;

            const agent: any = await this.Model.AgentModel.findOne({ _id: new Types.ObjectId(agent_id) }, {}, { lean: true }).populate([
                { path: "knowledge_base_id", select: "_id name created_at" },
                { path: "twilio_config", select: "_id phone_number" },
            ])

            const web_script = await this.Model.scriptModel.findOne(
                { agent_id: new Types.ObjectId(agent_id) },
                {
                    _id: 1,
                    key: 1,
                    title: 1,
                    colour: 1,
                    agent_id: 1,
                    created_at: 1,
                    description: 1,
                    font_colour: 1,
                    script_status: 1,
                }
            );

            Object.assign(agent, {
                script_data: web_script,
            });
            return agent;
        } catch (error) {
            console.error("Error retrieving AI agent details:", error);
            throw error;
        }
    }

    /**
     * Retrieves a paginated list of AI agents that are not deleted.
     * 
     * @param {any} req - The request object containing user data.
     * @param {dto.AIAgent.get_agent} body - The request body containing pagination details.
     * @returns {Promise<{ data: any[], count: number }>} - An object containing the agent list and total count.
     * @throws {Error} - Throws an error if retrieval fails.
     */
    async getAdminAIAgents(req: any, body: dto.AIAgent.get_agent): Promise<{ data: any[], count: number }> {
        try {
            const { _id: user_id } = req.user_data;
            const { pagination, limit, search } = body;
            const option = await this.CommonService.set_options(pagination, limit);

            const temp_update = await this.Model.AgentModel.updateMany({ is_deleted: { $ne: true } }, { is_deleted: false }, { new: true })
            console.log('temp_update', temp_update)

            const temp_status = await this.Model.AgentModel.updateMany({ status: { $nin: [ AgentStatus.ACTIVE, AgentStatus.IN_ACTIVE ] } }, { status: AgentStatus.ACTIVE }, { new: true })

            const query: any = {
                is_deleted: false,
                agent_type: "ADMIN_AGENT",
                is_default: false,
            };

            const project = {
                _id: true,
                name: true,
                type: true,
                image: true,
                phone_no: true,
                country_code: true,
                voice: true,
                ai_model: true,
                ai_model_version: true,
                created_at: true,
            }

            if (search) {
                query.name = { $regex: search, $options: "i" };
            }

            const data = await this.Model.AgentModel.find(query, project, option)
                .populate([{ path: "knowledge_base_id", select: "_id name" }]);
            const count = await this.Model.AgentModel.countDocuments(query);

            return { data, count };
        } catch (error) {
            console.error("Error retrieving AI agents:", error);
            throw error;
        }
    }

    async getDefaultAIAgents(req: any, body: dto.AIAgent.get_agent): Promise<{ data: any[], count: number }> {
        try {
            const { _id: user_id } = req.user_data;
            const { pagination, limit, search } = body;

            const temp_update = await this.Model.AgentModel.updateMany({ is_default: true, is_deleted: { $ne: true } }, { is_deleted: false, agent_type: "ADMIN_AGENT" }, { new: true })
            console.log('temp_update', temp_update)

            const result = await this.Model.AgentModel.aggregate([
                {
                    $match: {
                        is_deleted: false,
                        agent_type: "ADMIN_AGENT",
                        is_default: true
                    }
                },
                await admin_ai_agent_aggrigation.lookup_chat(),
                await admin_ai_agent_aggrigation.lookup_call(),
                await admin_ai_agent_aggrigation.lookup_voice_call(),
                await admin_ai_agent_aggrigation.add_fields(),
                await admin_ai_agent_aggrigation.lookup_twilio_config(),
                await admin_ai_agent_aggrigation.unwind_twilio_config(),
                await admin_ai_agent_aggrigation.lookup_script_data(),
                await admin_ai_agent_aggrigation.unwind_script_data(),
                await admin_ai_agent_aggrigation.group_data(),
                await admin_ai_agent_aggrigation.facet_data(pagination, limit),
                await admin_ai_agent_aggrigation.project_count(),
            ]);

            return {
                count: result[0]?.count || 0,
                data: result[0]?.data || []
            };
        } catch (error) {
            console.error("Error retrieving AI agents:", error);
            throw error;
        }
    }

    async chatLogsHistory(agent_id: string, body: dto.AIAgent.get_agent_chat) {
        try {
            const { pagination, limit, type, search } = body;

            const option = await this.CommonService.set_options(pagination, limit)
            const query = {
                agent_id: new Types.ObjectId(agent_id),
                ...(type && { type: type }),
                ...(search && {
                    $or: [
                        { name: { $regex: search, $options: "i" } },
                        { email: { $regex: search, $options: "i" } },
                        { phone_no: { $regex: search, $options: "i" } }]
                })

            }
            const data = await this.Model.CallModel.find(query, {}, option).populate([{ path: 'agent_id', select: 'name image' }])
            const count = await this.Model.CallModel.countDocuments(query)

            return {
                data,
                count
            }
        } catch (error) {
            throw error;
        }
    }

    async getEmotionPercentagesByAgent(agent_id: string) {
        try {
            const result = await this.Model.CallModel.aggregate([
                await admin_ai_agent_aggrigation.match_with_agent_id(agent_id),
                {
                    $facet: {
                        tele_call: [
                            { $match: { type: "CALL" } },
                            { $group: admin_ai_agent_aggrigation.emotionGroupStage() },
                            { $project: admin_ai_agent_aggrigation.emotionProjectStage() }
                        ],
                        voice_call: [
                            { $match: { type: "VOICE_CHAT" } },
                            { $group: admin_ai_agent_aggrigation.emotionGroupStage() },
                            { $project: admin_ai_agent_aggrigation.emotionProjectStage() }
                        ],
                        chat: [
                            { $match: { type: "CHAT" } },
                            { $group: admin_ai_agent_aggrigation.emotionGroupStage() },
                            { $project: admin_ai_agent_aggrigation.emotionProjectStage() }
                        ]
                    }
                }
            ]);

            return {
                tele_call_emotion: result[0].tele_call[0] || { positive: 0, negative: 0, neutral: 0 },
                voice_call_emotion: result[0].voice_call[0] || { positive: 0, negative: 0, neutral: 0 },
                chat_emotion: result[0].chat[0] || { positive: 0, negative: 0, neutral: 0 }
            };
        } catch (error) {
            throw error;
        }
    }


    async scriptCreate(agent_id: string, body: dto.AIAgent.workspace_script_create, req: any) {
        try {
            const { _id: user_id, email } = req.user_data;
            const { workspace_id, key, name, image, title, description, colour, font_colour } = body

            const is_agent_assigned: any = await this.Model.scriptModel.findOne({ agent_id: new Types.ObjectId(agent_id) }).populate({ path: 'agent_id', select: 'name' });
            if (is_agent_assigned) throw new HttpException(`The agent '${is_agent_assigned?.agent_id?.name}' is already assigned to this script. Kindly select a different agent to proceed.`, HttpStatus.BAD_REQUEST);

            let data = await this.Model.scriptModel.create({
                workspace_id: new Types.ObjectId(workspace_id),
                agent_id: new Types.ObjectId(agent_id),
                key: key,
                name: name,
                image: image,
                title: title,
                description: description,
                colour: colour,
                font_colour: font_colour,
            }, {}, { lean: true })
            return data
        } catch (error) {
            throw error
        }
    }

    async scriptEdit(agent_id: string, body: dto.AIAgent.workspace_script_edit, req: any) {
        try {
            const { _id: user_id, email } = req.user_data;
            const { name, image, title, description, colour, font_colour } = body

            const data = await this.Model.scriptModel.findOneAndUpdate(
                { agent_id: new Types.ObjectId(agent_id) }, {
                ...(name && { name: name }),
                ...(image && { image: image }),
                ...(title && { title: title }),
                ...(description && { ndescriptioname: description }),
                ...(colour && { colour: colour }),
                ...(font_colour && { font_colour: font_colour }),
            }, { lean: true, new: true });
            return data
        } catch (error) {
            throw error
        }
    }

    async scriptEnableDisable(agent_id: string, script_id: string, req: any, body: dto.AIAgent.workspace_script_status) {
        try {
            const { script_status } = body;
            const { _id: user_id, email } = req.user_data;

            return await this.Model.scriptModel.findOneAndUpdate({ _id: new Types.ObjectId(script_id) }, { script_status: script_status }, { new: true })
        } catch (error) {
            throw error
        }
    }

    async getTestAgent() {
        try {
            const agent = await this.Model.AgentModel.findOne({ is_default: true, default_type : "CALL"}, { _id: 1, name: 1, image: 1 }, { lean: true })
            return agent;
        } catch (error) {
            throw error
        }
    }

}
