
import { gptConfigStore, gptServerStore, homeStore,useAuthStore } from "@/store";
import { mlog,myTrim } from "./mjapi";
import { fetchSSE } from "./sse/fetchsse";
import axios from 'axios';
import { localGet, localSaveAny } from "./mjsave";
import { isNumber, isObject } from "@/utils/is";
import { t } from "@/locales";
import { ChatMessage } from "gpt-tokenizer/esm/GptEncoding";
import { chatSetting } from "./chat";
import { getToken } from '@/store/modules/auth/helper'


const getUrl=(url:string)=>{
    if(url.indexOf('http')==0) return url;
    if(gptServerStore.myData.OPENAI_API_BASE_URL){
        return `${ gptServerStore.myData.OPENAI_API_BASE_URL}${url}`;
    }
    return `/api${url}`;
}
export const gptGetUrl = getUrl
export const gptFetch=(url:string,data?:any,opt2?:any )=>{
    mlog('gptFetch', url  );
    let headers= {'Content-Type':'application/json','Authorization':'Bearer ' + getToken()}
    if(opt2 && opt2.headers ) headers= opt2.headers;

    headers={...headers,...getHeaderAuthorization()}
    return new Promise<any>((resolve, reject) => {
        let opt:RequestInit ={method:'GET'};
        opt.headers= headers ;
        if(opt2?.upFile ){
             opt.method='POST';
             opt.body=data as FormData ;
        }
        else if(data) {
            opt.body= JSON.stringify(data) ;
            opt.method='POST';
        }
        fetch(getUrl(url),  opt )
        .then(d=>d.json().then(d=> resolve(d))
        .catch(e=>reject(e)))
        .catch(e=>reject(e))
    })

}
 // 前端直传 cloudflare r2
function uploadR2(file: File) {
	return new Promise<any>((resolve, reject) => {
			//预签名
			axios.post(gptGetUrl("/pre_signed"), { file_name: file.name, content_type: file.type }, {
					headers: { 'Content-Type': 'application/json' }
			}).then(response => {
							if (response.data.status == "Success") {
									const signedUrl = response.data.data.up;
									//上传
									fetch(signedUrl, {
											method: 'PUT',
											body: file,
											headers: {
													'Content-Type': file.type,
											},
									}).then(res2 => {
											if (res2.ok) {
													console.log('Upload successful!', response.data.data.url);
													return resolve({ url: response.data.data.url });
											} else {
													return reject(res2)
											}
									}).catch(error => {
											return reject(error)
									});

							} else {
									return reject(response.data);
							}
					}
			).catch(error => reject(error));
	});
}

export const GptUploader =   ( _url :string, FormData:FormData )=>{

    //R2上传
    const upLoaderR2= ()=>{
        const file = FormData.get('file') as File;
		return uploadR2(file);
    }

    //执行上传
    const uploadNomalDo = (url:string, headers:any)=>{
        return new Promise<any>((resolve, reject) => {
                axios.post( url , FormData, {
                headers
            }).then(response =>  resolve(response.data )
            ).catch(error =>reject(error)  );
        })
    }

    //除R2外默认流程
    const uploadNomal= (url:string)=>{
        url= gptServerStore.myData.UPLOADER_URL? gptServerStore.myData.UPLOADER_URL :  gptGetUrl( url );
        let headers=   {'Content-Type': 'multipart/form-data' }
        if(gptServerStore.myData.OPENAI_API_BASE_URL && url.indexOf(gptServerStore.myData.OPENAI_API_BASE_URL)>-1  ) {
            headers={...headers,...getHeaderAuthorization()}

        }else{
            const authStore = useAuthStore()
            if( authStore.token ) {
                const  header2={ 'x-ptoken':  authStore.token };
                headers= {...headers, ...header2}
            }
        }
        return  uploadNomalDo(url,headers );

    }

    //处理上传流程
    const uploadType=   ( (homeStore.myData.session.uploadType??'') as string).toLocaleLowerCase() ;
    let headers=   {'Content-Type': 'multipart/form-data' }
    //R2
    if(uploadType=='r2' ){
        return upLoaderR2();
    //容器
    }else if( uploadType=='container' ) {
         const authStore = useAuthStore()
        if( authStore.token ) {
            const  header2={ 'x-ptoken':  authStore.token };
            headers= {...headers, ...header2}
        }
        let url= `/openapi${_url}`
        return  uploadNomalDo(url,headers );

    //前端API
    }else if( uploadType=='api' ) {
        headers={...headers,...getHeaderAuthorization()}
        let url= `${ gptServerStore.myData.OPENAI_API_BASE_URL}${_url}`
        return  uploadNomalDo(url,headers );

    //自定义链接
    }else if( uploadType=='myurl' ) {
        return  uploadNomalDo(_url,headers );
    }

    //默认上传流程
    if(homeStore.myData.session.isUploadR2){
    return upLoaderR2();
    }
    return uploadNomal( _url);
}

