import { useState,useCallback, useEffect } from "react";
const APPLICATION_JSON = 'application/json';
const CONTENT_TYPE = 'Content-Type';





//Interfaces
export interface HttpBuilder{
  baseUrl:string;
  defaultApplyError:(error:any) => void;
  getToken:() => string | null;
  refreshToken:(respones:Response) => void;
  logoutAction:() => void;
}





export interface RequestConfi<TData> {
  url: string,
  method?: 'get' | 'post' | 'put' | 'delete',
  header?: Map<string,string>,
  auth?: boolean,
  state?: 'one' | 'multi',
  preventRefreshToken?:boolean,
  applyData : (reponse:HttpResponse<TData>) => void,
  dependinces?: any[],
  applyError?:(error:any) => void
}




export interface RequestParams {
  query?: Object,
  pathParams?: string[],
  body?:any
}


export interface HttpResponse<T> {
  data: T,
  status:number,
  statusText:string,
  headers: Headers
}


export interface AuthStoreBuilder {
  getToke:() => string | null;
  setToken:(token:string) => void;
  removeToken:() => void;
}

export interface AuthState<TUser> {
  isLogin:boolean;
  user:TUser | null;
}






  



// Default values
const defaultCreateHttp: HttpBuilder = {
    baseUrl: '',
    defaultApplyError:(error:any) => {},
    getToken:() => null,
    refreshToken:(res:Response) => {},
    logoutAction:() => {},
  }




  const defaultRequestConfig:RequestConfi<any> = {
      url: '',
      method:'get',
      header: new Map<string,string>(),
      auth: false,
      state: 'one',
      preventRefreshToken:false,
      applyData: (response:HttpResponse<any>) => {throw 'you have to provide applyData property ...'},
      dependinces: [],  
  }


  const defaultRequestParams:RequestParams = {
      query: {},
      pathParams: [],
      body: null,
    }





  export const httpProviderBuilder = function(createHttpParams:HttpBuilder = defaultCreateHttp){

    createHttpParams = {...defaultCreateHttp,...createHttpParams};

    const  {baseUrl,defaultApplyError,getToken,refreshToken,logoutAction:logout} = createHttpParams;

    return <TResut = any> (reqConfig:RequestConfi<TResut> = defaultRequestConfig) => {

    if (!reqConfig.applyError) reqConfig.applyError = defaultApplyError;

    reqConfig = {...defaultRequestConfig,...reqConfig};

    const [isLoading,setIsLoading] = useState<boolean>(false);
    const [error,setError] = useState<any>(null);


    const sendRequest = useCallback(async (params: RequestParams = defaultRequestParams) => {
      params = {...defaultRequestParams,...params};
      if (isLoading && reqConfig.state === 'one') return;

      setIsLoading(true);
      setError(null);
      const variablesInUrl = (params?.pathParams && params.pathParams?.length > 0) ? '/' + params.pathParams.join('/') : '';

      let queryParams = '';

      if (params?.query && Object.entries(params.query).length > 0){
        queryParams = '?';
        
        Object.entries(params.query).forEach(item => {
          queryParams += `${item[0]}=${item[1]}&`
        });

        queryParams.slice(-1);
      }

        const reqHeader: HeadersInit = new Headers();

        (params?.body && !(params.body instanceof FormData)) && reqHeader.append(CONTENT_TYPE,APPLICATION_JSON);

        if (reqConfig.header){
          reqConfig.header.forEach((value:string,key:string) => {
            reqHeader.append(key,value);
          });
        }

        if (reqConfig.auth){
          const jwt = getToken();
          if (!jwt) logout();
          reqHeader.append('Authorization', `Bearer ${jwt}`);
        }


        let bodyBuilder: BodyInit | null | undefined;

        if (params.body){

          if (reqHeader.get(CONTENT_TYPE) === APPLICATION_JSON){
            bodyBuilder = JSON.stringify(params.body);
          }
          else if(params.body instanceof FormData){
            bodyBuilder = params.body
          }
        }

        try {
          const response = await fetch(baseUrl + reqConfig.url + variablesInUrl + queryParams,{
            method: reqConfig.method!.toUpperCase(),
            headers: reqHeader,
            body: bodyBuilder,
          });

          if (response.status === 401){
            logout();
          }


          if (response.status >= 400){
            throw await response.json();
          }

          if (reqConfig.auth && !reqConfig.preventRefreshToken){
            refreshToken(response);
          }



          let data:any;
          try{
            data = (await response.json()) as TResut;
          }catch(err){

            try{
              data = (await response.text()) as TResut;
            }
            catch(err2){
              data = undefined;
            }
          }

          const httpResponse:HttpResponse<TResut> = {
            data:data,
            headers: response.headers,
            status:response.status,
            statusText:response.statusText
          }

          reqConfig.applyData(httpResponse);

        }catch(error){
          setError(error);
        }finally {
          setIsLoading(false);
        }



      

    },[isLoading, ...reqConfig.dependinces!])


    useEffect(() =>{ 
      if (error)
        reqConfig.applyError!(error);
    },[error]);


    return {
      sendRequest,
      error,
      isLoading,
    }



  }
}


