import {
  Alert,
  AlertTitle,
  Box,
  Button,
  Card,
  CardActions,
  CardContent,
  CardHeader,
  darken,
  Divider,
  Grid2 as Grid,
  InputAdornment,
  Skeleton,
  Step,
  StepLabel,
  Stepper,
  TextField,
  Theme,
  Typography
} from "@mui/material";
// import { Alert, AlertTitle, Skeleton } from "@material-ui/lab";
import { GrainOptions, MoistureToHumidity } from "grain";
import GrainDryingChart, { GrainDryingPoint } from "charts/GrainDryingChart";
import SearchSelect, { Option } from "common/SearchSelect";
import { DateRange, GetDefaultDateRange } from "common/time/DateRange";
import DateSelect from "common/time/DateSelect";
import { useComponentAPI } from "hooks";
import { cloneDeep } from "lodash";
import { Component, Device } from "models";
import moment, { Moment } from "moment";
import { extension } from "pbHelpers/ComponentType";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { useEffect, useState } from "react";
import { FindPlenumSensors } from "./GrainUtils";
import { makeStyles } from "@mui/styles";

const useStyles = makeStyles((theme: Theme) => {
  const bg = darken(theme.palette.background.paper, theme.palette.mode === "light" ? 0.1 : 0.15);

  return ({
    card: {
      backgroundColor: bg
    },
    stepper: {
      padding: 0
    },
    header: {
      paddingBottom: theme.spacing(1)
    },
    actions: {
      paddingTop: theme.spacing(1)
    },
    gutter: {
      padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`
    }
  });
});

interface Props {
  devices: Device[];
  loading: boolean;
}

const devicesToOptions = (devices: Device[]): Option[] => {
  return devices.map(d => {
    return { value: d.id().toString(), label: d.name() } as Option;
  });
};

//NOREF: has no references in codebase, is this not used anywhere?
export default function GrainDryingCalculator(props: Props) {
  const classes = useStyles();
  const componentAPI = useComponentAPI();
  const grainOptions = GrainOptions();
  const { devices, loading } = props;
  const [device, setDevice] = useState<Option | undefined>(undefined);
  const [deviceOptions, setDeviceOptions] = useState(devicesToOptions(devices));
  const [components, setComponents] = useState<Component[]>([]);
  const [componentOptions, setComponentOptions] = useState<Option[]>([]);
  const [component, setComponent] = useState<Option | undefined>(undefined);
  const [dateRange, setDateRange] = useState<DateRange>(GetDefaultDateRange());
  const [grainType, setGrainType] = useState<Option | undefined>(
    grainOptions.find(o => o.value === pond.Grain[pond.Grain.GRAIN_NONE])
  );
  const [rawGrainMoisture, setRawGrainMoisture] = useState<string>("15.0");
  const [samples, setSamples] = useState<pond.Measurement[]>([]);
  const [componentsLoading, setComponentsLoading] = useState(false);
  const [samplesLoading, setSamplesLoading] = useState(false);
  const steps = [0, 1, 2];
  const [activeStep, setActiveStep] = useState(0);
  const [chartData, setChartData] = useState<GrainDryingPoint[]>([]);

  useEffect(() => {
    setDeviceOptions(devicesToOptions(devices));
  }, [devices]);

  useEffect(() => {
    let options = FindPlenumSensors(components).map(
      c =>
        ({
          value: c.key(),
          label: c.name()
        } as Option)
    );
    setComponentOptions(options);
    setComponent(options[0] ? options[0] : undefined);
  }, [components]);

  useEffect(() => {
    if (device) {
      const deviceID = Number(device.value);
      setComponentsLoading(true);
      componentAPI
        .list(deviceID)
        .then(response => {
          let data = response.data.components ? response.data.components : [];
          let rComponents: Component[] = data.map((c: any) => {
            return Component.create(pond.Component.fromObject(c));
          });
          setComponents(rComponents);
        })
        .catch(() => {
          setComponents([]);
        })
        .finally(() => {
          setComponentsLoading(false);
        });
    }
  }, [componentAPI, device]);

  useEffect(() => {
    if (device && component) {
      const deviceID = Number(device.value);
      const componentKey = String(component.value);
      setSamplesLoading(true);
      componentAPI
        .sampleMeasurements(deviceID, componentKey, dateRange.start, dateRange.end, 500)
        .then(response => {
          let measurements: pond.Measurement[] | undefined = response.data.measurements;
          let newSamples = measurements
            ? cloneDeep(measurements).sort((a, b) => {
                return b.timestamp > a.timestamp ? 1 : -1;
              })
            : [];

          setSamples(newSamples);
        })
        .catch(() => {
          setSamples([]);
        })
        .finally(() => {
          setSamplesLoading(false);
        });
    }
  }, [componentAPI, device, component, dateRange]);

  // T = temperature im degrees Celsius
  // RH = relative humidity (%)
  const calculateVPD = (T: number, RH: number) => {
    const es = 0.6108 * Math.exp((17.27 * T) / (T + 237.3));
    const ea = (RH / 100) * es;
    return ea - es;
  };

  const calculateEffectiveVPD = (initialVPD: number, curVPD: number) => {
    return initialVPD - curVPD;
  };

  useEffect(() => {
    let data: GrainDryingPoint[] = [];
    let curComponent = component
      ? components.find(c => c.key() === component.value.toString())
      : undefined;
    if (curComponent) {
      let ext = extension(curComponent.settings.type, curComponent.settings.subtype);
      const grainMoisture = parseFloat(rawGrainMoisture) || 15.0;
      samples.forEach(s => {
        let measurement = s.measurement;
        if (measurement) {
          let T = 0;
          let RH = 0;
          ext.measurements.forEach(cm => {
            if (cm.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
              T = cm.extract(measurement as quack.Measurement);
            }
            if (cm.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
              RH = cm.extract(measurement as quack.Measurement);
            }
          });

          const grain = grainType
            ? pond.Grain[grainType.value as keyof typeof pond.Grain]
            : pond.Grain.GRAIN_NONE;
          let initialRH = MoistureToHumidity(grain, T, grainMoisture);
          let initialVPD = calculateVPD(T, initialRH);
          let vpd = calculateVPD(T, RH);
          data.push({
            timestamp: moment(s.timestamp).valueOf(),
            dryScore: calculateEffectiveVPD(initialVPD, vpd)
          });
        }
      });
    }

    setChartData(data);
  }, [samples, rawGrainMoisture, grainType, component, components]);

  const grainMoistureInvalid = () => {
    return rawGrainMoisture === "" || isNaN(Number(rawGrainMoisture));
  };

  const initialSetup = () => {
    const invalidGrainMoisture = grainMoistureInvalid();
    return (
      <Box marginX={1}>
        <Typography variant="subtitle1" color="textSecondary" gutterBottom align="center">
          Please input your grain type and moisture
        </Typography>
        <Box paddingY={1}>
          <SearchSelect
            selected={grainType}
            changeSelection={o => setGrainType(o ? o : undefined)}
            label="Grain Type"
            options={grainOptions}
            group
          />
        </Box>
        <Box paddingY={1}>
          <TextField
            value={rawGrainMoisture}
            onChange={event => setRawGrainMoisture(event.target.value)}
            fullWidth
            label="Grain Moisture"
            variant="outlined"
            error={invalidGrainMoisture}
            helperText={invalidGrainMoisture ? "Invalid number" : ""}
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  <InputAdornment position="end">% WB</InputAdornment>
                </InputAdornment>
              )
            }}
          />
        </Box>
      </Box>
    );
  };

  const setupChart = () => {
    return (
      <Box>
        <Typography variant="subtitle1" color="textSecondary" gutterBottom align="center">
          Please select your plenum moisture sensor
        </Typography>{" "}
        <Grid container spacing={2}>
          <Grid size={{ xs: 12 }}>
            <SearchSelect
              label="Device"
              selected={device}
              disabled={loading}
              options={deviceOptions}
              changeSelection={option => setDevice(option ? option : undefined)}
            />
          </Grid>
          <Grid size={{ xs: 12 }}>
            <SearchSelect
              label="Moisture Sensor"
              selected={component}
              options={componentOptions}
              disabled={!device}
              loading={componentsLoading}
              changeSelection={option => setComponent(option ? option : undefined)}
            />
          </Grid>
        </Grid>
      </Box>
    );
  };

  const analyzeChart = () => {
    return (
      <Box>
        {samplesLoading ? (
          <Skeleton variant="rectangular" />
        ) : chartData.length > 1 ? (
          <GrainDryingChart data={chartData} />
        ) : (
          <Alert severity="info" variant="outlined">
            <AlertTitle>No data to display graph</AlertTitle>
            <Box>Tips:</Box>
            <Box> - increase the time range</Box>
            <Box> - select an active moisture sensor</Box>
          </Alert>
        )}
        <DateSelect
          updateDateRange={(newStart: Moment, newEnd: Moment) =>
            setDateRange({ start: newStart, end: newEnd })
          }
          startDate={dateRange.start}
          endDate={dateRange.end}
        />
        <Typography variant="caption" color="textSecondary">
          Effective Vapor Pressure Deficit (VPD) represents the potential for moisture to be added
          or removed
        </Typography>
      </Box>
    );
  };

  const stepContent = () => {
    switch (activeStep) {
      case 1:
        return setupChart();
      case 2:
        return analyzeChart();
      default:
        return initialSetup();
    }
  };

  const stepComplete = (): boolean => {
    switch (activeStep) {
      case 0:
        return grainType !== undefined && !grainMoistureInvalid();
      case 1:
        return device !== undefined && component !== undefined;
      default:
        return true;
    }
  };

  const actions = () => {
    return (
      <CardActions className={classes.actions}>
        <Box display="flex" width="100%" justifyContent="space-between">
          <Button
            color="primary"
            onClick={() => setActiveStep(activeStep - 1)}
            disabled={activeStep <= 0}>
            Back
          </Button>
          <Button
            color="primary"
            onClick={() => setActiveStep(activeStep + 1)}
            disabled={activeStep >= steps.length - 1 || !stepComplete()}>
            Next
          </Button>
        </Box>
      </CardActions>
    );
  };

  if (loading) return <Skeleton variant="rectangular" width="100%" height="300px" />;

  return (
    <Card elevation={4} className={classes.card}>
      <CardHeader
        title="Grain Drying Calculator"
        subheader={
          <Typography variant="body2" color="textSecondary">
            Find out how well your grain is drying
          </Typography>
        }
        className={classes.header}
      />
      <CardContent className={classes.gutter}>
        <Stepper activeStep={activeStep} className={classes.stepper} alternativeLabel>
          <Step key={0}>
            <StepLabel>Grain Moisture</StepLabel>
          </Step>
          <Step key={1}>
            <StepLabel>Select Plenum</StepLabel>
          </Step>
          <Step key={2}>
            <StepLabel>Analyze</StepLabel>
          </Step>
        </Stepper>
        <Box paddingY={1}>
          <Divider />
        </Box>
        <Box paddingTop={1}>{stepContent()}</Box>
      </CardContent>
      {actions()}
    </Card>
  );
}
