import {
  Box,
  Button,
  DialogActions,
  DialogContent,
  DialogTitle,
  FormControlLabel,
  InputAdornment,
  Radio,
  RadioGroup,
  TextField,
  Typography
} from "@mui/material";
import FileUploader from "common/FileUploads/FileUploader";
import ResponsiveDialog from "common/ResponsiveDialog";
import SearchSelect, { Option } from "common/SearchSelect";
import { useMobile, useUserAPI } from "hooks";
import { Bin, Field, Contract, GrainBag, teamScope } from "models";
// import { Contract } from "models/Contract";
// import { GrainBag } from "models/GrainBag";
import moment from "moment";
import { pond } from "protobuf-ts/pond";
import {
  useBinAPI,
  useFieldAPI,
  useGlobalState,
  useGrainBagAPI,
  useSnackbar,
  useTransactionAPI
} from "providers";
import { useState, useEffect } from "react";

interface Props {
  mainObject: Bin | GrainBag | Field | Contract;
  grainAdjustment?: number;
  hideFields?: boolean;
  asDestination?: boolean;
  restrictMatching?: boolean;
  open: boolean;
  close: (confirmed?: boolean) => void;
  callback?: (newTransaction?: pond.Transaction) => void;
  allowAttachmentUploads?: boolean;
}

