import { AxiosResponse } from "axios";
import { useHTTP } from "hooks";
import { Interaction } from "models";
import { componentIDToString } from "pbHelpers/Component";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useGlobalState } from "providers/StateContainer";
import { createContext, PropsWithChildren, useContext } from "react";
import { has } from "utils/types";
import { pondURL } from "./pond";

export interface IInteractionsAPIContext {
  addInteraction: (device: number, settings: pond.IInteractionSettings, otherTeam?: string) => Promise<AxiosResponse<pond.AddInteractionResponse>>;
  addMultiInteractions: (device: number, settings: pond.MultiInteractionSettings, otherTeam?: string) => Promise<AxiosResponse<pond.AddMultiInteractionsResponse>>;
  addInteractionToComponents: (
    fullComponentLocations: string[],
    settings: pond.IInteractionSettings,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.AddInteractionToComponentsResponse>>;
  updateInteraction: (device: number, settings: pond.IInteractionSettings, otherTeam?: string) => Promise<AxiosResponse<pond.UpdateInteractionResponse>>;
  updateInteractionPondSettings: (
    device: number,
    interaction: string,
    settings: pond.IInteractionPondSettings,
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.UpdateInteractionPondSettingsResponse>>;
  removeInteraction: (device: number, interaction: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveInteractionResponse>>;
  listInteractionsByDevice: (device: number | string, demo?: boolean, otherTeam?: string) => Promise<Interaction[]>;
  listInteractionsByComponent: (
    device: number,
    component: quack.ComponentID,
    demo?: boolean,
    otherTeam?: string
  ) => Promise<Interaction[]>;
  setAlertInteractions: (
    alerts: pond.AlertData[],
    otherTeam?: string
  ) => Promise<AxiosResponse<pond.SetAlertInteractionsResponse>>;
}

export const InteractionsAPIContext = createContext<IInteractionsAPIContext>(
  {} as IInteractionsAPIContext
);

interface Props {}

//used to validate interaction fields before being sent outbound
function sanitizeInteraction(interaction: pond.IInteractionSettings): pond.IInteractionSettings {
  let sanitizedInteraction = interaction;
  if (sanitizedInteraction.conditions) {
    sanitizedInteraction.conditions.forEach((condition, i) => {
      if (condition.value && sanitizedInteraction.conditions) {
        sanitizedInteraction.conditions[i].value = Math.round(condition.value);
      }
    });
  }
  return sanitizedInteraction;
}

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

  const addInteraction = (device: number, settings: pond.IInteractionSettings, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    return new Promise<AxiosResponse<pond.AddInteractionResponse>>((resolve, reject) => {
      post<pond.AddInteractionResponse>(
        pondURL("/devices/" + device + "/interactions" + (view ? "?as=" + view : "")),
        sanitizeInteraction(settings)
      ).then(resp => {
        resp.data = pond.AddInteractionResponse.fromObject(resp)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  /**
   * this function is intended for use to add a single interaction to multiple components by way of an object that they are linked to (bin, gate etc)
   * @param fullComponentLocations the full id of a component including the device ie: [4:9-5-12, 4:9-5-13, 3:9-5-12]
   * @param settings the settings of the interaction to add
   * @returns
   */
  const addInteractionToComponents = (
    fullComponentLocations: string[],
    settings: pond.IInteractionSettings,
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url = pondURL(
      "/interactions/forComponents?componentIds=" +
        fullComponentLocations.toString() +
        (view ? "&as=" + view : "")
    )
    return new Promise<AxiosResponse<pond.AddInteractionToComponentsResponse>>((resolve, reject) => {
      post<pond.AddInteractionToComponentsResponse>(url,sanitizeInteraction(settings)).then(resp => {
        resp.data = pond.AddInteractionToComponentsResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const addMultiInteractions = (device: number, settings: pond.MultiInteractionSettings, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    let url = pondURL("/devices/" + device + "/interactions/multi" + (view ? "?as=" + view : ""))
    return new Promise<AxiosResponse<pond.AddMultiInteractionsResponse>>((resolve, reject) => {
      post<pond.AddMultiInteractionsResponse>(url,settings).then(resp => {
        resp.data = pond.AddMultiInteractionsResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateInteraction = (device: number, settings: pond.IInteractionSettings, otherTeam?: string) => {
    const view = otherTeam ? otherTeam : as
    let url =       pondURL(
      "/devices/" +
        device +
        "/interactions?keys=" +
        [device] +
        "&types=" +
        ["device"] +
        (view ? "&as=" + view : "")
    )
    return new Promise<AxiosResponse<pond.UpdateInteractionResponse>>((resolve, reject) => {
      put<pond.UpdateInteractionResponse>(url,sanitizeInteraction(settings)).then(resp => {
        resp.data = pond.UpdateInteractionResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  const updateInteractionPondSettings = (
    device: number,
    interaction: string,
    settings: pond.IInteractionPondSettings,
    otherTeam?: string
  ) => {
    const view = otherTeam ? otherTeam : as
    let url = pondURL(
      "/devices/" +
        device +
        "/interactions/" +
        interaction +
        "/interactionPondSettings?keys=" +
        [device] +
        "&types=" +
        ["device"] +
        (view ? "&as=" + view : "")
    )
    return new Promise<AxiosResponse<pond.UpdateInteractionPondSettingsResponse>>((resolve, reject) => {
      put<pond.UpdateInteractionPondSettingsResponse>(url, sanitizeInteraction(settings)).then(resp => {
        resp.data = pond.UpdateInteractionPondSettingsResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

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

  const listInteractionsByDevice = (
    device: number | string,
    demo: boolean = false,
    otherTeam?: string
  ): Promise<Interaction[]> => {
    const view = otherTeam ? otherTeam : as
    return new Promise((resolve, reject) => {
      get(pondURL("/devices/" + device + "/interactions" + (view ? "?as=" + view : ""), demo))
        .then((response: any) => {
          if (!has(response, "data.interactions")) {
            return resolve([]);
          }
          let interactions: Interaction[] = [];
          response.data.interactions.forEach((interaction: Interaction) => {
            if (
              interaction.settings.nodeOne > interaction.settings.nodeTwo &&
              interaction.settings.nodeTwo !== 0
            ) {
              //flip operator and send negative comparitor to save
              interaction.settings.conditions.forEach(condition => {
                //coming back from the backend as a string for some reason
                if (condition.comparison.toString() === "RELATIONAL_OPERATOR_GREATER_THAN") {
                  condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN;
                } else {
                  condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN;
                }
                condition.value = condition.value * -1;
              });
            }
            interactions.push(Interaction.any(interaction));
          });
          return resolve(interactions);
        })
        .catch((error: any) => {
          return reject(error);
        });
    });
  };

  const listInteractionsByComponent = (
    device: number,
    component: quack.ComponentID,
    demo: boolean = false,
    otherTeam?: string
  ): Promise<Interaction[]> => {
    const view = otherTeam ? otherTeam : as
    return new Promise((resolve, reject) => {
      get(
        pondURL(
          "/devices/" +
            device +
            "/components/" +
            componentIDToString(component) +
            "/interactions" +
            (view ? "?as=" + view : ""),
          demo
        )
      )
        .then((response: any) => {
          if (!has(response, "data.interactions")) {
            return resolve([]);
          }
          let interactions: Interaction[] = [];
          response.data.interactions.forEach((interaction: Interaction) => {
            if (
              interaction.settings.nodeOne > interaction.settings.nodeTwo &&
              interaction.settings.nodeTwo !== 0
            ) {
              //flip operator and send negative comparitor to save
              interaction.settings.conditions.forEach(condition => {
                //coming back from the backend as a string for some reason
                if (condition.comparison.toString() === "RELATIONAL_OPERATOR_GREATER_THAN") {
                  condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN;
                } else {
                  condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN;
                }
                condition.value = condition.value * -1;
              });
            }
            interactions.push(Interaction.any(interaction));
          });
          return resolve(interactions);
        })
        .catch((error: any) => {
          return reject(error);
        });
    });
  };

  const setAlertInteractions = (
    alerts: pond.AlertData[],
    otherTeam?: string
  ): Promise<AxiosResponse<pond.SetAlertInteractionsResponse>> => {
    const view = otherTeam ? otherTeam : as
    let url = pondURL("/interactions/setAlerts" + (view ? "?as=" + view : ""))
    return new Promise<AxiosResponse<pond.SetAlertInteractionsResponse>>((resolve, reject) => {
      post<pond.SetAlertInteractionsResponse>(url, { data: alerts }).then(resp => {
        resp.data = pond.SetAlertInteractionsResponse.fromObject(resp.data)
        return resolve(resp)
      }).catch(err => {
        return reject(err)
      })
    })
  };

  return (
    <InteractionsAPIContext.Provider
      value={{
        addInteraction,
        addMultiInteractions,
        setAlertInteractions,
        addInteractionToComponents,
        updateInteraction,
        updateInteractionPondSettings,
        removeInteraction,
        listInteractionsByDevice,
        listInteractionsByComponent
      }}>
      {children}
    </InteractionsAPIContext.Provider>
  );
}

export const useInteractionsAPI = () => useContext(InteractionsAPIContext);
