import {
  Button,
  CircularProgress,
  Dialog,
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
  Divider,
  IconButton,
  LinearProgress,
  Link,
  Theme,
  Tooltip,
  Typography
} from "@mui/material";
// import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import UpdateIcon from "@mui/icons-material/Update";
import { useDeviceAPI, useFirmwareAPI, usePrevious, useSnackbar } from "hooks";
import { Device, Firmware, latestFirmwareVersion, Upgrade } from "models";
import moment from "moment";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { useGlobalState } from "providers";
import React, { useCallback, useEffect, useState } from "react";
import { or } from "utils/types";
import { makeStyles } from "@mui/styles";

const useStyles = makeStyles((theme: Theme) => {
  return ({
    dialogContent: {
      marginTop: theme.spacing(2)
    },
    buttonProgress: {
      color: theme.palette.primary.main,
      position: "absolute",
      zIndex: 1
    },
    failed: {
      color: theme.palette.error.main
    }
  })
});

interface Props {
  device: Device;
}

export default function UpgradeDevice(props: Props) {
  const classes = useStyles();
  const deviceAPI = useDeviceAPI();
  const { success, error } = useSnackbar();
  const firmwareAPI = useFirmwareAPI();
  // const [{ firmware }] = useGlobalState();
  const { device } = props;
  const prevDevice = usePrevious(device);
  const [open, setOpen] = useState<boolean>(false);
  const prevOpen = usePrevious(open);
  const [upgrade, setUpgrade] = useState<Upgrade>(Upgrade.create());
  const [gettingStatus, setGettingStatus] = useState(0);
  const [firmware, setFirmware] = useState<Map<string, Firmware>>(new Map())
  const [firmwareLoading, setFirmwareLoading] = useState(false)
  const prevFirmware = usePrevious(firmware);

  const progress = useCallback(() => {
    return upgrade.status.progress;
  }, [upgrade.status.progress]);

  const currentFirmware = () => {
    return device.status.firmwareVersion;
  };

  const latestFirmware = () => {
    return latestFirmwareVersion(
      firmware,
      device.settings.platform,
      device.settings.upgradeChannel
    );
  };

  useEffect(() => {
    if (firmwareLoading) return
    // if (firmware.size > 0) return
    setFirmwareLoading(true)
    firmwareAPI.getAllLatestFirmware().then(resp => {
      let versions: Map<string, Firmware> = new Map();
      if (resp && resp.data && resp.data.firmware) {
        resp.data.firmware.forEach((raw: any) => {
          let firmware = Firmware.any(raw);
          let key = firmware.settings.platform + ":" + firmware.settings.channel;
          versions.set(key, firmware);
        });
      }
      // dispatch({ key: "firmware", value: versions });
      setFirmware(versions)
    }).finally(() => {
      setFirmwareLoading(false)
    })
  }, [])

  const upgradeVersion = () => {
    return upgrade.settings.version;
  };

  const available = () => {
    if (firmware.size < 1) return false
    return currentFirmware() !== latestFirmware();
  };

  const upgrading = useCallback(() => {
    return progress() === "upgrading";
  }, [progress]);

  const completed = () => {
    return progress() === "completed" && currentFirmware() === latestFirmware();
  };
  const failed = () => {
    return progress() === "failed" && upgradeVersion() === latestFirmware();
  };

  const percent = () => {
    let progress = Math.round((upgrade.status.offset / upgrade.status.size) * 100)
    return progress;
  };

  const tooltip = () => {
    return upgrading()
      ? "Upgrade in progress: " + percent() + "%"
      : failed()
      ? "Upgrade failed"
      : "Upgrade Device";
  };

  const upgradeTimingDesc = () => {
    let desc = "requested " + moment(or(upgrade.status.started, 0)).fromNow();
    if (completed()) {
      desc = "completed " + moment(or(upgrade.status.stopped, 0)).fromNow();
    } else if (failed()) {
      desc = "failed " + moment(or(upgrade.status.changed, 0)).fromNow();
    }

    return desc;
  };

  const mode = () => {
    let mode = "prompt";
    if (upgrading() || failed() || completed()) {
      mode = "progress";
    }
    return mode;
  };

  const getStatus = useCallback(() => {
    if (gettingStatus === device.id() && !upgrading()) {
      return;
    }
    setGettingStatus(device.id());
    deviceAPI
      .getUpgradeStatus(device.id(), getContextKeys(), getContextTypes())
      .then((res: any) => {
        if (res && res.data && res.data.upgrade) {
          setUpgrade(Upgrade.any(res.data.upgrade));
        }
      })
      .catch((err: any) => error(err));
  }, [device, deviceAPI, error, gettingStatus, upgrading]);

  useEffect(() => {
    const deviceIdUnchanged = !prevDevice || prevDevice.id() === device.id();
    if (
      (open && !prevOpen) ||
      firmware !== prevFirmware ||
      (device !== prevDevice && deviceIdUnchanged)
    ) {
      getStatus();
    }
  }, [open, prevOpen, getStatus, device, prevDevice, firmware, prevFirmware]);

  useEffect(() => {
    function getStatusAgain() {
      if (upgrading()) {
        getStatus();
      }
    }
    const interval = setInterval(() => getStatusAgain(), 5000);
    return () => {
      clearInterval(interval);
    };
  }, [open, getStatus, upgrading]);

  const close = () => {
    setOpen(false);
  };

  const handleUpgrade = () => {
    firmwareAPI
      .upgradeFirmware(device.id())
      .then(() => {
        success(device.name() + " will upgrade to " + latestFirmware() + " next time it checks in");
        getStatus();
      })
      .catch((err: any) => {
        console.error(err);
        error(err);
      });
  };

  const restart = () => {
    firmwareAPI
      .upgradeFirmware(device.id())
      .then(() => {
        success(
          "Upgrade successfully restarted, " +
            device.name() +
            " will upgrade next time it checks in"
        );
        getStatus();
      })
      .catch((err: any) => {
        console.error(err);
        error(err);
      });
  };

  const cancel = () => {
    firmwareAPI
      .cancelUpgrade(device.id())
      .then(() => {
        success("Upgrade cancelled");
      })
      .catch((err: any) => {
        console.error(err);
        error(err);
      });
  };

  const button = () => {
    return (
      <Tooltip title={tooltip()}>
        <IconButton
          color="default"
          className={failed() ? classes.failed : undefined}
          onClick={() => setOpen(true)}
          aria-label={tooltip()}>
          <UpdateIcon />
          {upgrading() && (
            <CircularProgress
              variant="determinate"
              value={percent()}
              size={"2rem"}
              className={classes.buttonProgress}
            />
          )}
        </IconButton>
      </Tooltip>
    );
  };

  return (
    <React.Fragment>
      {(available() || upgrading() || failed()) && button()}
      <Dialog fullWidth open={open} onClose={close} aria-labelledby="form-dialog-title">
        {mode() === "prompt" && (
          <React.Fragment>
            <DialogTitle id="form-dialog-title">
              Upgrade Device
              <Typography variant="body2" color="textSecondary">
                {device.name()}
              </Typography>
            </DialogTitle>
            <Divider />
            <DialogContent className={classes.dialogContent}>
              <DialogContentText id="alert-dialog-slide-description">
                Upgrading from {currentFirmware()} to {latestFirmware()}
                <br />
                <br />
                It is recommended that the device be plugged in during the upgrade as it will take
                several minutes to complete
                <br />
                <br />
                While the upgrade is in progress you can return to this dialog to view its status
              </DialogContentText>
            </DialogContent>
            <DialogActions>
              <Button onClick={close} color="primary">
                Close
              </Button>
              <Button onClick={handleUpgrade} color="primary">
                Upgrade
              </Button>
            </DialogActions>
          </React.Fragment>
        )}
        {mode() === "progress" && (
          <React.Fragment>
            <DialogTitle id="form-dialog-title">
              Upgrading Device
              <Typography variant="body2" color="textSecondary">
                {device.name()}
              </Typography>
            </DialogTitle>
            <Divider />
            <DialogContent className={classes.dialogContent}>
              <DialogContentText id="alert-dialog-slide-description">
                Upgrade to {latestFirmware()} {upgradeTimingDesc()}
              </DialogContentText>
              <br />
              <LinearProgress variant="determinate" value={percent()} />
              <DialogContentText>
                {or(upgrade.status.offset, 0)} / {upgrade.status.size} bytes ({percent()}%)
              </DialogContentText>
              {failed() && (
                <React.Fragment>
                  <DialogContentText>
                    <br />
                    Uh oh – something went wrong. You can try to{" "}
                    <Link variant="body2" onClick={restart} style={{ cursor: "pointer" }}>
                      restart
                    </Link>{" "}
                    the upgrade, if the problem persists we'll be alerted and follow up as soon as
                    possible.
                  </DialogContentText>
                </React.Fragment>
              )}
            </DialogContent>
            <DialogActions>
              <Button onClick={close} color="primary">
                Close
              </Button>
              {upgrading() && (
                <Button onClick={cancel} style={{ color: "red" }}>
                  Cancel
                </Button>
              )}
              {failed() && (
                <Button onClick={restart} color="primary">
                  Restart
                </Button>
              )}
            </DialogActions>
          </React.Fragment>
        )}
      </Dialog>
    </React.Fragment>
  );
}
