import {
  Button,
  CircularProgress,
  createStyles,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  Divider,
  Grid2 as Grid,
  InputAdornment,
  TextField,
  Typography,
  Theme
} from "@mui/material"
import { withStyles, WithStyles } from "@mui/styles";

import DateSelect from "common/time/DateSelect";
import { Component, Device, User } from "models";
import moment, { Moment } from "moment";
import { ComponentMeasurement, getMeasurements } from "pbHelpers/ComponentType";
import { pond } from "protobuf-ts/pond";
import React from "react";
import { downloadJSON, exportDataToCSV } from "utils/download";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { ComponentAPIContext } from "providers/pond/componentAPI";
import { UnitMeasurement } from "models/UnitMeasurement";

const styles = (theme: Theme) =>
  createStyles({
    loadingContent: {
      marginTop: theme.spacing(4)
    }
  });

interface Props extends WithStyles<typeof styles> {
  device: Device;
  component: Component;
  isDialogOpen: boolean;
  closeDialogCallback: Function;
  initialStartDate?: Moment;
  initialEndDate?: Moment;
  isJSON?: boolean;
  newMeasurements?: boolean;
  user?: User;
  as?: string
}

interface State {
  startDate: any;
  endDate: any;
  isLoading: boolean;
  filename: string;
}

class ExportDataSettings extends React.Component<Props, State> {
  static contextType = ComponentAPIContext;
  declare context: React.ContextType<typeof ComponentAPIContext>;
  constructor(props: Props) {
    super(props);
    this.state = this.defaultSettings(props);
  }

  componentDidUpdate = (prevProps: Props) => {
    const { initialStartDate, initialEndDate } = this.props;
    if (
      prevProps.initialStartDate !== initialStartDate ||
      prevProps.initialEndDate !== initialEndDate
    ) {
      this.setState({
        startDate: initialStartDate,
        endDate: initialEndDate
      });
    }
  };

  defaultSettings = (props: Props) => {
    const { initialStartDate, initialEndDate } = props;
    return {
      startDate: initialStartDate ? initialStartDate : moment().subtract(1, "days"),
      endDate: initialEndDate ? initialEndDate : moment(),
      isLoading: false,
      filename: moment()
        .utc()
        .format()
    } as State;
  };

  close = () => {
    this.setState(this.defaultSettings(this.props));
    this.props.closeDialogCallback();
  };

  submit = () => {
    const { device, component, user, as } = this.props;
    const { startDate, endDate } = this.state;
    const { /*sampleMeasurements*/ listUnitMeasurements } = this.context;

    this.setState({ isLoading: true });
    // if (newMeasurements) {
      listUnitMeasurements(
        device.id(),
        component.key(),
        startDate,
        endDate,
        0,
        0,
        "desc",
        undefined,
        [device.id().toString()],
        ["device"],
        undefined,
        undefined,
        as
      )
        .then(resp => {
          let measurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
          this.formatUnitMeasurements(measurements)
            .then((data: Array<any>) => {
              this.setState({
                isLoading: false
              });
              this.exportData(
                data
                  .map(entry => {
                    let out = entry;
                    out.Timestamp = moment(entry.Timestamp)
                      .local()
                      .format();
                    return out;
                  })
                  .sort(function(a, b) {
                    return a.Timestamp < b.Timestamp ? -1 : 1;
                  })
              );
              this.close();
            })
            .catch((error: any) => {
              this.setState({ isLoading: false });
              this.close();
            });
        })
        .catch((error: any) => {
          this.setState({ isLoading: false });
          this.close();
        });
    // } else {
    //   sampleMeasurements(device.id(), component.key(), startDate, endDate, 4294967295)
    //     .then((response: any) => {
    //       this.formatMeasurements(response.data.measurements)
    //         .then((data: Array<any>) => {
    //           this.setState({
    //             isLoading: false
    //           });
    //           this.exportData(
    //             data
    //               .map(entry => {
    //                 let out = entry;
    //                 out.Timestamp = moment(entry.Timestamp)
    //                   .local()
    //                   .format();
    //                 return out;
    //               })
    //               .sort(function(a, b) {
    //                 return a.Timestamp < b.Timestamp ? -1 : 1;
    //               })
    //           );
    //           this.close();
    //         })
    //         .catch((error: any) => {
    //           this.setState({ isLoading: false });
    //           this.close();
    //         });
    //     })
    //     .catch((error: any) => {
    //       this.setState({ isLoading: false });
    //       this.close();
    //     });
    // }
  };

  // formatExportedMeasurements(measurements: Array<pond.ExportedMeasurement>): Promise<Array<any>> {
  //   return new Promise(function(resolve, reject) {
  //     if (!measurements) reject("Invalid measurements");

  //     let formattedMeasurements: Array<any> = [];
  //     for (var i = 0; i < measurements.length; i++) {
  //       let measurement: pond.ExportedMeasurement = measurements[i];
  //       let lastMeasurement = i === measurements.length - 1;

  //       if (!measurement || !measurement.timestamp) {
  //         if (lastMeasurement) {
  //           resolve(formattedMeasurements);
  //         }
  //       } else {
  //         let row: any = {
  //           Timestamp: measurement.timestamp,
  //           MeasurementType: measurement.measurementType,
  //           MeasurementValue: measurement.measurementValue,
  //           MeasurementUnit: measurement.measurementUnit
  //         };
  //         formattedMeasurements.push(row);

