import i18n from "./i18nMessages.js";
const baseUrl = "https://emiapi.reynafamily.com";
//const baseUrl = "http://localhost:3000";
const requestErrorCooldownMs = 30000;
const profileFailureCooldownMs = 60000;
const recentRequestErrors = {};
const failedProfileCache = {};
const getCurrentLanguage = () => (i18n?.locale || "en").toLowerCase();

const normalizePath = (path = "") => {
    return path.replace(/[a-f0-9]{24}/gi, ":id");
};

const logRequestErrorOnce = (key, message) => {
    const now = Date.now();
    if (recentRequestErrors[key] && now - recentRequestErrors[key] < requestErrorCooldownMs) return;
    recentRequestErrors[key] = now;
    console.error(message);
};

const getFallbackForPath = (path = "") => {
    if (path.startsWith("/post/") || path === "/post/") return [];
    return {};
};

const parseJsonResponse = async (response, path = "", fallback) => {
    const text = await response.text();
    if (!text) return fallback;
    try {
        return JSON.parse(text);
    } catch (error) {
        const normalizedPath = normalizePath(path);
        logRequestErrorOnce(`invalid-json:${normalizedPath}`, `Invalid JSON from ${normalizedPath}: ${text.slice(0, 200)}`);
        return fallback;
    }
};

let getCall = async (path = "", params = {}) => {
    let queryParams = "?";
    Object.keys(params).forEach(p => {
        queryParams += p + "=" + params[p] + "&"
    });
    let localBaseUrl = global.baseUrl ?? baseUrl;
    const fallback = getFallbackForPath(path);
    return fetch(localBaseUrl + path + queryParams, {
        method: 'GET',
        mode: 'cors',
        credentials: 'include',
        headers: {
            'Content-Type': 'application/json',
            'Accept-Language': getCurrentLanguage(),
            'x-app-language': getCurrentLanguage(),
        }
    }).then(async (response) => {
        const normalizedPath = normalizePath(path);
        if (!response.ok) {
            logRequestErrorOnce(`GET:${normalizedPath}:${response.status}`, `GET ${normalizedPath} failed: ${response.status}`);
            return fallback;
        }
        return parseJsonResponse(response, path, fallback);
    }).catch((error) => {
        const normalizedPath = normalizePath(path);
        logRequestErrorOnce(`GET:${normalizedPath}:network`, `GET ${normalizedPath} network error: ${error?.message || error}`);
        return fallback;
    })
}

let deleteCall = async (path = "", params = {}) => {
    let queryParams = "?";
    Object.keys(params).forEach(p => {
        queryParams += p + "=" + params[p] + "&"
    });
    let localBaseUrl = global.baseUrl ?? baseUrl;
    const fallback = {};
    return fetch(localBaseUrl + path + queryParams, {
        method: 'DELETE',
        mode: 'cors',
        credentials: 'include',
        headers: {
            'Content-Type': 'application/json',
            'Accept-Language': getCurrentLanguage(),
            'x-app-language': getCurrentLanguage(),
        }
    }).then(async (response) => {
        const normalizedPath = normalizePath(path);
        if (!response.ok) {
            logRequestErrorOnce(`DELETE:${normalizedPath}:${response.status}`, `DELETE ${normalizedPath} failed: ${response.status}`);
            return fallback;
        }
        return parseJsonResponse(response, path, fallback);
    }).catch((error) => {
        const normalizedPath = normalizePath(path);
        logRequestErrorOnce(`DELETE:${normalizedPath}:network`, `DELETE ${normalizedPath} network error: ${error?.message || error}`);
        return fallback;
    })
}