export const whisperUpload = ( FormData:FormData )=>{
    const url = gptGetUrl('/chat/audio');
    let headers=   {'Content-Type': 'multipart/form-data','Authorization':'Bearer ' + getToken()}
    headers={...headers,...getHeaderAuthorization()}
    return new Promise<any>((resolve, reject) => {
            axios.post( url , FormData, {
            headers
        }).then(response =>  resolve(response.data )
        ).catch(error =>reject(error)  );
    })
}

export const subGPT= async (data:any, chat:Chat.Chat )=>{
   let d:any;
   let action= data.action;
   //chat.myid=  `${Date.now()}`;
   if(  action=='gpt.dall-e-3' ){ //执行变化
       // chat.model= 'dall-e-3';

       let d= await gptFetch('/dall3', data.data);
       try{
            const rz : any= d.data[0];
            chat.text= rz.revised_prompt??`图片已完成`;
            chat.opt={imageUrl:rz.url } ;
            chat.loading = false;
            homeStore.setMyData({act:'updateChat', actData:chat });
       }catch(e){
            chat.text='失败！'+"\n```json\n"+JSON.stringify(d, null, 2)+"\n```\n";
            chat.loading=false;
            homeStore.setMyData({act:'updateChat', actData:chat });
       }

   }

}

interface subModelType{
    kid: string;
    message:any[]
    onMessage:(d:{text:string,isFinish:boolean})=>void
    onError?:(d?:any)=>void
    signal?:AbortSignal
    model?:string
    uuid?:string|number
}
function getHeaderAuthorization(){
    // if(!gptServerStore.myData.OPENAI_API_KEY){
    //     const authStore = useAuthStore()
    //     if( authStore.token ) return { 'x-ptoken':  authStore.token };
    //     return {}
    // }
    return {
        'Authorization': 'Bearer ' + getToken()
    }
}

export const getSystemMessage = (uuid?:number )=>{
    //KnowledgeCutOffDate
    let sysTem= gptConfigStore.myData.systemMessage;
    if( uuid ){
        const chatS= new chatSetting(uuid);
        sysTem= chatS.getGptConfig().systemMessage ;
    }
    if(  sysTem ) return sysTem;
    let model= gptConfigStore.myData.model;
    let producer= 'Please respond in concise and clear language, prioritizing Chinese.'
    const DEFAULT_SYSTEM_TEMPLATE = `${producer}
    Current model: ${model}
    Current time: ${ new Date().toLocaleString()}`
    return DEFAULT_SYSTEM_TEMPLATE;

}
export const subModel= async (opt: subModelType)=>{

    const model= opt.model?? ( gptConfigStore.myData.model);
    let max_tokens= gptConfigStore.myData.max_tokens;
    let temperature= 0.5;
    let top_p= 1;
    let presence_penalty= 0 , frequency_penalty=0;
    if(opt.uuid){
        const chatSet= new chatSetting( +opt.uuid);
        const gStore= chatSet.getGptConfig();
        temperature= gStore.temperature??temperature;
        top_p = gStore.top_p??top_p;
        presence_penalty = gStore.presence_penalty??presence_penalty;
        frequency_penalty = gStore.frequency_penalty??frequency_penalty;
        max_tokens= gStore.max_tokens;
    }
   
    let body ={
            max_tokens ,
            model ,
            temperature,
            top_p,
            presence_penalty ,frequency_penalty,
            "messages": opt.message
           ,stream:true
           ,kid:gptConfigStore.myData.kid
        }

        let headers=   {'Content-Type': 'application/json;charset=UTF-8',
                        'Authorization':'Bearer ' + getToken(),
                        'Accept': 'text/event-stream '}
        headers={...headers,...getHeaderAuthorization()}
        try {
            let url = "/chat/send"
            if(gptConfigStore.myData.kid){
                url = "/knowledge/send"
            }
        
         await fetchSSE( gptGetUrl(url),{
            method: 'POST',
            headers: headers,
            signal:opt.signal,
            onMessage: async (data:string)=> {
                 //mlog('🐞测试'  ,  data )  ;
                 if(data=='[DONE]') opt.onMessage({text:'',isFinish:true})
                 else {
                    try{
                        const obj= JSON.parse(data );
                        opt.onMessage({text:obj.choices[0].delta?.content??'' ,isFinish:obj.choices[0].finish_reason!=null })
                    }catch{
                        opt.onMessage({
                            text: data,
                            isFinish: false
                        });
                    }

                 }
            },
            onError(e ){
                //console.log('eee>>', e )
                mlog('❌未错误',e    )
                opt.onError && opt.onError(e)
            },
            body:JSON.stringify(body)
        });
     } catch (error ) {
        mlog('❌未错误2',error  )
        opt.onError && opt.onError(error)
     }
}