  //         if (lastMeasurement) {
  //           resolve(formattedMeasurements);
  //         }
  //       }
  //     }
  //   });
  // }

  formatMeasurements(measurements: Array<pond.Measurement>): Promise<Array<any>> {
    const { device, component } = this.props;

    return new Promise(function(resolve, reject) {
      if (!measurements) reject("Invalid measurements");
      let formattedMeasurements: Array<any> = [];
      for (var i = 0; i < measurements.length; i++) {
        let measurement: pond.Measurement = measurements[i];
        let lastMeasurement = i === measurements.length - 1;

        if (!measurement || !measurement.timestamp || !measurement.measurement) {
          if (lastMeasurement) {
            resolve(formattedMeasurements);
          }
        } else {
          let row: any = {
            Device: device.name(),
            Component: component.name(),
            Timestamp: measurement.timestamp
          };

          const componentMeasurements: Array<ComponentMeasurement> = getMeasurements(
            component.settings.type
          );
          componentMeasurements.forEach((componentMeasurement: ComponentMeasurement) => {
            let unit = describeMeasurement(
              componentMeasurement.measurementType,
              component.settings.type,
              component.settings.subtype
            ).unit();
            let key = componentMeasurement.label + " (" + unit + ")";
            row[key] = componentMeasurement.extract(measurement.measurement);
          });
          formattedMeasurements.push(row);
          if (lastMeasurement) {
            resolve(formattedMeasurements);
          }
        }
      }
    });
  }

  formatUnitMeasurements(unitMeasurements: UnitMeasurement[]): Promise<Array<any>> {
    const { device, component } = this.props;

    return new Promise(function(resolve, reject) {
      if (!unitMeasurements) reject("Invalid measurements");
      let formattedMeasurements: Map<string, any> = new Map<string, any>();

      unitMeasurements.forEach(um => {
        let describer = describeMeasurement(
          um.type,
          component.settings.type,
          component.settings.subtype
        );

        um.values.forEach((vals, i) => {
          let timestamp = um.timestamps[i];
          let row: any = formattedMeasurements.get(timestamp);
          if (row === undefined) {
            row = {
              Device: device.name(),
              Component: component.name(),
              Timestamp: timestamp
            };
          }

          let key = describer.label() + "(" + describer.unit() + ")";
          let valueObj: any = {};
          vals.values.forEach((val, i) => {
            valueObj["node" + i] = val;
          });
          row[key] = valueObj;
          formattedMeasurements.set(timestamp, row);
        });
      });
      resolve(Array.from(formattedMeasurements.values()));
    });
  }

  exportJSON(data: Array<any>) {
    const { filename } = this.state;
    downloadJSON(data, filename + ".json");
  }

  exportData(data: Array<any>) {
    const { filename } = this.state;
    exportDataToCSV(filename, data);
  }

  updateDateRange = (newStartDate: any, newEndDate: any) => {
    this.setState({ startDate: newStartDate, endDate: newEndDate });
  };

  updateFilename = (event: any) => {
    this.setState({ filename: event.target.value });
  };

  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UI BEGINS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  title() {
    const { component } = this.props;

    return (
      <React.Fragment>
        Data Export Settings
        <Typography variant="body2" color="textSecondary">
          {component.name()}
        </Typography>
      </React.Fragment>
    );
  }

  content() {
    const { classes } = this.props;
    const { startDate, endDate, filename, isLoading } = this.state;

    if (isLoading) {
      return (
        <Grid
          container
          direction="column"
          alignItems="center"
          className={classes.loadingContent}>
          <CircularProgress color="secondary" />

          <Typography variant="subtitle1" color="textPrimary">
            Generating a custom {this.props.isJSON ? "json..." : "csv..."}
          </Typography>
          <Typography variant="subtitle2" color="textSecondary">
            (This may take a while, please wait)
          </Typography>
        </Grid>
      );
    }

    return (
      <Grid container direction="row" spacing={2}>
        <Grid size={12}>
          <TextField
            id="exportDataFilename"
            label="Filename"
            value={filename}
            fullWidth
            variant="standard"
            onChange={this.updateFilename}
            margin="normal"
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  {this.props.isJSON ? ".json" : ".csv"}
                </InputAdornment>
              )
            }}
          />
        </Grid>
        <Grid size={12}>
          <DateSelect
            startDate={startDate}
            endDate={endDate}
            updateDateRange={this.updateDateRange}
            label="Extract data from"
          />
        </Grid>
        <Grid size={12}>
          <Typography variant="body2" color="textSecondary">
            Note: This process might take a while (depends on how much data is requested)
          </Typography>
        </Grid>
      </Grid>
    );
  }

  actions() {
    const { isLoading } = this.state;

    return (
      <React.Fragment>
        <span>
          <Button onClick={this.close} color="primary">
            Cancel
          </Button>
          <Button onClick={this.submit} color="primary" disabled={isLoading}>
            Export
          </Button>
        </span>
      </React.Fragment>
    );
  }

  render() {
    return (
      <Dialog
        fullWidth
        open={this.props.isDialogOpen}
        onClose={this.close}
        aria-labelledby="exportDataSettingsDialog">
        <DialogTitle id="exportDataSettingsTitle">{this.title()}</DialogTitle>
        <Divider />
        <DialogContent>{this.content()}</DialogContent>
        <DialogActions>{this.actions()}</DialogActions>
      </Dialog>
    );
  }
}

export default withStyles(styles)(ExportDataSettings);