let postCall = async (path, params) => {
    let localBaseUrl = global.baseUrl ?? baseUrl;
    const fallback = {};
    return fetch(localBaseUrl + path, {
        method: 'POST',
        mode: 'cors',
        credentials: 'include',
        body: JSON.stringify(params),
        headers: {
            'Content-Type': 'application/json',
            'Accept-Language': getCurrentLanguage(),
            'x-app-language': getCurrentLanguage(),
        }
    }).then(async (response) => {
        const normalizedPath = normalizePath(path);
        if (!response.ok) {
            logRequestErrorOnce(`POST:${normalizedPath}:${response.status}`, `POST ${normalizedPath} failed: ${response.status}`);
            return fallback;
        }
        return parseJsonResponse(response, path, fallback);
    }).catch((error) => {
        const normalizedPath = normalizePath(path);
        logRequestErrorOnce(`POST:${normalizedPath}:network`, `POST ${normalizedPath} network error: ${error?.message || error}`);
        return fallback;
    })
}

let CurrentUserId;
let CurrentProfile;
let CurrentProfileData;
let userNameCache = {}; //save this on localstorage
let working_on = {}; //Promises
const emptyProfileData = (id = "") => ({
    _id: id || "",
    profile: {
        firstName: "",
        lastName: "",
        photo: "",
        description: "",
        language: "en",
    },
    data: {},
    following: [],
});
let getProfileFromCache = async (id, refresh=false) => {
    if(!id) console.trace();
    const lastFailure = failedProfileCache[id] || 0;
    if (!refresh && lastFailure && Date.now() - lastFailure < profileFailureCooldownMs) {
        return emptyProfileData(id);
    }
    if (userNameCache[id] && !refresh)
        return userNameCache[id];
    if (working_on[id] && !refresh)
        return working_on[id];
    working_on[id] = getCall("/user/" + id).then((profile) => {
        const hasRealProfile = !!(profile && profile._id);
        const safeProfile = hasRealProfile ? { ...emptyProfileData(id), ...profile, profile: { ...emptyProfileData(id).profile, ...(profile.profile || {}) } } : emptyProfileData(id);
        if (hasRealProfile) {
            userNameCache[id] = safeProfile;
            delete failedProfileCache[id];
        } else {
            failedProfileCache[id] = Date.now();
        }
        delete working_on[id];
        return safeProfile;
    }).catch(() => {
        failedProfileCache[id] = Date.now();
        delete working_on[id];
        return emptyProfileData(id);
    });
    return working_on[id];
}