export default function GrainTransaction(props: Props) {
  const {
    open,
    mainObject,
    close,
    grainAdjustment,
    callback,
    restrictMatching,
    hideFields,
    asDestination,
    allowAttachmentUploads
  } = props;
  const [isObject, setIsObject] = useState<"source" | "destination" | "">("");
  const [selectedSource, setSelectedSource] = useState<Option | null>(null);
  const [selectedDestination, setSelectedDestination] = useState<Option | null>(null);
  const [binOptions, setBinOptions] = useState<Option[]>([]);
  const [bagOptions, setBagOptions] = useState<Option[]>([]);
  const [fieldOptions, setFieldOptions] = useState<Option[]>([]);
  const [destinationOptions, setDestinationOptions] = useState<Option[]>([]);
  const [sourceOptions, setSourceOptions] = useState<Option[]>([]);
  const binAPI = useBinAPI();
  const grainBagAPI = useGrainBagAPI();
  const fieldAPI = useFieldAPI();
  const [{ as, user }] = useGlobalState();
  const { openSnack } = useSnackbar();
  const [grainChangeDialog, setGrainChangeDialog] = useState(false);
  const [grainEntry, setGrainEntry] = useState("0");
  const [grainChangeVal, setGrainChangeVal] = useState("0");
  const transactionAPI = useTransactionAPI();
  const [grainMessage, setGrainMessage] = useState("");
  const isMobile = useMobile();
  const [binsLoading, setBinsLoading] = useState(false);
  const [bagsLoading, setBagsLoading] = useState(false);
  const [fieldsLoading, setFieldsLoading] = useState(false);
  const [uploadingFile, setUploadingFile] = useState(false);
  const [imageIDs, setImageIDs] = useState<string[]>([]);
  const userAPI = useUserAPI();
  const [filePermission, setFilePermission] = useState(false);

  const closeDialogs = (confirmed: boolean, newTransaction?: pond.Transaction) => {
    close(confirmed);
    setGrainChangeVal("0");
    setGrainEntry("0");
    setIsObject("");
    setGrainChangeDialog(false);
    if (callback && confirmed) {
      callback(newTransaction);
    }
  };

  useEffect(()=>{
    //if they are viewing as a team make sure they have file permission to the team for the transactions
    if(as){
      let scope = teamScope(as)
      userAPI.getUser(user.id(), scope).then(resp => {
        setFilePermission(resp.permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT))
      });
    }else{
      //if they are not viewing as a team and they were able to open this window, they have edit permission to at least the one object they started the change with
      //a transaction is a new object that permissions will not exist for yet so just allow them to do it
      setFilePermission(true)
    }
  },[as])

  //get the possible destinations/options (grainbags and bins)
  useEffect(() => {
    if (open) {
      if (grainAdjustment) {
        setGrainChangeVal(grainAdjustment.toString());
      } else {
        setGrainChangeVal("0");
      }
      if ((grainAdjustment && grainAdjustment > 0) || asDestination) {
        setIsObject("destination");
        setSelectedDestination({
          label: mainObject.name(),
          value: mainObject,
          group: mainObject.objectTypeString()
        });
        setSelectedSource(null);
      } else if (grainAdjustment && grainAdjustment < 0) {
        setIsObject("source");
        setSelectedSource({
          label: mainObject.name(),
          value: mainObject,
          group: mainObject.objectTypeString()
        });
        setSelectedDestination(null);
      }

      //list bins and add to both options
      if (!binsLoading) {
        setBinsLoading(true);
        binAPI
          .listBins(0, 0, undefined, undefined, undefined, as)
          .then(resp => {
            //let sourceOps: Option[] = sourceOptions
            let binOps: Option[] = [];
            if(resp.data.bins){
              resp.data.bins.forEach(bin => {
                let b = Bin.create(bin);
                if (mainObject.key() !== b.key()) {
                  let op: Option = {
                    label: b.name(),
                    value: b,
                    group: "Bins"
                  };
                  (!restrictMatching || grainTypesMatch(op.value, mainObject) || op.value.grainName() === mainObject.grainName()) && binOps.push(op);
                }
              });
            }
            setBinOptions([...binOps]);
          })
          .finally(() => {
            setBinsLoading(false);
          });
      }

      //list bags and add to both options
      if (!bagsLoading) {
        setBagsLoading(true);
        grainBagAPI
          .listGrainBags(0, 0, undefined, undefined, undefined, undefined, undefined, undefined, as)
          .then(resp => {
            //let sourceOps: Option[] = sourceOptions
            let bagOps: Option[] = [];
            if(resp.data.grainBags){
              resp.data.grainBags.forEach(bag => {
                let b = GrainBag.create(bag);
                if (mainObject.key() !== b.key()) {
                  let op: Option = {
                    label: b.name(),
                    value: b,
                    group: "Grain Bags"
                  };
                  (!restrictMatching || grainTypesMatch(op.value, mainObject) || op.value.grainName() === mainObject.grainName()) && bagOps.push(op);
                }
              });
            }
            setBagOptions([...bagOps]);
          })
          .finally(() => {
            setBagsLoading(false);
          });
      }

      //list fields and add to source options
      if (!fieldsLoading && !hideFields) {
        setFieldsLoading(true);
        fieldAPI
          .listFields(0, 0, undefined, undefined, undefined, as)
          .then(resp => {
            //let sourceOps: Option[] = sourceOptions
            let fieldOps: Option[] = [];
            resp.data.fields.forEach(field => {
              let f = Field.create(field);
              let op: Option = {
                label: f.name(),
                value: f,
                group: "Fields"
              };
              (!restrictMatching || grainTypesMatch(op.value, mainObject) || op.value.grainName() === mainObject.grainName()) && fieldOps.push(op);
            });
            setFieldOptions([...fieldOps]);
          })
          .finally(() => {
            setFieldsLoading(false);
          });
      }
    }
  }, [binAPI, fieldAPI, grainBagAPI, as, grainAdjustment, mainObject, open, asDestination]); //eslint-disable-line react-hooks/exhaustive-deps

  //watches the object options arrays and build new options when they change
  useEffect(() => {
    setSourceOptions([...binOptions.concat(bagOptions, fieldOptions)]);
    setDestinationOptions([...binOptions.concat(bagOptions)]);
  }, [binOptions, bagOptions, fieldOptions]);

  //create a transaction to enter into the table
  const createTransaction = (finalVal: number) => {
    let dest = selectedDestination;
    let source = selectedSource;
    let bPerTonne = 1;
    let bPerTon = 1;
    //use the sources bushel per tonne if one was selected otherwise use the destination
    if (source) {
      bPerTonne = source.value.bushelsPerTonne();
    } else if (dest) {
      bPerTonne = dest.value.bushelsPerTonne();
    }

    let newTransaction = pond.Transaction.create({
      timestamp: moment().toISOString(),
      transaction: pond.TransactionData.create({
        grainTransaction: pond.GrainTransaction.create({
          bushels: finalVal,
          message: grainMessage,
          bushelsPerTonne: bPerTonne,
        })
      })
    });

    if (dest) {
      let obj: Contract | Bin | GrainBag = dest.value;
      newTransaction.toKey = obj.key();
      newTransaction.toObject = obj.objectType();
      let grainTransaction = newTransaction.transaction?.grainTransaction;
      //set the grain in the transaction to use the destination by default
      if (grainTransaction) {
        grainTransaction.grainType = obj.grain();
        grainTransaction.customGrain = obj.customType();
        grainTransaction.subtype = obj.subtype();
      }
    }
    if (source) {
      let obj: Field | Bin | GrainBag = source.value;
      newTransaction.fromKey = obj.key();
      newTransaction.fromObject = obj.objectType();
      let grainTransaction = newTransaction.transaction?.grainTransaction;
      //over write the grain type to be what the source has if there was a source
      if (grainTransaction) {
        grainTransaction.grainType = obj.grain();
        grainTransaction.customGrain = obj.customType();
        grainTransaction.subtype = obj.subtype();
      }
    }
    transactionAPI.addTransaction(newTransaction, imageIDs, as).then(resp => {
      newTransaction.key = resp.data.key;
      closeDialogs(true, newTransaction);
    });
  };

  const updateInventory = () => {
    //this is the amount to add to the destination
    let destinationAdd = Math.abs(
      isNaN(parseFloat(grainChangeVal)) ? 1 : parseFloat(grainChangeVal)
    );

    //check the source to see how much can actually be moved
    if (selectedSource) {
      if (selectedSource?.value.objectTypeString() === "Bin") {
        let bin: Bin = selectedSource.value as Bin;
        if (bin.settings.inventory) {
          //if the difference is less than 0 that means there werent enough bushels in the source
          if (bin.settings.inventory.grainBushels - destinationAdd < 0) {
            //set the amount to add to the destination to be what is in the source
            destinationAdd = bin.settings.inventory.grainBushels;
          }
        }
      } else if (selectedSource?.value.objectTypeString() === "Grain Bag") {
        let bag: GrainBag = selectedSource.value as GrainBag;
        if (bag.bushels() - destinationAdd < 0) {
          destinationAdd = bag.bushels();
        }
      }
    }
    //check the destination to see if there is enough room to move from the source
    if (selectedDestination) {
      if (selectedDestination?.value.objectTypeString() === "Bin") {
        let bin: Bin = selectedDestination.value as Bin;
        if (bin.settings.inventory && bin.settings.specs) {
          if (
            bin.settings.inventory.grainBushels + destinationAdd >
            bin.settings.specs.bushelCapacity
          ) {
            openSnack("Destination does not have enough room in bin for selected amount");
            return;
          }
        }
      } else if (selectedDestination?.value.objectTypeString() === "Grain Bag") {
        let bag: GrainBag = selectedDestination.value as GrainBag;
        if (bag.bushels() + destinationAdd > bag.capacity()) {
          openSnack("Destination does not have enough room in bag for selected amount");
          return;
        }
      } else if (selectedDestination?.value.objectTypeString() === "Contract") {
        let contract: Contract = selectedDestination.value as Contract;
        if (contract.settings.delivered + destinationAdd > contract.settings.size) {
          openSnack("Selected value will overdeliver on the contract");
          return;
        }
      }
    }

    //---!!moved the update functionality for updating objects to the backend inside the add transaction call!!---
    createTransaction(destinationAdd);
  };

  //select for sources (fields, bins, bags)
  const sourceSelect = () => {
    return (
      <Box>
        <SearchSelect
          label="Grain Source"
          selected={selectedSource}
          changeSelection={option => {
            setSelectedSource(option);
          }}
          group
          //disabled={!canEdit}
          options={sourceOptions}
        />
      </Box>
    );
  };

  //select for destinations (bins,bags)
  const destinationSelect = () => {
    return (
      <Box>
        <SearchSelect
          label="Grain Destination"
          selected={selectedDestination}
          changeSelection={option => {
            setSelectedDestination(option);
          }}
          group
          options={destinationOptions}
        />
      </Box>
    );
  };

  const grainTypesMatch = (
    source: Bin | GrainBag | Field | Contract,
    destination: Bin | GrainBag | Field | Contract
  ) => {
    let matching = false;
    //check if the storage types are the same between the two
    if (source.storage() === destination.storage()) {
      if (source.storage() === pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN) {
        if (source.grain() === destination.grain()) {
          matching = true;
        }
      }
    }
    return matching;
  };

  const confirm = () => {
    if (selectedSource && selectedDestination) {
      if (grainTypesMatch(selectedSource.value, selectedDestination.value)) {
        updateInventory();
      } else {
        //open dialog saying that the grain types do not match or because it is a custom type 
        setGrainChangeDialog(true);
      }
    } else {
      updateInventory();
    }
  };

  /**
   * the function is used to determine what text to display to the user when the grain types dont match
   * or when it sees any of the objects involved as having a custom grain type
   * @param source the source of the grain
   * @param destination the destination of the grain
   * @returns the text describing the situation
   */
  const confirmationText = () => {
    if(selectedSource && selectedDestination){

      let source = selectedSource?.value
      let destination = selectedDestination?.value
      //both are custom
      if(source.storage() === pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN && destination.storage() === pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN){
        return "Both source and destination are using custom grain types, they may not match. Would you like to continue with the transaction?"
      }
      //source is custom destination is not
      if(source.storage() === pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN){
        return "The source is using a custom grain type and may not match the destination. Would you like to continue with the transaction?"
      }
      //destination is custom source is not
      if(destination.storage() === pond.BinStorage.BIN_STORAGE_UNSUPPORTED_GRAIN){
        return "The destination is using a custom grain type and may not match the source. Would you like to continue with the transaction?"
      }
    }

    //neither are custom 
    return "The Grain types do not match. Are you sure you would like to perform this transaction?"
  }

  const grainChange = () => {
    return (
      <ResponsiveDialog
        open={grainChangeDialog}
        onClose={() => {
          setGrainChangeDialog(false);
        }}>
        <DialogContent>
          {confirmationText()}
        </DialogContent>
        <DialogActions>
          <Button
            onClick={() => {
              setGrainChangeDialog(false);
            }}>
            Cancel
          </Button>
          <Button onClick={updateInventory} variant="contained" color="primary">
            Confirm
          </Button>
        </DialogActions>
      </ResponsiveDialog>
    );
  };

  const grainUnitDisplay = () => {
    switch (user.grainUnit()){
      case pond.GrainUnit.GRAIN_UNIT_TONNE:
        return "mT"
      case pond.GrainUnit.GRAIN_UNIT_TON:
        return "t"
      default:
        return "bu"
    }
  }

  return (
    <ResponsiveDialog
      open={open}
      onClose={() => {
        close();
      }}>
      {grainChange()}
      <DialogTitle>New Grain Transaction</DialogTitle>
      <DialogContent style={{ minWidth: isMobile ? "100%" : 500 }}>
        {/* radio button to determine if the object you are making the adjustment from is the source/destination */}
        {!grainAdjustment && !asDestination && (
          <RadioGroup>
            <FormControlLabel
              value="source"
              control={<Radio />}
              label="Load Out"
              onChange={() => {
                setIsObject("source");
                let option: Option = {
                  value: mainObject,
                  label: mainObject.name(),
                  group: mainObject.objectTypeString()
                };
                setSelectedSource(option);
                setSelectedDestination(null);
              }}
            />
            <FormControlLabel
              value="destination"
              control={<Radio />}
              label="Load In"
              onChange={() => {
                setIsObject("destination");
                let option: Option = {
                  value: mainObject,
                  label: mainObject.name(),
                  group: mainObject.objectTypeString()
                };
                setSelectedSource(null);
                setSelectedDestination(option);
              }}
            />
          </RadioGroup>
        )}
        {!grainAdjustment && (
          <TextField
            label="Adjustment"
            margin="normal"
            fullWidth
            type="number"
            variant="outlined"
            value={grainEntry}
            error={isNaN(parseFloat(grainEntry))}
            helperText={isNaN(parseFloat(grainEntry)) ? "must be a valid number" : ""}
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  {grainUnitDisplay()}
                </InputAdornment>
              )
            }}
            onChange={e => {
              //if the user is viewing the grain in weight
              let grainVal = e.target.value;
              if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
                //need to convert what the user input (weight) into what is actually stored (bushels)
                //use source of the conversion value if it was selected, otherwise use the destination
                if (!isNaN(parseFloat(e.target.value))) {
                  if (selectedSource) {
                    grainVal = (+grainVal * selectedSource.value.bushelsPerTonne()).toFixed(2);
                  } else if (selectedDestination) {
                    grainVal = (+grainVal * selectedDestination.value.bushelsPerTonne()).toFixed(2);
                  }
                }
              }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
                if (!isNaN(parseFloat(e.target.value))) {
                  if (selectedSource) {
                    if(selectedSource.value.bushelsPerTonne){
                      grainVal = (+grainVal * (selectedSource.value.bushelsPerTonne() * 0.907)).toFixed(2);
                    }
                  } else if (selectedDestination) {
                    if(selectedDestination.value.bushelsPerTonne){
                      grainVal = (+grainVal * (selectedDestination.value.bushelsPerTonne() * 0.907)).toFixed(2);
                    }
                  }
                }
              }
              setGrainEntry(e.target.value);
              setGrainChangeVal(grainVal);
            }}
          />
        )}
        <Typography>Leave selection blank to perform a correction</Typography>
        {isObject === "source" && destinationSelect()}
        {isObject === "destination" && sourceSelect()}
        <TextField
          label="Message(optional)"
          margin="dense"
          fullWidth
          variant="outlined"
          value={grainMessage}
          onChange={e => setGrainMessage(e.target.value)}
        />
        <Box marginTop={1}>
          {allowAttachmentUploads && (
            <FileUploader
              hasFilePermission={filePermission}
              uploadEnd={fileID => {
                if (fileID) {
                  let ids = imageIDs;
                  ids.push(fileID);
                  setImageIDs([...ids]);
                }
                setUploadingFile(false);
              }}
              uploadStart={() => {
                setUploadingFile(true);
              }}
            />
          )}
        </Box>
      </DialogContent>

      <DialogActions>
        <Button onClick={() => closeDialogs(false)}>Cancel</Button>
        <Button
          onClick={confirm}
          variant="contained"
          color="primary"
          disabled={uploadingFile || isNaN(parseFloat(grainEntry))}>
          Confirm
        </Button>
      </DialogActions>
    </ResponsiveDialog>
  );
}
