"use strict";

import {__} from "@wordpress/i18n";
import {urlSearchParams} from "../../helpers";

export default class Chatbot {
    /**
     * Active chats local storage key
     * @type {string}
     */
    #activeChatsStorageKey = '';

    /**
     * Rest API endpoints
     * @type {{chats: string, chatsMessages: string, chatsRun: string}}
     */
    #endpoints = {
        chats: `${LimbChatbot.rest.url}chats`,
        chatsMessages: '',
        chatsRun: '',
    };

    /**
     * Chatbot UUID
     * @type {string}
     */
    #chatbot_uuid = null;

    /**
     * Chatbot chat UUID
     * @type {string}
     */
    #chat_uuid = false;

    /**
     * Constructor
     */
    constructor(chatbot_uuid, activeChatsStorageKey) {
        if (typeof chatbot_uuid === 'string') {
            if (chatbot_uuid !== 'default') {
                this.#chatbot_uuid = chatbot_uuid;
            }
        }
        this.#activeChatsStorageKey = activeChatsStorageKey;
    }

    /**
     * Changes the active chat to the specified chat identified by its unique UUID.
     *
     * @param {string} uuid The unique identifier of the chat to switch to.
     * @return {void}
     */
    changeChatTo(uuid) {
        this.#chat_uuid = typeof uuid === 'string' ? uuid : false;
        this.#setChatsEndpoints();
    }

    /**
     * Save active chat state in local storage
     */
    saveActiveChatState() {
        try {
            // Get
            const prevState = JSON.parse(localStorage.getItem(this.#activeChatsStorageKey) || '{}');
            const chatbotId = this.#chatbot_uuid || 'default';
            // Set
            localStorage.setItem(this.#activeChatsStorageKey, JSON.stringify({
                ...prevState,
                [chatbotId]: {
                    ...(prevState[chatbotId] || {}),
                    chat: this.#chat_uuid,
                }
            }));
        } catch (e) {
            console.error(e);
        }
    }

    /**
     * Setup chatbot chats endpoints
     */
    #setChatsEndpoints() {
        if (this.#chat_uuid) {
            this.#endpoints.chatsMessages = `${LimbChatbot.rest.url}chats/${this.#chat_uuid}/messages`;
            this.#endpoints.chatsRun = `${LimbChatbot.rest.url}chats/${this.#chat_uuid}/run`;
        } else {
            this.#endpoints.chatsMessages = '';
            this.#endpoints.chatsRun = '';
        }
        this.saveActiveChatState();
    }

    /**
     * Has chat
     *
     * @return {boolean}
     */
    hasChat() {
        return Boolean(this.#chat_uuid);
    }

    /**
     * Create chatbot chat
     *
     * @param {object|null} message Optional message to send with chat creation
     * @param {object} params Query params
     * @param {boolean} stream Whether to request streaming response
     * @return {Promise<void>}
     */
    async createChat(message = null, params = {}, stream = false) {
        // Request params
        let requestParams = {};
        if (this.#chatbot_uuid || message) {
            requestParams.body = {};
            if (this.#chatbot_uuid) {
                requestParams.body.chatbot_uuid = this.#chatbot_uuid;
            }
            if (message) {
                requestParams.body.message = message;
            }
        }
        if (requestParams.body) {
            requestParams.body = JSON.stringify(requestParams.body);
        }
        // Send request
        try {
            const query = urlSearchParams(params);
            const headers = {
                'Content-Type': 'application/json',
                'X-WP-Nonce': LimbChatbot.rest.nonce
            };

            // Set Accept and Connection headers based on stream parameter
            if (stream) {
                headers['Accept'] = 'text/event-stream';
                headers['Connection'] = 'keep-alive';
            } else {
                headers['Accept'] = 'application/json';
            }

            const res = await fetch(this.#endpoints.chats + query, {
                method: 'POST',
                headers,
                ...requestParams
            });

            // If streaming, return the Response object directly
            if (stream) {
                return {
                    success: true,
                    response: res,
                };
            }

            const data = await res.json();
            if (!res.ok) {
                return {
                    success: false,
                    message: data.message ? __(data.message, 'limb-chatbot') : __("The chat hasn't been created.", 'limb-chatbot'),
                    code: data.code,
                }
            }
            this.#chat_uuid = data?.uuid || false;
            this.#setChatsEndpoints();
            return {
                success: true,
                data: data,
            };
        } catch (e) {
            return {
                success: false,
                message: __(e.message, 'limb-chatbot'),
            };
        }
    }

    /**
     * Get a list of chats associated with the chatbot.
     *
     * @return {Promise<Array>}
     */
    async getChats({page = 1, per_page = 10, orderby = 'created_at', order = 'desc', include = []}) {
        try {
            const query = urlSearchParams({
                chatbot_uuid: [this.#chatbot_uuid],
                page,
                per_page,
                include,
                orderby,
                order
            });
            const res = await fetch(`${this.#endpoints.chats}${query}`, {
                method: 'GET',
                cache: 'no-store', // Prevent caching
                headers: {
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce
                }
            });
            return res.ok ? await res.json() : null;
        } catch (e) {
            console.error(e);
            return null;
        }
    }

    /**
     * Get chat with messages and live agent status
     *
     * @return {Promise<{messages: {items: Array, total: number}, is_live_agent_active: boolean}|false|{}>}
     */
    async getChatWithMessages() {
        if (!this.#chat_uuid) {
            return {};
        }
        try {
            const query = urlSearchParams({
                include: ['messages', 'is_live_agent_active']
            });
            const res = await fetch(`${this.#endpoints.chats}/${this.#chat_uuid}${query}`, {
                method: 'GET',
                cache: 'no-store', // Prevent caching
                headers: {
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce
                }
            });
            if (res.ok) {
                return await res.json();
            } else {
                if (res.status === 400) {
                    return false;
                }
                return {};
            }
        } catch (e) {
            console.error(e);
            return {};
        }
    }

    /**
     * Get chat messages
     *
     * @return {Promise<*[]>}
     */
    async getChatMessages({page = 1, per_page = 10, orderby = 'id', order = 'asc'}) {
        try {
            const query = urlSearchParams({page, per_page, orderby, order});
            const res = await fetch(`${this.#endpoints.chatsMessages}${query}`, {
                method: 'GET',
                cache: 'no-store', // Prevent caching
                headers: {
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce
                }
            });
            if (res.ok) {
                return await res.json();
            } else {
                if (res.status === 400) {
                    return false;
                }
                return {};
            }
        } catch (e) {
            console.error(e);
            return {};
        }
    }

    /**
     * Update chat
     *
     * @param {string} uuid Chat UUID
     * @param {object} data Chat data
     * @return {Promise<any|boolean>}
     */
    async updateChat(uuid, data) {
        try {
            const res = await fetch(`${this.#endpoints.chats}/${uuid}`, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce,
                },
                body: JSON.stringify(data),
            });
            return await res.json();
        } catch (e) {
            console.error(e);
            return false;
        }
    }

    /**
     * Delete chat
     *
     * @param {string|false} uuid Chat UUID
     * @param {object} params Query params
     * @return {Promise<boolean>}
     */
    async deleteChat(uuid = false, params = {}) {
        if (!uuid && !this.#chat_uuid) {
            return true;
        }
        try {
            const query = urlSearchParams(params);
            const res = await fetch(`${this.#endpoints.chats}/${uuid || this.#chat_uuid}${query}`, {
                method: 'DELETE',
                headers: {
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce
                }
            });
            // Reset
            if (!uuid || uuid === this.#chat_uuid) {
                this.changeChatTo(false);
            }
            return await res.json();
        } catch (e) {
            console.error(e);
            return false;
        }
    }

    /**
     * Clear chat
     *
     * @param {string|false} uuid Chat UUID
     * @param {object} params Query params
     * @return {Promise<boolean>}
     */
    async clearChat(uuid = false, params = {}) {
        if (!uuid && !this.#chat_uuid) {
            return false;
        }
        try {
            const query = urlSearchParams(params);
            const res = await fetch(`${LimbChatbot.rest.url}chats/${uuid || this.#chat_uuid}/clear${query}`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce,
                },
            });
            return res.ok;
        } catch (e) {
            console.error(e);
            return false;
        }
    }

    /**
     * Update conversation state
     *
     * @param {object} params Query params
     * @return {Promise<boolean>}
     */
    async updateConversationState(params = {}) {
        if (!this.#chat_uuid) {
            return false;
        }
        try {
            const query = urlSearchParams(params);
            const res = await fetch(`${this.#endpoints.chats}/${this.#chat_uuid}/conversation_state${query}`, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce,
                },
            });
            return res.ok;
        } catch (e) {
            console.error(e);
            return false;
        }
    }

    /**
     * Add user message
     *
     * @param {array[]} content Messages
     * @param {object} params Query params
     * @param {boolean} stream Whether to request streaming response
     * @return {Promise<void>}
     */
    async addMessage(content, params = {}, stream = false) {
        // Create chat if didn't, passing the message in the same request
        if (this.#chat_uuid === false) {
            const message = {
                role: 'user',
                content,
            };
            const res = await this.createChat(message, params, stream);
            if (!res.success) {
                // Chat isn't created
                const error = new Error(res.message);
                error.code = res.code;
                return Promise.reject(error);
            }
            // If streaming, return the Response object
            if (stream && res.response) {
                return res.response;
            }
            // Return the response data which contains chat and message information
            return res.data;
        }
        // Chat already exists, send message to existing chat
        const headers = {
            'Content-Type': 'application/json',
            'X-WP-Nonce': LimbChatbot.rest.nonce
        };

        // Set Accept and Connection headers based on stream parameter
        if (stream) {
            headers['Accept'] = 'text/event-stream';
            headers['Connection'] = 'keep-alive';
        } else {
            headers['Accept'] = 'application/json';
        }

        const response = await fetch(this.#endpoints.chatsMessages + urlSearchParams(params), {
            method: 'POST',
            headers,
            body: JSON.stringify({
                role: 'user',
                content,
            })
        });

        // If streaming, return the Response object directly
        if (stream) {
            return response;
        }

        // Otherwise, parse JSON as before
        const data = await response.json();
        if (!response.ok) {
            const error = new Error(data.message ? data.message : __("Something went wrong", 'limb-chatbot'));
            error.code = data.code;
            throw error;
        }
        return data;
    }

    /**
     * Run the chat
     *
     * @param {object} params Query params
     * @param {boolean} stream Whether to request a streaming (SSE) response
     * @return {Promise<object|Response>} Parsed JSON when stream is false, Response when stream is true
     */
    async runChat(params = {}, stream = false) {
        const headers = {
            'X-WP-Nonce': LimbChatbot.rest.nonce
        };

        if (stream) {
            headers['Content-Type'] = 'application/json';
            headers['Accept'] = 'text/event-stream';
            headers['Connection'] = 'keep-alive';
        } else {
            headers['Accept'] = 'application/json';
        }

        const fetchOptions = {
            method: 'POST',
            headers,
        };

        if (stream) {
            fetchOptions.body = JSON.stringify(params);
        }

        const res = await fetch(this.#endpoints.chatsRun + urlSearchParams(params), fetchOptions);

        if (stream) {
            return res;
        }

        const data = await res.json();
        if (!res.ok) {
            throw new Error(data.message ? data.message : "Something went wrong");
        }
        return data;
    }

    /**
     * Get live agent messages
     *
     * @param {string} since UTC datetime string (format: YYYY-MM-DD HH:mm:ss)
     * @param {object} params Query params
     * @return {Promise<{items: Array, total: number}|null>}
     */
    async getLiveAgentMessages(since, params = {}) {
        if (!this.#chat_uuid) {
            return null;
        }
        try {
            const query = urlSearchParams({since, ...params});
            const res = await fetch(`${LimbChatbot.rest.url}chats/${this.#chat_uuid}/live-agent/messages${query}`, {
                method: 'GET',
                cache: 'no-store', // Prevent caching
                headers: {
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce
                }
            });
            if (res.ok) {
                return await res.json();
            }
            return null;
        } catch (e) {
            console.error('Failed to get live agent messages:', e);
            return null;
        }
    }

    /**
     * Search parameter (recommendation)
     *
     * @param {number} parameterId Parameter ID
     * @param {string} searchKeyword Search keyword
     * @param {AbortSignal} [signal] Optional AbortSignal to cancel the request
     * @return {Promise<object|null>}
     */
    async searchParameter(parameterId, searchKeyword, signal = null) {
        if (!this.#chat_uuid || !parameterId) {
            return null;
        }

        try {
            const fetchOptions = {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    'X-WP-Nonce': LimbChatbot.rest.nonce,
                },
                body: JSON.stringify({
                    chat_uuid: this.#chat_uuid,
                    search_keyword: searchKeyword,
                }),
            };

            // Add signal if provided
            if (signal) {
                fetchOptions.signal = signal;
            }

            const res = await fetch(`${LimbChatbot.rest.url}parameters/${parameterId}/callback`, fetchOptions);

            if (!res.ok) {
                throw new Error(`HTTP error! status: ${res.status}`);
            }

            const data = await res.json();

            if (data.status === 'success' && Array.isArray(data.results)) {
                return data;
            }

            return null;
        } catch (e) {
            // Don't log AbortError as it's expected when cancelling
            if (e.name !== 'AbortError') {
                console.error('Search parameter error:', e);
            }
            return null;
        }
    }

    /**
     * Widget callback
     *
     * @param {string} widgetId Widget ID
     * @param {object} data Data
     * @return {Promise<boolean>}
     */
    async widgetCallback(widgetId, data) {
        try {
            const headers = {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'X-WP-Nonce': LimbChatbot.rest.nonce
            };

            const res = await fetch(`${LimbChatbot.rest.url}chatbots/${(this.#chatbot_uuid || 'default')}/widgets/${widgetId}/callback`, {
                method: 'POST',
                headers,
                body: JSON.stringify({
                    params: data
                })
            });

            return {
                success: res.ok,
                data: await res.json(),
            };
        } catch (e) {
            return {
                success: false,
                data: e,
            }
        }
    }
}
