import {
  Button,
  Card,
  CardActions,
  CardContent,
  CardHeader,
  darken,
  Grid2 as Grid,
  IconButton,
  Slider,
  Theme,
  ToggleButton,
  ToggleButtonGroup,
  Tooltip,
  Typography
} from "@mui/material";
import { Settings } from "@mui/icons-material";
import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks";
import { cloneDeep } from "lodash";
import { Component, Device, Interaction } from "models";
import moment from "moment";
import { componentIDToString } from "pbHelpers/Component";
import {
  abbreviatedWeekdays,
  friendlyTimeOfDayDescriptor,
  interactionConditionText,
  interactionResultText,
  isSource,
  timeOfDayDescriptor
} from "pbHelpers/Interaction";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { canWrite } from "pbHelpers/Permission";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import React, { useEffect, useState } from "react";
import InteractionSettings from "./InteractionSettings";
import { makeStyles } from "@mui/styles";
import { useGlobalState } from "providers";

const useStyles = makeStyles((theme: Theme) => {
  return ({
    card: {
      backgroundColor: darken(theme.palette.background.paper, 0.05)
    },
    header: {
      padding: theme.spacing(1),
      paddingBottom: 0
    },
    content: {
      padding: `${theme.spacing(1)}px !important`
    },
    actions: {
      marginTop: "auto",
      display: "flex",
      flexDirection: "row",
      justifyContent: "flex-end",
      padding: theme.spacing(1),
      paddingTop: 0
    }
  })
});

interface Props {
  device: Device;
  component: Component;
  components: Component[];
  interactions: Interaction[];
  permissions: pond.Permission[];
  refreshCallback: () => void;
}

