import { useHTTP, usePermissionAPI } from "hooks";
import { Component, deviceScope, User } from "models";
import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond";
import { AxiosResponse } from "axios";
import { useGlobalState } from "providers";
import moment from "moment";
import { or } from "utils/types";
import { dateRange } from "providers/http";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";

export interface IDeviceAPIContext {
  add: (name: string, description: string, backpack: pond.BackpackSettings, otherTeam?: string) => Promise<AxiosResponse<pond.AddDeviceResponse>>;
  update: (id: number, settings: pond.DeviceSettings, otherTeam?: string) => Promise<AxiosResponse<pond.UpdateDeviceResponse>>;
  remove: (id: number, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveDeviceResponse>>;
  get: (id: number | string, demo?: boolean, keys?: string[], types?: string[], otherTeam?: string) => Promise<AxiosResponse<pond.Device>>
  getPageData: (
    id: number | string,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.GetDevicePageDataResponse>>;
  getMulti: (ids: number[] | string[], otherTeam?: string) => Promise<AxiosResponse<pond.GetMultiDeviceResponse>>;
  detectI2C: (id: number, otherTeam?: string) => Promise<AxiosResponse<pond.DetectI2CResponse>>;
  detectOneWire: (id: number, port: number, otherTeam?: string) => Promise<AxiosResponse<pond.DetectOneWireResponse>>
  listFoundComponents: (id: number, otherTeam?: string) => Promise<AxiosResponse<pond.ListFoundComponentsResponse>>
  getGeoJson: (id: number | string, demo?: boolean, otherTeam?: string) => Promise<any>;
  getMultiGeoJson: (ids: number[] | string[], otherTeam?: string) => Promise<any>;
  list: (
    limit: number,
    offset: number,
    order?: "asc" | "desc",
    orderBy?: string,
    search?: string,
    settings?: string,
    status?: string,
    ids?: number[] | string[] | Long[],
    asRoot?: boolean,
    comprehensive?: boolean,
    withMeasurements?: boolean,
    keys?: string[],
    types?: string[],
    fieldContains?: Map<string, string>,
    prefixSearch?: string,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.ListDevicesResponse>>;
  listForUser: (
    asRoot: boolean,
    user?: string,
    search?: string,
    hologram?: boolean,
    keys?: string[],
    types?: string[]
  ) => Promise<AxiosResponse<pond.ListDevicesResponse>>;
  claim: (id: number | Long, body: any) => Promise<any>;
  listHistory: (id: number, limit: number, offset: number) => Promise<any>;
  listCompleteHistory: (
    id: number,
    limit: number,
    offset: number,
    filters?: pond.ObjectType[]
  ) => Promise<AxiosResponse<pond.ListCompleteHistoryResponse>>;
  updatePermissions: (id: number | string, users: User[]) => Promise<any>;
  updatePreferences: (
    id: number | string,
    preferences: pond.DevicePreferences,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => Promise<any>;
  updateComponentPreferences: (
    id: number | string,
    component: string,
    preferences: pond.DeviceComponentPreferences,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.UpdateDeviceComponentPreferencesResponse>>;
  listDeviceComponentPreferences: (
    id: number | string,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.ListDeviceComponentPreferencesResponse>>;
  updateStatus: (
    id: number | string,
    status: pond.DeviceStatus,
    keys?: string[],
    types?: string[]
  ) => Promise<AxiosResponse<pond.UpdateDeviceStatusResponse>>;
  sync: (id: number) => Promise<any>;
  clearPending: (id: number) => Promise<any>;
  pause: (id: number) => Promise<any>;
  bulkPause: (
    ids: number[],
    setPaused: boolean
  ) => Promise<AxiosResponse<pond.BulkPauseDeviceResponse>>;
  bulkChangeDataCaps: (
    ids: number[],
    newCap: number
  ) => Promise<AxiosResponse<pond.BulkChangeDataCapsResponse>>;
  resume: (id: number) => Promise<any>;
  getUpgradeStatus: (id: number, keys?: string[], types?: string[], otherTeam?: string) => Promise<any>;
  loadBackpack: (id: number, backpack: number) => Promise<any>;
  setTags: (id: number, tags: string[]) => Promise<any>;
  setWifi: (
    id: number,
    gateway: string,
    password: string,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => Promise<any>;
  statistics: (
    keys?: string[],
    types?: string[]
  ) => Promise<any>;
  tag: (id: number, tag: string) => Promise<any>;
  untag: (id: number, tag: string) => Promise<any>;
  getDatacap: (id: number) => Promise<any>;
  isOverLimit: (id: number) => Promise<any>;
  isPaused: (id: number) => Promise<any>;
  setDatacap: (id: number, newCap: number) => Promise<any>;
  listJSONMeasurements: (
    id: number,
    components: Component[],
    startDate: any,
    endDate: any,
    limit: number,
    offset: number,
    order: string,
    orderBy: string,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.ListDeviceExportedMeasurementsResponse>>;
  listSimpleJSON: (device: number, otherTeam?: string) => Promise<any>;
  resetQuackCount: (id: number) => Promise<any>;
  resetQuackCountTx: (id: number) => Promise<any>;
  resetQuackCountTx1000: (id: number) => Promise<any>;
  linearMutation: (
    id: number,
    mutation: pond.LinearMutation
  ) => Promise<AxiosResponse<pond.DeviceLinearMutationResponse>>;
  getDataUsage: (id: number, start?: any) => Promise<any>;
  buyData: (
    id: number | string,
    MB: number,
    source?: string,
    keys?: string[],
    types?: string[]
  ) => Promise<any>;
  removeFoundComponents: (id: number, key: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveFoundComponentsResponse>>;
}

export const DeviceAPIContext = createContext<IDeviceAPIContext>({} as IDeviceAPIContext);

interface Props {}

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

  const add = (name: string, description: string, backpack: pond.BackpackSettings, otherTeam?: string) => {
    let url = pondURL("/devices")
    const view = otherTeam ? otherTeam : as
    if (view) url = pondURL("/devices?as=" + view)
    return new Promise<AxiosResponse<pond.AddDeviceResponse>>((resolve, reject) => {
      post<pond.AddDeviceResponse>(url, { name, description, backpack }).then(resp => {
        resp.data = pond.AddDeviceResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getDevice = (
    id: number | string,
    demo: boolean = false,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url = pondURL(
      "/devices/" +
        id + 
        (keys ? "?keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : ""),
      demo
    );
    if (view) {
      url = pondURL(
        "/devices/" +
          id + 
          "?as=" + view +
          (keys ? "&keys=" + keys.toString() : "") +
          (types ? "&types=" + types.toString() : ""),
        demo
      );
    }
    return new Promise<AxiosResponse<pond.Device>>((resolve, reject) => {
      get<pond.Device>(url).then(resp => {
        resp.data = pond.Device.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const statistics = (
    keys?: string[],
    types?: string[]
  ) => {
    let url = pondURL("/deviceStatistics");
    if (types && types.length > 0) {
      url = pondURL(
        "/deviceStatistics/" +
        (keys ? "?keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : "")
        )
      if (!types.includes("team") && as.length > 0) {
        url = pondURL(
          "/deviceStatistics/" +
          (keys ? "?keys=" + keys.toString() : "") +
          (types ? "&types=" + types.toString() : "") +
          ("&as=" + as)
        )
      }
    } else {
      if (as.length > 0) {
        url = pondURL(
          "/deviceStatistics/?as=" + as
        )
      }
    }
    return new Promise<AxiosResponse<pond.GetDeviceStatisticsResponse>>((resolve, reject) => {
      get<pond.GetDeviceStatisticsResponse>(url).then(resp => {
        resp.data = pond.GetDeviceStatisticsResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getDevicePageData = (
    id: number | string,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ): Promise<AxiosResponse<pond.GetDevicePageDataResponse>> => {
    const view = otherTeam ? otherTeam : as
    let url = pondURL(
      "/devicePageData/" +
        id +
        (keys ? "?keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : "")
    );
    if (view && !(types && types.length > 0 && types[0] === "team")) {
      url = pondURL(
        "/devicePageData/" +
          id +
          "?as=" +
          view +
          (keys ? "&keys=" + keys.toString() : "") +
          (types ? "&types=" + types.toString() : "")
      )
    }
    return new Promise<AxiosResponse<pond.GetDevicePageDataResponse>>((resolve, reject) => {
      get<pond.GetDevicePageDataResponse>(url).then(resp => {
        resp.data = pond.GetDevicePageDataResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getDeviceGeoJSON = (id: number | string, demo: boolean = false, otherTeam?: string) => {
    let url = pondURL("/devices/" + id + "/geojson", demo);
    const view = otherTeam ? otherTeam : as
    if (view) url = pondURL("/devices/" + id + "/geojson?as=" + view, demo)
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(url).catch(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    }) 
  };

  const getMulti = (ids: number[] | string[], otherTeam?: string) => {
    let idString = "";
    ids.forEach((id: number | string, i: number) => {
      if (i === 0) {
        idString = idString + id;
      } else {
        idString = idString + "," + id;
      }
    });
    const view = otherTeam ? otherTeam : as
    let url = pondURL("/multidevices?devices=" + idString + (view ? "&as=" + view : ""))
    return new Promise<AxiosResponse<pond.GetMultiDeviceResponse>>((resolve, reject) => {
      get<pond.GetMultiDeviceResponse>(url).then(resp => {
        resp.data = pond.GetMultiDeviceResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getMultiDevicesGeoJSON = (ids: number[] | string[], otherTeam?: string) => {
    let idString = "";
    ids.forEach((id: number | string, i: number) => {
      if (i === 0) {
        idString = idString + id;
      } else {
        idString = idString + "," + id;
      }
    });
    const view = otherTeam ? otherTeam : as
    let url = pondURL("/geojson/devices?devices=" + idString + (view ? "&as=" + view : ""))
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(url).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const list = (
    limit: number,
    offset: number,
    order?: "asc" | "desc",
    orderBy?: string,
    search?: string,
    settings?: string,
    status?: string,
    ids?: number[] | string[] | Long[],
    asRoot?: boolean,
    comprehensive?: boolean,
    withMeasurements?: boolean,
    keys?: string[],
    types?: string[],
    fieldContains?: Map<string, string>,
    prefixSearch?: string,
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    const url = pondURL(
      "/devices" +
        "?limit=" +
        limit +
        "&offset=" +
        offset +
        ("&order=" + (order ? order : "asc")) +
        ("&by=" + (orderBy ? orderBy : "deviceId")) +
        (search ? "&search=" + search : "") +
        (settings ? "&settings=" + settings : "") +
        (status ? "&status=" + status : "") +
        (ids ? "&ids=" + ids.join(",") : "") +
        (asRoot ? "&asRoot=" + asRoot.toString() : "") +
        (view && !(types && types.length > 0 && types[0] === "team") ? "&as=" + view : "") +
        (comprehensive ? "&comprehensive=" + comprehensive.toString() : "") +
        (withMeasurements ? "&measurements=" + withMeasurements.toString() : "") +
        (keys ? "&keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : "") +
        (fieldContains ? "&fieldContains=" + JSON.stringify(Object.fromEntries(fieldContains)) : "") +
        (prefixSearch ? "&prefixSearch=" + prefixSearch : "")
    );
    return new Promise<AxiosResponse<pond.ListDevicesResponse>>((resolve, reject)=>{
      get<pond.ListDevicesResponse>(url).then(resp => {
        resp.data = pond.ListDevicesResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      });
    })
  };

  const listForUser = (
    asRoot: boolean,
    user?: string,
    search?: string,
    hologram?: boolean,
    keys?: string[],
    types?: string[]
  ) => {
    const url = pondURL(
      "/devices" +
        (user ? "?as=" + user : "?asRoot=" + asRoot.toString()) +
        (search ? "&search=" + search : "") +
        (hologram ? "&hologram=" + hologram.toString() : "") +
        (keys ? "&keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : "")
    );
    return new Promise<AxiosResponse<pond.ListDevicesResponse>>((resolve, reject) => {
      get<pond.ListDevicesResponse>(url).then(resp => {
        resp.data = pond.ListDevicesResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const remove = (id: number, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    const url = pondURL("/devices/" + id + (view ? "?as=" + view : ""))
    return new Promise<AxiosResponse<pond.RemoveDeviceResponse>>((resolve, reject) => {
      del<pond.RemoveDeviceResponse>(url).then(resp => {
        resp.data = pond.RemoveDeviceResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const update = (id: number, settings: pond.DeviceSettings, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    const url = pondURL("/devices/" + id + "/update" + (view ? "?as=" + view : ""));
    return new Promise<AxiosResponse<pond.UpdateDeviceResponse>>((resolve, reject) => {
      put<pond.UpdateDeviceResponse>(url, settings).then(resp => {
        resp.data = pond.UpdateDeviceResponse.fromObject(resp)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const claim = (id: number | Long, body: any) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      put(pondURL("/devices/" + id), body).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listHistory = (id: number, limit: number, offset: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(pondURL("/devices/" + id + "/history?limit=" + limit + "&offset=" + offset)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listCompleteHistory = (
    id: number,
    limit: number,
    offset: number,
    filters?: pond.ObjectType[]
  ) => {
    let url = pondURL(
      "/devices/" +
        id +
        "/completehistory?limit=" +
        limit +
        "&offset=" +
        offset +
        (filters ? "&sources=" + filters.toString() : "")
    )
    return new Promise<AxiosResponse<pond.ListCompleteHistoryResponse>>((resolve, reject) => {
      get<pond.ListCompleteHistoryResponse>(url).then(resp => {
        resp.data = pond.ListCompleteHistoryResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateDevicePermissions = (id: number | string, users: User[]) => {
    return permissionAPI.updatePermissions(deviceScope(id.toString()), users);
  };

  const updatePreferences = (
    id: number | string,
    preferences: pond.DevicePreferences,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url =       pondURL(
      "/devices/" +
        id +
        "/preferences" +
        "?as=" +
        or(view, "") +
        (keys ? "&keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : "")
    )
    return new Promise<AxiosResponse>((resolve, reject) => {
      put(url,preferences).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateComponentPreferences = (
    id: number | string,
    component: string,
    preferences: pond.DeviceComponentPreferences,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url =       pondURL(
      "/devices/" +
        id +
        "/components/" +
        component +
        "/componentPreferences" +
        "?as=" +
        view +
        (keys ? "&keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : "")
    )
    return new Promise<AxiosResponse<pond.UpdateDeviceComponentPreferencesResponse>>((resolve, reject) => {
      put<pond.UpdateDeviceComponentPreferencesResponse>(url, preferences).then(resp => {
        resp.data = pond.UpdateDeviceComponentPreferencesResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listDeviceComponentPreferences = (
    id: number | string,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url = pondURL(
      "/devices/" +
        id +
        "/componentPreferences" +
        (keys ? "?keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : "")
    )
    if (view) {
      url = pondURL(
        "/devices/" +
          id +
          "/componentPreferences?as=" + view +
          (keys ? "&keys=" + keys.toString() : "") +
          (types ? "&types=" + types.toString() : "")
      )
    }
    return new Promise<AxiosResponse<pond.ListDeviceComponentPreferencesResponse>>((resolve, reject) => {
      get<pond.ListDeviceComponentPreferencesResponse>(url).then(resp => {
        resp.data = pond.ListDeviceComponentPreferencesResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateStatus = (
    id: number | string,
    status: pond.DeviceStatus,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url =       pondURL(
      "/devices/" +
        id +
        "/updateStatus" +
        "?as=" +
        view +
        (keys ? "&keys=" + keys.toString() : "") +
        (types ? "&types=" + types.toString() : "")
    )
    return new Promise<AxiosResponse<pond.UpdateDeviceStatusResponse>>((resolve, reject) => {
      put<pond.UpdateDeviceStatusResponse>(url, status).then(resp => {
        resp.data = pond.UpdateDeviceStatusResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const sync = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      post(pondURL("/devices/" + id + "/sync"), {}).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const clearPending = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      post(pondURL("/devices/" + id + "/clearPending"), {}).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const pause = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      post(pondURL("/devices/" + id + "/pause"), {}).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const bulkPause = (ids: number[], setPaused: boolean) => {
    let url = pondURL(
      "/bulkPauseDevices?ids=" +
        ids.toString() +
        (setPaused ? "&pause=" + setPaused.toString() : "")
    )
    return new Promise<AxiosResponse<pond.BulkPauseDeviceResponse>>((resolve, reject) => {
      post<pond.BulkPauseDeviceResponse>(url,{}).then(resp => {
        resp.data = pond.BulkPauseDeviceResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const bulkChangeDataCaps = (ids: number[], newCap: number) => {
    return new Promise<AxiosResponse<pond.BulkChangeDataCapsResponse>>((resolve, reject) => {
      post<pond.BulkChangeDataCapsResponse>(
        pondURL("/bulkChangeDataCap?ids=" + ids.toString() + "&dataCap=" + newCap),
        {}
        ).then(resp => {
          resp.data = pond.BulkChangeDataCapsResponse.fromObject(resp.data)
          return resolve(resp)
        }).catch(err => {
          return reject(err)
        })
      })
  };

  const resume = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      post(pondURL("/devices/" + id + "/resume"), {}).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getUpgradeStatus = (id: number, keys?: string[], types?: string[], otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    let a = view ? "?as=" + view : "";
    if (keys && keys.length > 0 && types && types.length > 0) {
      a = view ? a + "&keys=" + keys + "&types=" + types : "?keys=" + keys + "&types=" + types;
    }
    let url = pondURL("/devices/" + id + "/firmware" + a);
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(url).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const loadBackpack = (id: number, backpack: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      put(pondURL("/devices/" + id + "/load/" + backpack), {}).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const setTags = (id: number, tags: string[]) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      put(pondURL("/devices/" + id + "/tags"), { tags }).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const setWifi = (
    id: number,
    gateway: string,
    password: string,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let a = view ? "?as=" + view : "";
    if (keys && keys.length > 0 && types && types.length > 0) {
      a = view ? a + "&keys=" + keys + "&types=" + types : "?keys=" + keys + "&types=" + types;
    }
    return new Promise<AxiosResponse>((resolve, reject) => {
      put(pondURL("/devices/" + id + "/wifi" + a), { gateway, password }).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const tag = (id: number, tag: string) => {
    const keys = getContextKeys()
    const types = getContextTypes()
    let url = "/devices/" + id + "/tags/" + tag + "?keys=" + keys + "&types=" + types
    if (as && !keys.includes(as)) url += "&as=" + as
    return new Promise<AxiosResponse>((resolve, reject) => {
      put(pondURL(url), {}).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const untag = (id: number, tag: string) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      del(pondURL("/devices/" + id + "/tags/" + tag)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getDatacap = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(pondURL("/devices/" + id + "/datacap/")).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const isOverLimit = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(pondURL("/devices/" + id + "/overlimit/")).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const isPaused = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(pondURL("/devices/" + id + "/paused/")).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const setDatacap = (id: number, newCap: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      post(pondURL("/devices/" + id + "/datacap?limit=" + newCap)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const getDataUsage = (id: number, start?: any) => {
    if (start) {
      start = "?start=" + moment(start).toISOString();
    } else {
      start = "";
    }
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(pondURL("/devices/" + id + "/usage/" + start)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const listJSONMeasurements = (
    id: number,
    components: Component[],
    startDate: any,
    endDate: any,
    limit: number,
    offset: number,
    order: string,
    orderBy: string,
    otherTeam?: string
  ) => {
    let keyString = "";
    components.forEach((comp, i) => {
      if (i === 0) {
        keyString = keyString + comp.key();
      } else {
        keyString = keyString + "," + comp.key();
      }
    });
    const view = otherTeam ? otherTeam : as
    if (view) {
      return get<pond.ListDeviceExportedMeasurementsResponse>(
        pondURL(
          "/devices/" +
            id +
            "/measurements/exportComplex" +
            dateRange(startDate, endDate) +
            "&components=" +
            keyString +
            "&limit=" +
            limit +
            "&offset=" +
            offset +
            "&order=" +
            order +
            "&orderBy=" +
            orderBy +
            "&as=" +
            view
        )
      );
    }
    return get<pond.ListDeviceExportedMeasurementsResponse>(
      pondURL(
        "/devices/" +
          id +
          "/measurements/exportComplex" +
          dateRange(startDate, endDate) +
          "&components=" +
          keyString +
          "&limit=" +
          limit +
          "&offset=" +
          offset +
          "&order=" +
          order +
          "&orderBy=" +
          orderBy
      )
    );
  };

  const listSimpleJSON = (device: number, otherTeam?: string) => {
    let url = "/devices/" + device + "/measurements/exportSimple"
    const view = otherTeam ? otherTeam : as
    if(view) url = url + "?as=" + view
    return new Promise<AxiosResponse>((resolve, reject) => {
      get(pondURL(url)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const resetQuackCount = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      post(pondURL("/devices/" + id + "/resetCounts")).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const resetQuackCountTx = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      post(pondURL("/devices/" + id + "/resetCountTx")).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const resetQuackCountTx1000 = (id: number) => {
    return new Promise<AxiosResponse>((resolve, reject) => {
      post(pondURL("/devices/" + id + "/partialResetCountTx")).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const linearMutation = (id: number, mutation: pond.LinearMutation) => {
    return new Promise<AxiosResponse<pond.DeviceLinearMutationResponse>>((resolve, reject) => {
      post<pond.DeviceLinearMutationResponse>(
        pondURL("/devices/" + id + "/linearMutation"),
        mutation
        ).then(resp => {
          resp.data = pond.DeviceLinearMutationResponse.fromObject(resp.data)
          return resolve(resp)
        }).catch(err => {
          return reject(err)
        })
      }) 
  };

  const buyData = (
    id: number | string,
    MB: number,
    source?: string,
    keys?: string[],
    types?: string[],
    otherTeam?: string
  ) => {
    let url = "/devices/" + id + "/buyData?MB=" + MB;
    const view = otherTeam ? otherTeam : as
    if (view === team?.key()) url = url + "&team=" + team?.key();
    if (source) url = url + "&source=" + source;
    if (keys) url = url + "&keys=" + keys;
    if (types) url = url + "&types=" + types;
    return new Promise<AxiosResponse>((resolve, reject) => {
      put(pondURL(url)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const detectI2C = (id: number, otherTeam?: string) => {
    let url = "/devices/" + id + "/detectI2C";
    const view = otherTeam ? otherTeam : as
    if(view) url = url + "?as=" + view
    return new Promise<AxiosResponse<pond.DetectI2CResponse>>((resolve, reject) => {
      put<pond.DetectI2CResponse>(pondURL(url)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  }

  const detectOneWire = (id: number, port: number, otherTeam?: string) => {
    let url = "/devices/" + id + "/detectOneWire?port=" + port;
    const view = otherTeam ? otherTeam : as
    if(view) url = url + "?as=" + view
    return new Promise<AxiosResponse<pond.DetectOneWireResponse>>((resolve, reject) => {
      put<pond.DetectOneWireResponse>(pondURL(url)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  }

  const listFoundComponents = (id: number, otherTeam?: string) => {
    let url = "/devices/" + id + "/listScannedComponents";
    const view = otherTeam ? otherTeam : as
    if(view) url = url + "?as=" + view
    return new Promise<AxiosResponse<pond.ListFoundComponentsResponse>>((resolve, reject) => {
      get<pond.ListFoundComponentsResponse>(pondURL(url)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  }

  const removeFoundComponents = (id: number, key: string, otherTeam?: string) => {
        let url = "/devices/" + id + "/removeFoundComponents/" + key;
    const view = otherTeam ? otherTeam : as
    if(view) url = url + "?as=" + view
    return new Promise<AxiosResponse<pond.RemoveFoundComponentsResponse>>((resolve, reject) => {
      del<pond.RemoveFoundComponentsResponse>(pondURL(url)).then(resp => {
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  }

  return (
    <DeviceAPIContext.Provider
      value={{
        add,
        update,
        remove,
        get: getDevice,
        statistics,
        getPageData: getDevicePageData,
        getMulti,
        getGeoJson: getDeviceGeoJSON,
        getMultiGeoJson: getMultiDevicesGeoJSON,
        list,
        claim,
        listHistory,
        listCompleteHistory,
        updatePermissions: updateDevicePermissions,
        updateStatus,
        updatePreferences,
        clearPending,
        sync,
        pause,
        bulkPause,
        bulkChangeDataCaps,
        resume,
        getUpgradeStatus,
        loadBackpack,
        setTags,
        setWifi,
        tag,
        untag,
        getDatacap,
        isOverLimit,
        isPaused,
        setDatacap,
        listJSONMeasurements,
        listSimpleJSON,
        resetQuackCount,
        resetQuackCountTx,
        resetQuackCountTx1000,
        listForUser,
        linearMutation,
        getDataUsage,
        buyData,
        updateComponentPreferences,
        listDeviceComponentPreferences,
        detectI2C,
        detectOneWire,
        listFoundComponents,
        removeFoundComponents
      }}>
      {children}
    </DeviceAPIContext.Provider>
  );
}

export const useDeviceAPI = () => useContext(DeviceAPIContext);

// export const useDeviceWebsocket = (
//   id: number | string,
//   emitter: (m: any) => void,
//   rate: number = 0
// ) => useWebsocket("/devices/" + id, m => Device.any(JSON.parse(m.data)), emitter, rate);
