import { useHTTP, usePermissionAPI } from "hooks";
import { createContext, PropsWithChildren, useContext } from "react";
import { pond } from "protobuf-ts/pond";
import { has, or } from "utils/types";
import { User, binScope } from "models";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
import { dateRange } from "providers/http";
import { useGlobalState } from "providers";
import { quack } from "protobuf-ts/quack";

export interface IBinAPIContext {
  addBin: (bin: pond.BinSettings, otherTeam?: string) => Promise<AxiosResponse<pond.AddBinResponse>>;
  updateBin: (key: string, bin: pond.BinSettings, otherTeam?: string) => Promise<AxiosResponse<pond.UpdateBinResponse>>;
  bulkBinUpdate: (bins: pond.BinSettings[], otherTeam?: string) => Promise<AxiosResponse<pond.BulkBinUpdateResponse>>;
  updateBinStatus: (
    key: string,
    binStatus: pond.BinStatus,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.UpdateBinResponse>>;
  removeBin: (key: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveBinResponse>>;
  getBin: (key: string, otherTeam?: string) => Promise<AxiosResponse<pond.Bin>>;
  addComponent: (
    bin: string,
    device: number,
    component: string,
    preferences?: pond.BinComponentPreferences,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.AddBinComponentResponse>>;
  removeComponent: (
    bin: string,
    component: string,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.RemoveBinComponentResponse>>;
  removeAllComponents: (bin: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveAllBinComponentsResponse>>;
  updateComponentPreferences: (
    bin: string,
    component: string,
    preferences: pond.BinComponentPreferences,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.UpdateBinComponentPreferencesResponse>>;
  getBinPageData: (binKey: string, userKey: string, showErrors?: boolean, otherTeam?: string) => Promise<AxiosResponse<pond.GetBinPageDataResponse>>;
  listBins: (
    limit: number,
    offset: number,
    order?: "asc" | "desc",
    orderBy?: string,
    search?: string,
    otherTeam?: string,
    asRoot?: boolean,
    numerical?: boolean,
    keys?: string[],
    types?: string[]
  ) => Promise<AxiosResponse<pond.ListBinsResponse>>;
  listBinsAndData: (
    limit: number,
    offset: number,
    order?: "asc" | "desc",
    orderBy?: string,
    search?: string,
    otherTeam?: string,
    asRoot?: boolean,
    keys?: string[],
    types?: string[]
  ) => Promise<AxiosResponse<pond.ListBinsAndDataResponse>>;
  listHistory: (
    id: string,
    limit: number,
    offset: number
  ) => Promise<AxiosResponse<pond.ListBinHistoryResponse>>;
  listHistoryBetween: (
    id: string,
    limit: number,
    start: string,
    end: string
  ) => Promise<AxiosResponse<pond.ListBinHistoryResponse>>;
  listBinStatus: (
    key: string,
    startDate: any,
    endDate: any,
    limit: number,
    offset: number,
    order?: "asc" | "desc",
    asRoot?: boolean
  ) => Promise<AxiosResponse<pond.ListBinStatusResponse>>;
  listBinComponents: (key: string) => Promise<AxiosResponse<pond.ListBinComponentsResponse>>;
  listBinComponentsMeasurements: (
    key: string,
    start: string,
    end: string,
    showErrors?: boolean,
    allNodes?: boolean,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.ListBinComponentsMeasurementsResponse>>;
  updateBinPermissions: (
    key: string,
    users: User[]
  ) => Promise<AxiosResponse<pond.UpdateBinResponse>>;
  getBinMetrics: (search?: string, otherTeam?: string) => Promise<AxiosResponse<pond.GetBinMetricsResponse>>;
  listBinLiters: (
    key: string,
    start: string,
    end: string,
    moisture: number,
    plenum: string,
    cable: string,
    pressure: string,
    fan?: string, // the fan controller is optional so that if they don't have one the fan is assumed on
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.ListBinLiterResponse>>;
  listBinPrefs: (key: string) => Promise<AxiosResponse<pond.ListBinComponentPreferencesResponse>>;
  updateTopNodes: (
    key: string,
    newTopNodes: pond.NewTopNode[],
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.UpdateTopNodesResponse>>;
  listBinMeasurements: (
    key: string,
    startDate: any,
    endDate: any,
    limit: number,
    offset: number,
    order: string,
    measurementType?: number | quack.MeasurementType,
    keys?: string[],
    types?: string[],
    showErrors?: boolean,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.ListObjectMeasurementsResponse>>;
  getBinsTrendData: (
    bins: string[],
    days: number
  ) => Promise<AxiosResponse<pond.GetBinsTrendDataResponse>>;
}

export const BinAPIContext = createContext<IBinAPIContext>({} as IBinAPIContext);

interface Props {}

export default function BinProvider(props: PropsWithChildren<Props>) {
  const { children } = props;
  const { get, post, put, del } = useHTTP();
  const permissionAPI = usePermissionAPI();
  const [{ as }] = useGlobalState();

  const addBin = (bin: pond.BinSettings, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    const url = "/bins" + (view ? "?as=" + view : "")
    return new Promise<AxiosResponse<pond.AddBinResponse>>((resolve, reject)=>{
      post<pond.AddBinResponse>(pondURL(url), bin).then(resp => {
        resp.data = pond.AddBinResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateBin = (key: string, bin: pond.BinSettings, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    const url = "/bins/" + key + (view ? "?as=" + view : "")
    return new Promise<AxiosResponse<pond.UpdateBinResponse>>((resolve, reject)=>{
      put<pond.UpdateBinResponse>(pondURL(url), bin).then(resp => {
        resp.data = pond.UpdateBinResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const bulkBinUpdate = (bins: pond.BinSettings[], otherTeam?: string) => {
    let url = "/bulkBins/update"
    const view = otherTeam ? otherTeam : as
    if (view) url = url + "?as=" + view
    return new Promise<AxiosResponse<pond.BulkBinUpdateResponse>>((resolve, reject)=>{
      put<pond.BulkBinUpdateResponse>(pondURL(url), { bins: bins }).then(resp => {
        resp.data = pond.BulkBinUpdateResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateBinStatus = (key: string, binStatus: pond.BinStatus, otherTeam?: string) => {
    let url = "/bins/" + key + "/status"
    const view = otherTeam ? otherTeam : as
    if (view) url = url + "?as=" + view
    return new Promise<AxiosResponse<pond.UpdateBinStatusResponse>>((resolve, reject)=>{
      put<pond.UpdateBinStatusResponse>(pondURL(url), binStatus).then(resp=>{
        resp.data = pond.UpdateBinStatusResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err=>{
        return reject(err)
      })
    })
  };

  const removeBin = (key: string, otherTeam?: string) => {
    let url = "/bins/" + key
    const view = otherTeam ? otherTeam : as
    if (view) url = url + "?as=" + view
    return new Promise<AxiosResponse<pond.RemoveBinResponse>>((resolve, reject) => {
      del<pond.RemoveBinResponse>(pondURL(url)).then(resp => {
        resp.data = pond.RemoveBinResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getBin = (key: string, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    const url = "/bins/" + key + (view ? "?as=" + view : "")
    return new Promise<AxiosResponse<pond.Bin>>((resolve, reject)=>{
      get<pond.Bin>(pondURL(url))
      .then(resp => {
        resp.data = pond.Bin.fromObject(resp.data)
        resolve(resp)
      })
      .catch(err => {
        reject(err)
      })
    }) 
  };

  const addComponent = (
    bin: string,
    device: number,
    component: string,
    preferences?: pond.BinComponentPreferences,
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url = "/bins/" + bin + "/addComponent/" + device + "/" + component;
    if (view) {
      url =
        url +
        "?as=" +
        view +
        (preferences ? "&binPref=" + preferences.type + "&fillNode=" + preferences.node : "");
    } else if (preferences) {
      url = url + "?binPref=" + preferences.type + "&fillNode=" + preferences.node;
    }
    return new Promise<AxiosResponse<pond.AddBinComponentResponse>>((resolve, reject)=>{
      post<pond.AddBinComponentResponse>(pondURL(url)).then(resp => {
        resp.data = pond.AddBinComponentResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const removeComponent = (bin: string, component: string, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    let url = "/bins/" + bin + "/removeComponent/" + component;
    if (view) url = url + "?as=" + view;
    return new Promise<AxiosResponse<pond.RemoveBinComponentResponse>>((resolve, reject)=>{
      post<pond.RemoveBinComponentResponse>(pondURL(url)).then(resp => {
        resp.data = pond.RemoveBinComponentResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const removeAllComponents = (bin: string, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    let url = "/bins/" + bin + "/removeAllComponents";
    if (view) url = url + "?as=" + view;
    return new Promise<AxiosResponse<pond.RemoveAllBinComponentsResponse>>((resolve, reject)=>{
      post<pond.RemoveAllBinComponentsResponse>(pondURL(url)).then(resp => {
        resp.data = pond.RemoveAllBinComponentsResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateComponentPreferences = (
    bin: string,
    component: string,
    preferences: pond.BinComponentPreferences,
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url = "/bins/" + bin + "/updateComponent/" + component + "/preferences";
    if (view) url = url + "?as=" + view;
    interface request {
      preferences: Object;
    }
    let body: request = { preferences: pond.BinComponentPreferences.toObject(preferences) };
    return new Promise<AxiosResponse<pond.UpdateBinComponentPreferencesResponse>>((resolve,reject)=>{
      put<pond.UpdateBinComponentPreferencesResponse>(pondURL(url), body).then(resp => {
        resp.data = pond.UpdateBinComponentPreferencesResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getBinPageData = (binKey: string, userKey: string, showErrors = true, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    const url = "/bins/" +
    binKey +
    "/users/" +
    userKey +
    (showErrors ? "?showErrors=true" : "?showErrors=false") +
    (view ? "&as=" + view : "")
    return new Promise<AxiosResponse<pond.GetBinPageDataResponse>>((resolve, reject)=>{
      get<pond.GetBinPageDataResponse>(pondURL(url))
      .then(resp => {
        resp.data = pond.GetBinPageDataResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
      
    }) 
  };

  const listBins = (
    limit: number,
    offset: number,
    order?: "asc" | "desc",
    orderBy?: string,
    search?: string,
    otherTeam?: string,
    asRoot?: boolean,
    numerical?: boolean,
    keys?: string[],
    types?: string[]
  ) => {
    return new Promise<AxiosResponse<pond.ListBinsResponse>>((resolve, reject)=>{
    const view = otherTeam ? otherTeam : as
      get<pond.ListBinsResponse>(
        pondURL(
          "/bins" +
            "?limit=" +
            limit +
            "&offset=" +
            offset +
            ("&order=" + or(order, "asc")) +
            ("&by=" + or(orderBy, "key")) +
            (numerical ? "&numerical=true" : "") +
            (view ? "&as=" + view : "") +
            (asRoot ? "&asRoot=" + asRoot.toString() : "") +
            (search ? "&search=" + search : "") +
            (keys ? "&keys=" + keys.join(",") : "") +
            (types ? "&types=" + types.join(",") : "")
        )
      ).then(resp => {
        resp.data = pond.ListBinsResponse.fromObject(resp.data);
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listBinsAndData = (
    limit: number,
    offset: number,
    order?: "asc" | "desc",
    orderBy?: string,
    search?: string,
    otherTeam?: string,
    asRoot?: boolean,
    keys?: string[],
    types?: string[]
  ) => {
    const view = otherTeam ? otherTeam : as
    const url = "/binsAndData" +
    "?limit=" +
    limit +
    "&offset=" +
    offset +
    ("&order=" + or(order, "asc")) +
    ("&by=" + or(orderBy, "key")) +
    (view ? "&as=" + view : "") +
    (asRoot ? "&asRoot=" + asRoot.toString() : "") +
    (search ? "&search=" + search : "") +
    (keys ? "&keys=" + keys.join(",") : "") +
    (types ? "&types=" + types.join(",") : "")

    return new Promise<AxiosResponse<pond.ListBinsAndDataResponse>>((resolve, reject)=>{
      get<pond.ListBinsAndDataResponse>(pondURL(url)).then(resp => {
        resp.data = pond.ListBinsAndDataResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listBinStatus = (
    key: string,
    startDate: any,
    endDate: any,
    limit: number,
    offset: number,
    order?: "asc" | "desc",
    asRoot?: boolean
  ) => {
    const url = "/bins/" +
    key +
    "/status" +
    dateRange(startDate, endDate) +
    "?limit=" +
    limit +
    "&offset=" +
    offset +
    ("&order=" + or(order, "asc")) +
    (asRoot ? "&asRoot=" + asRoot.toString() : "")
    return new Promise<AxiosResponse<pond.ListBinStatusResponse>>((resolve, reject)=>{
      get<pond.ListBinStatusResponse>(
        pondURL(url)
      ).then(resp => {
        resp.data = pond.ListBinStatusResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listBinComponents = (key: string) => {
    return new Promise<AxiosResponse<pond.ListBinComponentsResponse>>((resolve, reject)=>{
      get<pond.ListBinComponentsResponse>(pondURL("/bins/" + key + "/components"))
      .then(resp => {
        resp.data = pond.ListBinComponentsResponse.fromObject(resp.data)
        return resolve(resp)
      })
      .catch(err => {
        return reject(err)
      })
    })
  };

  const listBinComponentsMeasurements = (
    key: string,
    start: string,
    end: string,
    showErrors = false,
    allNodes = false,
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url = "/bins/" +
    key +
    "/components/measurements?start=" +
    start +
    "&end=" +
    end +
    (view ? "&as=" + view : "") +
    (showErrors ? "&showErrors=true" : "&showErrors=false") +
    (allNodes ? "&allNodes=true" : "&allNodes=false")

    return new Promise<AxiosResponse<pond.ListBinComponentsMeasurementsResponse>>((resolve, reject) => {
      get<pond.ListBinComponentsMeasurementsResponse>(pondURL(url))
      .then(resp => {
        resp.data = pond.ListBinComponentsMeasurementsResponse.fromObject(resp.data)
        return resolve(resp)
      })
      .catch(err => {
        return reject(err)
      })
      }
    ) 
  };

  const listHistory = (id: string, limit: number, offset: number) => {
    let url = "/bins/" + id + "/history?limit=" + limit + "&offset=" + offset
    return new Promise<AxiosResponse<pond.ListBinHistoryResponse>>((resolve, reject)=>{
      get<pond.ListBinHistoryResponse>(
        pondURL(url)
      ).then(resp => {
        resp.data = pond.ListBinHistoryResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listHistoryBetween = (id: string, limit: number, start: string, end: string) => {
    let url = "/bins/" + id + "/historyBetween?limit=" + limit + "&start=" + start + "&end=" + end
    return new Promise<AxiosResponse<pond.ListBinHistoryResponse>>((resolve, reject)=>{
      get<pond.ListBinHistoryResponse>(
        pondURL(url)
      ).then(resp => {
        resp.data = pond.ListBinHistoryResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateBinPermissions = (key: string, users: User[]) => {
    return permissionAPI.updatePermissions(binScope(key.toString()), users);
  };

  const getBinMetrics = (search?: string, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    let url = "/metrics/bins" + (search ? "?search=" + search : "")
    if (view) {
      if (search){
        url = "/metrics/bins/?search=" + search + "&as=" + view
      }
      url = "/metrics/bins/?as=" + view;
    }
    return new Promise<AxiosResponse<pond.GetBinMetricsResponse>>((resolve, reject)=>{
      get<pond.GetBinMetricsResponse>(pondURL(url)).then(resp => {
        resp.data = pond.GetBinMetricsResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listBinLiters = (
    key: string,
    start: string,
    end: string,
    moisture: number,
    plenum: string,
    cable: string,
    pressure: string,
    fan?: string,
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    const url = "/bins/" +
    key +
    "/liters?start=" +
    start +
    "&end=" +
    end +
    "&moisture=" +
    moisture +
    "&plenum=" +
    plenum +
    "&cable=" +
    cable +
    "&pressure=" +
    pressure +
    (fan ? "&fan=" + fan : "") +
    (view ? "&as=" + view : "")

    return new Promise<AxiosResponse<pond.ListBinLiterResponse>>((resolve, reject)=>{
      get<pond.ListBinLiterResponse>(pondURL(url))
      .then(resp => {
        resp.data = pond.ListBinLiterResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listBinPrefs = (key: string, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    const url = "/bins/" + key + "/componentPreferences?as=" + view
    return new Promise<AxiosResponse<pond.ListBinComponentPreferencesResponse>>((resolve, reject)=>{
      get<pond.ListBinComponentPreferencesResponse>(pondURL(url))
      .then(resp => {
        resp.data = pond.ListBinComponentPreferencesResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateTopNodes = (key: string, newTopNodes: pond.NewTopNode[], otherTeam?: string) => {
    let body: pond.TopNodeList = pond.TopNodeList.create({ newTopNodes: newTopNodes });
    const view = otherTeam ? otherTeam : as
    let url = "/bins/" + key + "/updateTopNodes" + (view ? "?as=" + view : "")
    return new Promise<AxiosResponse<pond.UpdateTopNodesResponse>>((resolve,response) => {
      post<pond.UpdateTopNodesResponse>(pondURL(url),body).then(resp => {
        resp.data = pond.UpdateTopNodesResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return response(err)
      })
    })
  };

  const listBinMeasurements = (
    key: string,
    startDate: any,
    endDate: any,
    limit: number,
    offset: number,
    order: string,
    measurementType?: number | quack.MeasurementType,
    keys?: string[],
    types?: string[],
    showErrors?: boolean,
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    const url = pondURL(
      "/objects/" +
        key +
        "/1/unitMeasurements" +
        dateRange(startDate, endDate) +
        "&limit=" +
        limit +
        "&offset=" +
        offset +
        "&order=" +
        order +
        (measurementType ? "&type=" + measurementType : "") +
        (view ? "&as=" + view : "") +
        (keys ? "&keys=" + keys : "") +
        (types ? "&types=" + types : "") +
        (showErrors ? "&showErrors=true" : "&showErrors=false")
    );
    return new Promise<AxiosResponse<pond.ListObjectMeasurementsResponse>>((resolve, reject) => {
      get<pond.ListObjectMeasurementsResponse>(url)
      .then(resp => {
        resp.data = pond.ListObjectMeasurementsResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    }) 
  };

  const getBinsTrendData = (bins: string[], days: number) => {
    return get<pond.GetBinsTrendDataResponse>(
      pondURL("/binTrends?bins=" + bins.toString() + "&days=" + days + (as ? "&as=" + as : ""))
    );
  };

  return (
    <BinAPIContext.Provider
      value={{
        addBin,
        updateBin,
        bulkBinUpdate,
        updateBinStatus,
        removeBin,
        listBinComponents,
        listBinComponentsMeasurements,
        listBinStatus,
        getBin,
        addComponent,
        removeComponent,
        removeAllComponents,
        updateComponentPreferences,
        getBinPageData,
        listBins,
        listBinsAndData,
        listHistory,
        updateBinPermissions,
        getBinMetrics,
        listBinLiters,
        listHistoryBetween,
        listBinPrefs,
        updateTopNodes,
        listBinMeasurements,
        getBinsTrendData
      }}>
      {children}
    </BinAPIContext.Provider>
  );
}

export const useBinAPI = () => useContext(BinAPIContext);