const API = {
    isLoggedIn: async () => {
        return getCall().then((data) => {
            //console.log("isLoggedIn", data)
            if (data && data.status && data.status === 'ok') {
                CurrentUserId = data.userInfo._id;
                CurrentProfile = data.profileInfo._id;
                return true;
            }
            CurrentUserId = '';
            CurrentProfile = '';
            CurrentProfileData = undefined;
            return false;
        })
    },
    logIn: async (username, password) => {
        return postCall("/login", {
            username,
            password
        }).then((data) => {
            if (data && data.status === "ok") {
                CurrentUserId = data.user_sid;
                CurrentProfile = data.profile_id;
            }
            return data;
        })
    },
    logInWithPasswordToken: async (token) => {
        return postCall("/password/token-login", { token }).then((data) => {
            if (data && data.status === "ok") {
                CurrentUserId = data.user_sid;
                CurrentProfile = data.profile_id;
            }
            return data;
        });
    },
    async logout(){
        console.log("Logging out...")
        return getCall("/logout").then(()=>{
            CurrentUserId = '';
            CurrentProfile = '';
            CurrentProfileData = undefined;
        });
    },
    async signup(username, password, email, profile){
        return postCall("/signup", {
            username,
            password,
            email,
            profile
        }).then((data) => {
            //console.log(data)
            if (data && data.status === "ok") {
                CurrentUserId = data.user_sid;
                CurrentProfile = data.profile_id;
            }
            return data;
        });
    },
    async subscribe(subscription){
        return postCall("/subscribe", subscription);
    },
    resetPassword(email){
        return postCall("/resetPassword", {username: email});
    },
    changePassword(newPassword){
        return postCall("/resetPassword", {password: newPassword});
    },
    currentUserId() {
        if (!CurrentUserId) this.isLoggedIn(); //replace with cookie
        return CurrentUserId;
    },
    addPaymentCard(cardInfo){
        return postCall("/payment/card", {cardInfo});
    },
    //Posts
    getPosts(userid) {
        if (userid) return getCall("/post/usr/" + userid).then((data) => Array.isArray(data) ? data : []);
        return getCall("/post/").then((data) => Array.isArray(data) ? data : []);
    },
    getPostsByTag(tag) {
        return getCall("/post/tag/" + tag);
    },
    getPostsWithTag(userid, tag = "images") {
        if (userid) return getCall("/post/usr/" + userid + "/" + tag);
        return getCall("/post/" + tag);
    },
    getPost(postid) {
        return getCall("/post/" + postid);
    },
    getVideo(videoId) {
        return getCall("/post/video/" + videoId);
    },
    registerToken(token) {
        return postCall("/token/", {token});
    },
    deletePost(postid){
        return deleteCall("/post/" + postid);
    },
    updatePost(post){
        return postCall("/post/" + post._id, {content: post.content});
    },
    translatePost(postid, targetLang) {
        return postCall("/post/translate", { postid, targetLang });
    },
    newPost(content, toProfile, isNews) {
        //Content is expected to be a string.
        let params = { content };
        if(isNews) params.nonOrganicType = "News"
        if (toProfile && CurrentUserId !== toProfile)
            params.toProfile = toProfile;
        return postCall("/post/", params);
    },
    newPostReaction(postid) {
        return postCall("/post/react", { postid });
    },
    removePostReaction(postid) {
        return postCall("/post/unreact", { postid });
    },
    newPostBookmark(postid) {
        return postCall("/post/bookmark", { postid });
    },
    removePostBookmark(postid) {
        return postCall("/post/unbookmark", { postid });
    },
    newPostComment(postid, content) {
        return postCall("/post/comment/", { postid, content });
    },
    newCommentReaction(postid, commentDate) {
        return postCall("/post/comment/react", { postid, commentDate });
    },
    removeCommentReaction(postid, commentDate) {
        return postCall("/post/comment/unreact", { postid, commentDate });
    },
    //Invitations
    newInvitation(name, email){
        return postCall("/user/invite", { name, email });
    },
    getInvitation(email){
        return getCall("/invite/" + email );
    },
    //Profiles
    async getMe() {
        if (!CurrentProfileData)
            if(!CurrentProfile) await this.isLoggedIn();
            CurrentProfileData = await this.getUserProfile(CurrentProfile, true);
        return CurrentProfileData;
    },
    getMyProfiles() {
        return getCall("/user/mine");
    },
    getMyProfile() {
        return getCall("/user/" + CurrentProfile);
    },
    updateMyProfile(profile, data) {
        if (CurrentProfile) {
            delete userNameCache[CurrentProfile];
            delete failedProfileCache[CurrentProfile];
        }
        return postCall("/user/myProfile", {profile, data});
    },
    markNotificationsViewed() {
        if (CurrentProfile) {
            delete userNameCache[CurrentProfile];
            delete failedProfileCache[CurrentProfile];
        }
        return postCall("/user/notifications/viewed", {});
    },
    searchProfiles(query){
        return getCall("/user/search", query ? {query} : {}).then((data)=>{
            if(data.status == "ok"){
                data.profiles.forEach((p)=>{
                    userNameCache[p._id] = p;
                });
                return data
            }
        });
    },
    changeProfile(profileid){
        return postCall("/changeProfile", {profileid}).then((profile) =>{
            CurrentProfile = profileid;
            CurrentProfileData = profile;
            return profile;
        });
    },
    getUserProfile(profileid, refresh=false) {
        return getProfileFromCache(profileid, refresh).then((profile) => profile || emptyProfileData(profileid));
    },
    deleteProfile(profileid){
        return deleteCall("/user/" + profileid);
    },
    currentProfileId() {
        if (!CurrentProfile) this.isLoggedIn(); //replace with cookie
        return CurrentProfile;
    },
    followProfile(profileid){
        return getCall("/user/" + profileid + "/follow");
    },
    unfollowProfile(profileid){
        return getCall("/user/" + profileid + "/unfollow");
    },
    setDataValue(key, value){
        if (CurrentProfile && userNameCache[CurrentProfile]) {
            if (!userNameCache[CurrentProfile].data) userNameCache[CurrentProfile].data = {};
            userNameCache[CurrentProfile].data[key] = value;
        }
        if (CurrentProfileData) {
            if (!CurrentProfileData.data) CurrentProfileData.data = {};
            CurrentProfileData.data[key] = value;
        }
        return postCall("/user/setData", {key, value});
    },
    //Groups
    newGroup(title, subtitle, description, isPrivate=false, isCourse=false, photo='') {
        return postCall("/user/groups", {
            profile: {
                firstName: title,
                lastName: subtitle,
                description,
                photo
            },
            isPrivate,
            isCourse
        });
    },
    getRecentGroups() {
        return getCall("/user/groups");
    },
    getFollowingGroups() {
        return getCall("/user/groups/following");
    },
    searchGroups(query){
        return getCall("/user/groups/search", query ? {query} : {}).then((data)=>{
            if(data.status == "ok"){
                data.groups.forEach((p)=>{
                    userNameCache[p._id] = p;
                });
                return data
            }
        });
    },
    getCourses() {
        return getCall("/user/courses");
    },
    getRecentCourses() {
        return getCall("/posts/course/recent");
    },
    searchCourses(query){
        return getCall("/user/groups/search", query ? {query, courses: true} : {courses: true}).then((data)=>{
            if(data.status == "ok"){
                data.groups.forEach((p)=>{
                    userNameCache[p._id] = p;
                });
                return data
            }
        });
    },
    getGroup(groupid) {
        return getCall("/user/groups/" + groupid);
    },
    async subscribeToGroup(groupid){
        delete userNameCache[groupid];
        return await getCall("/user/groups/" + groupid + "/subscribe");
    },
    acceptGroupRequest(profileid, groupid){
        delete userNameCache[groupid];
        return postCall("/user/groups/accept", {profileid, groupid});
    },
    rejectGroupRequest(profileid, groupid){
        delete userNameCache[groupid];
        return postCall("/user/groups/reject", {profileid, groupid});
    },
    unsubscribeToGroup(groupid){
        delete userNameCache[groupid];
        return getCall("/user/groups/" + groupid + "/unsubscribe");
    },
    //Payments
    paymentIntent(userid, price, description){
        return postCall("/payments/intent", {userid, price, description});
    },
    paymentRegister(userid, result){
        return postCall("/payments/register/", {userid, result});
    },
    //Chat
    getChatMessages(limit = 100) {
        return getCall("/chat/messages", { limit, lang: getCurrentLanguage() }).then((data) => Array.isArray(data?.messages) ? data.messages : []);
    },
    sendChatMessage(text) {
        return postCall("/chat/messages", { text, sourceLang: getCurrentLanguage() });
    },
    getChatActiveUsers() {
        return getCall("/chat/active").then((data) => Array.isArray(data?.activeUsers) ? data.activeUsers : []);
    },
    pingChatPresence() {
        return postCall("/chat/ping", {}).then((data) => Array.isArray(data?.activeUsers) ? data.activeUsers : []);
    },
    // Live captions
    getLiveCaptions(sinceSequence, limit = 40) {
        const params = { limit };
        if (Number.isFinite(sinceSequence)) params.sinceSequence = sinceSequence;
        return getCall("/live-captions/stream", params).then((data) => ({
            latestSequence: Number.isFinite(data?.latestSequence) ? data.latestSequence : 0,
            captions: Array.isArray(data?.captions) ? data.captions : [],
            availableLanguages: Array.isArray(data?.availableLanguages) ? data.availableLanguages : [],
        }));
    }
}

export default API;