export const getInitChat = (txt:string )=>{
    let promptMsg: Chat.Chat= {
        dateTime: new Date().toLocaleString(),
        text:  txt ,
        inversion: true,
        error: false,
        conversationOptions: null,
        requestOptions: { prompt:txt, options: null },
        }
        return promptMsg;
}

export interface ttsType{
        model: string,
        input: string ,
        voice?: string,

}
export const subTTS = async (tts:ttsType )=>{
    if(!tts.voice) tts.voice='alloy';
    let url= getUrl('/speech');
    let headers=  {
        'Content-Type': 'application/json'
      }
     headers={...headers,...getHeaderAuthorization()}
    const response = await fetch(url, {
      method: 'POST',
      headers,
      body: JSON.stringify(tts),
    });

    if (!response.ok) {
      throw new Error(`API request failed with status ${response.status}`);
    }
    const audioData = await response.arrayBuffer();
    const contentType = response.headers.get('Content-Type')
    const blob = new Blob([audioData], { type: contentType??'audio/mpeg' });
    mlog('blob', blob);
    const saveID = await localSaveAny( blob );
    const pp= await bolbObj(blob );
    return { blob,saveID ,...pp };

}

export const bolbObj= ( blob:Blob )=>{
    return new Promise<{player:HTMLAudioElement,duration:number }>((resolve, reject) => {
        const player = new window.Audio();
        player.src = URL.createObjectURL(blob);

        player.addEventListener('loadedmetadata', () => {
            mlog('时长', player.duration);
            resolve({player,duration: player.duration });
        });
        player.addEventListener('error',(e )=>{
            reject(e )
        })
        player.load();
    })

}

function formatDate(): string[] {
  const today = new Date()
  const year = today.getFullYear()
  const month = today.getMonth() + 1
  const lastDay = new Date(year, month, 0)
  const formattedFirstDay = `${year}-${month.toString().padStart(2, '0')}-01`
  const formattedLastDay = `${year}-${month.toString().padStart(2, '0')}-${lastDay.getDate().toString().padStart(2, '0')}`
  return [formattedFirstDay, formattedLastDay]
}

//

export const  gptUsage=async ()=>{

    // fetch(getUrl(url),  opt )
    //     .then(d=>d.json().then(d=> resolve(d))
    //     .catch(e=>reject(e)))
    //     .catch(e=>reject(e))
    const [startDate, endDate] = formatDate();
    const urlUsage = `/v1/dashboard/billing/usage?start_date=${startDate}&end_date=${endDate}`
    const usageData = await gptFetch(urlUsage);
    const billData = await gptFetch('/v1/dashboard/billing/subscription');

    const usage = Math.round(usageData.total_usage) / 100
     mlog('gpt', usage , billData  );
     //remaining = subscriptionData.system_hard_limit_usd - totalUsage;
     return {usage,remaining:Math.round( (billData.hard_limit??billData.hard_limit_usd*100) - usageData.total_usage ) / 100 ,hard_limit_usd:billData.hard_limit_usd } ;

}

export const openaiSetting= ( q:any )=>{
    //mlog()
    mlog('setting', q )
    if(q.settings){
        mlog('q.setting', q.settings )
        try {
            let obj = JSON.parse( q.settings );
            const url = obj.url ?? undefined;
            const key = obj.key ?? undefined;
            //let setQ= { }
            gptServerStore.setMyData(  {OPENAI_API_BASE_URL:url, MJ_SERVER:url, OPENAI_API_KEY:key,MJ_API_SECRET:key } )
            blurClean();
            gptServerStore.setMyData( gptServerStore.myData );

        } catch (error) {

        }
    }
    else if(isObject(q)){
        mlog('setting2', q )
        gptServerStore.setMyData(  q )
        //gptServerStore.setMyData( gptServerStore.myData );
        blurClean();
        gptServerStore.setMyData( gptServerStore.myData );

    }

}
export const blurClean= ()=>{
  mlog('blurClean');
  gptServerStore.myData.OPENAI_API_BASE_URL =myTrim( myTrim(gptServerStore.myData.OPENAI_API_BASE_URL.trim(),'/'), '\\' );
  gptServerStore.myData.OPENAI_API_KEY = gptServerStore.myData.OPENAI_API_KEY.trim();
  gptServerStore.myData.MJ_SERVER =myTrim( myTrim( gptServerStore.myData.MJ_SERVER.trim(),'/'),'\\');
  gptServerStore.myData.MJ_API_SECRET = gptServerStore.myData.MJ_API_SECRET.trim();
  gptServerStore.myData.UPLOADER_URL=  myTrim( myTrim( gptServerStore.myData.UPLOADER_URL.trim(),'/'),'\\');
}