export default function InteractionsOverview(props: Props) {
  const classes = useStyles();
  const [{as}] = useGlobalState();
  const interactionsAPI = useInteractionsAPI();
  const { success, error } = useSnackbar();
  const { device, component, components, permissions, refreshCallback } = props;
  const prevComponents = usePrevious(components);
  const prevInteractions = usePrevious(props.interactions);
  const [interactions, setInteractions] = useState<Interaction[]>(props.interactions);
  const [dirtyInteractions, setDirtyInteractions] = useState<Map<number, boolean>>(new Map());
  const [mappedComponents, setMappedComponents] = useState<Map<string, Component>>(new Map());
  const [renderToggle, setRenderToggle] = useState(false);
  const [selectedInteraction, setSelectedInteraction] = useState<Interaction | undefined>(
    undefined
  );

  useEffect(() => {
    if (components !== prevComponents) {
      let initMappedComponents: Map<string, Component> = new Map();
      components.forEach((component: Component) => {
        initMappedComponents.set(component.locationString(), component);
      });
      setMappedComponents(initMappedComponents);
    }

    if (props.interactions !== prevInteractions) {
      setInteractions(cloneDeep(props.interactions));
    }
  }, [components, prevComponents, props.interactions, prevInteractions]);

  const openInteractionSettings = (interaction: Interaction) => {
    setSelectedInteraction(interaction);
  };

  const closeInteractionSettings = () => {
    setSelectedInteraction(undefined);
  };

  const interactionConditions = (
    source: Component | undefined,
    conditions: pond.IInteractionCondition[] | null | undefined,
    interactionIndex: number
  ) => {
    let sourceType = source && source.settings.type;
    let sourceSubtype = source && source.settings.subtype;
    let items: any[] = [];
    if (!conditions) return items;
    conditions.forEach((condition: pond.IInteractionCondition, conditionIndex: number) => {
      let measurementType = condition.measurementType;
      let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype);
      items.push(
        <Grid container size={{ sm: 12 }} key={conditionIndex} alignItems="center">
          <Grid size={{ xs: 12, sm: 6 }}>
            <Typography variant="caption" gutterBottom>
              {interactionConditionText(source, condition, conditionIndex > 0)}
            </Typography>
          </Grid>
          <Grid size={{ xs: 12, sm: 6 }}>
            {measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN ? (
              <ToggleButtonGroup
                value={measurement.toDisplay(condition.value ? condition.value : 0)}
                exclusive
                size="small"
                onChange={(_, value) =>
                  updateConditionValue(
                    source,
                    interactionIndex,
                    conditionIndex,
                    parseInt(value) === 1 ? 1 : 0
                  )
                }>
                <ToggleButton value={0} size="small">
                  {measurement.convertWithUnits(0)}
                </ToggleButton>
                <ToggleButton value={1} size="small">
                  {measurement.convertWithUnits(1)}
                </ToggleButton>
              </ToggleButtonGroup>
            ) : (
              <Slider
                disabled={!canWrite(permissions)}
                valueLabelDisplay="auto"
                color="secondary"
                min={measurement.min()}
                max={measurement.max()}
                step={measurement.step()}
                track={false}
                value={measurement.toDisplay(condition.value ? condition.value : 0)}
                onChange={(_event, value) =>
                  updateConditionValue(source, interactionIndex, conditionIndex, value)
                }
              />
            )}
          </Grid>
        </Grid>
      );
    });
    return items;
  };

  const updateConditionValue = (
    source: Component | undefined,
    interactionIndex: number,
    conditionIndex: number,
    value: number | number[]
  ) => {
    if (interactions.length > interactionIndex) {
      let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
      let updatedInteractions = cloneDeep(interactions);
      let interaction = updatedInteractions[interactionIndex];
      let conditions = interaction.settings.conditions;
      if (conditions.length > conditionIndex) {
        if (typeof value === "number") {
          let condition = conditions[conditionIndex];
          let sourceType = source && source.settings.type;
          let sourceSubtype = source && source.settings.subtype;
          let describer = describeMeasurement(condition.measurementType, sourceType, sourceSubtype);
          condition.value = describer.toStored(value);
          updatedDirtyInteractions.set(interactionIndex, true);
        }
        setInteractions(updatedInteractions);
        setDirtyInteractions(updatedDirtyInteractions);
        setRenderToggle(!renderToggle);
      }
    }
  };

  const submitInteraction = (
    deviceID: number,
    index: number,
    settings: pond.IInteractionSettings
  ) => {
    interactionsAPI
      .updateInteraction(Number(deviceID), settings, as)
      .then((_response: any) => {
        let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
        updatedDirtyInteractions.set(index, false);
        setDirtyInteractions(updatedDirtyInteractions);
        success("Successfully updated the interaction for " + component.name());
        refreshCallback();
      })
      .catch((_err: any) => {
        error("Error occurred while updating the interaction for " + component.name());
      });
  };

  const cancel = (index: number) => {
    let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
    updatedDirtyInteractions.set(index, false);
    setDirtyInteractions(updatedDirtyInteractions);
    setInteractions(cloneDeep(props.interactions));
  };

  const interactionItem = (interaction: Interaction, index: number) => {
    let key = interaction.key();
    let source = mappedComponents.get(componentIDToString(interaction.settings.source));
    let sink = mappedComponents.get(componentIDToString(interaction.settings.sink));
    let statusText =
      !interaction.status.synced && interaction.status.lastUpdate
        ? "Pending " +
          moment(interaction.status.lastUpdate && interaction.status.lastUpdate).fromNow()
        : "";
    let schedule = pond.InteractionSchedule.create(
      interaction.settings.schedule !== null ? interaction.settings.schedule : undefined
    );
    let timezone = schedule && schedule.timezone ? schedule.timezone : moment.tz.guess();
    let todStart = (schedule.timeOfDayStart ? schedule.timeOfDayStart : "00:00").split(":");
    let todEnd = (schedule.timeOfDayEnd ? schedule.timeOfDayEnd : "23:59").split(":");
    let start = moment
      .tz(timezone)
      .startOf("day")
      .add(todStart[0], "hours")
      .add(todStart[1], "minutes")
      .local();
    let end = moment
      .tz(timezone)
      .startOf("day")
      .add(todEnd[0], "hours")
      .add(todEnd[1], "minutes")
      .local();
    let descriptor: "allDay" | "before" | "after" | "from" = timeOfDayDescriptor(schedule);
    let showStart = descriptor === "after" || descriptor === "from";
    let showEnd = descriptor === "before" || descriptor === "from";
    let showBoth = showStart && showEnd;
    let everyDay = schedule.weekdays && schedule.weekdays.length === 7;
    let action = <React.Fragment />;
    if (canWrite(permissions)) {
      action = (
        <Tooltip title="Settings">
          <IconButton onClick={() => openInteractionSettings(interaction)}>
            <Settings />
          </IconButton>
        </Tooltip>
      );
    }

    return (
      <Grid size={{ xs: 12 }} key={key}>
        <Card elevation={0} className={classes.card}>
          <CardHeader
            className={classes.header}
            titleTypographyProps={{ variant: "subtitle2" }}
            title={interactionResultText(interaction, sink)}
            subheader={statusText}
            subheaderTypographyProps={{ variant: "caption" }}
            action={action}
          />
          <CardContent className={classes.content}>
            {interaction.settings.conditions && (
              <Grid container>
                {interactionConditions(source, interaction.settings.conditions, index)}
              </Grid>
            )}
            {(descriptor !== "allDay" || !everyDay) && (
              <Grid container justifyContent="center">
                <Typography variant="caption" color="textSecondary">
                  {!everyDay ? abbreviatedWeekdays(schedule) : "Operational"}{" "}
                  {descriptor !== "allDay" && friendlyTimeOfDayDescriptor(descriptor)}{" "}
                  {showStart && start.format("h:mma")} {showBoth && "-"}{" "}
                  {showEnd && end.format("h:mma")}
                </Typography>
              </Grid>
            )}
          </CardContent>
          {dirtyInteractions.get(index) && (
            <CardActions className={classes.actions}>
              <Button size="small" color="primary" onClick={() => cancel(index)}>
                Cancel
              </Button>
              <Button
                size="small"
                color="primary"
                onClick={() => submitInteraction(device.id(), index, interaction.settings)}>
                Submit
              </Button>
            </CardActions>
          )}
        </Card>
      </Grid>
    );
  };

  const interactionList = (component: Component, interactions: Interaction[]) => {
    let items: any = [];
    interactions.forEach((interaction: Interaction, index: number) => {
      if (isSource(component.location(), interaction)) {
        items.push(interactionItem(interaction, index));
      }
    });

    if (items.length <= 0) {
      return null;
    }

    return items;
  };

  return (
    <Grid container spacing={1}>
      {interactionList(component, interactions)}
      <InteractionSettings
        device={device}
        components={components}
        initialComponent={component}
        initialInteraction={selectedInteraction}
        mode="update"
        isDialogOpen={selectedInteraction !== undefined}
        closeDialogCallback={() => closeInteractionSettings()}
        refreshCallback={refreshCallback}
        canEdit={canWrite(permissions)}
      />
    </Grid>
  );
}