export const countTokens= async ( dataSources:Chat.Chat[], input:string ,uuid:number )=>{
    const chatSet= new chatSetting(uuid);
    const myStore= chatSet.getGptConfig();
    let rz={system:0,input:0 ,history:0,remain:330,modelTokens:'4k',planOuter:myStore.max_tokens  }
    const model =myStore.model;
    const max= getModelMax(model );
    let unit= 1024;
    rz.modelTokens= `${max}k`
    //cl100k_base.encode(input)

    const encode= await encodeAsync();
    rz.input = encode(input).length;
    rz.system = encode(getSystemMessage() ).length;
    const encodeChat = await encodeChatAsync();
    const msg= await getHistoryMessage(  dataSources,1 ) ;
    rz.history= msg.length==0?0: encodeChat(msg, model.indexOf('gpt-4')>-1? 'gpt-4':'gpt-3.5-turbo').length
    //
    rz.remain = unit *max- rz.history- rz.planOuter- rz.input- rz.system;

    return rz ;
}
const getModelMax=( model:string )=>{
    let max=4;
    model= model.toLowerCase();
    if( model.indexOf('8k')>-1  ){
        return 8;
    }else if( model.indexOf('16k')>-1 || model=='gpt-3.5-turbo-1106' || model=='gpt-3.5-turbo-0125' ){
        return 16;
    }else if( model.indexOf('32k')>-1  ){
        return 32;
    }else if( model.indexOf('64k')>-1  ){
        return 64;
    }else if( model.indexOf('128k')>-1
    || model=='gpt-4-1106-preview'
    || model=='gpt-4-0125-preview'){
        return 128;
    }else if( model.indexOf('gpt-4')>-1  ){
        max=8;
    }else if( model.toLowerCase().includes('claude-3') ){
        //options.maxModelTokens = 120*1024;
        //options.maxResponseTokens = 4096
        return 120;
    }

    return max;
}

export const encodeAsync = async ( ) => {
  const { encode } = await import('gpt-tokenizer');

  return encode;//(str).length;
};
export const encodeChatAsync = async ( ) => {
  const { encodeChat } = await import('gpt-tokenizer');

  return encodeChat;//(obj,model ).length;
};


export const getHistoryMessage= async (dataSources:Chat.Chat[],loadingCnt=1 ,start=1000)=>{
    let i=0;
    let rz: ChatMessage[] = [];
    //const loadingCnt= 1;// 1就是没有loading，3 就是有loading
    let istart = (isNumber( start)&& start>=0 )? Math.min(start  ,   dataSources.length - loadingCnt ):  dataSources.length- loadingCnt  ;
    mlog('istart',istart, start);
    for( let ii=  istart  ; ii>=0 ; ii-- ){ //let o of dataSources.value
        if(i>=gptConfigStore.myData.talkCount) break;
        i++;

        let o = dataSources[ii];
        //mlog('o',ii ,o);
        let content= o.text;
        if( o.inversion && o.opt?.images && o.opt.images.length>0 ){
            //获取附件信息 比如 图片 文件等
            try{
               let str =  await localGet(  o.opt.images[0]) as string;
               let fileBase64= JSON.parse(str) as string[];
               let arr =  fileBase64.filter( (ff:string)=>ff.indexOf('http')>-1);
               if(arr.length>0) content = arr.join(' ')+' '+ content ;

               mlog(t('mjchat.attr') ,o.opt.images[0] , content );
            }catch(ee){
            }
        }

        //mlog('d',gptConfigStore.myData.talkCount ,i ,o.inversion , o.text);
        rz.push({content , role: !o.inversion ? 'assistant' : 'user'});
    }
    rz.reverse();
    mlog('rz',rz);
    return rz ;
}
