import { CheckOutlined } from "@mui/icons-material";
import { Autocomplete, Box, Button, Card, DialogActions, DialogContent, DialogTitle, TextField, Typography } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import ResponsiveTable from "common/ResponsiveTable";
import { Option } from "common/SearchSelect";
import GrainDescriber from "grain/GrainDescriber";
import { cloneDeep } from "lodash";
import { Bin, Contract, Field, GrainBag } from "models";
import { Transaction } from "models/Transaction";
import moment from "moment";
import ObjectDescriber from "objects/ObjectDescriber";
import { pond } from "protobuf-ts/pond";
import { useBinAPI, useContractAPI, useFieldAPI, useGrainBagAPI, useSnackbar, useTransactionAPI } from "providers";
import React from "react";
import { useCallback, useEffect, useState } from "react";
import TransactionDataDisplay from "transactions/transactionDataDisplay";
import { getGrainUnit } from "utils";

interface Props {
  bin: Bin
  permissions: pond.Permission[]
  refresh: () => void
}

export default function BinTransactions(props: Props){
  const { bin, permissions, refresh } = props
  const [state, setState] = useState<pond.TransactionState>(pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED)
  const transactionAPI = useTransactionAPI()
  const binAPI = useBinAPI()
  const grainBagAPI = useGrainBagAPI()
  const fieldAPI = useFieldAPI()
  const contractAPI = useContractAPI()
  const [transactions, setTransactions] = useState<Transaction[]>([])
  const [possibleTransactionMatches, setPossibleTransactionMatches] = useState<Transaction[]>([])
  const [tablePage, setTablePage] = useState(0)
  const [tablePageSize, setTablePageSize] = useState(10)
  const { openSnack } = useSnackbar();
  const [total, setTotal] = useState(0)
  const [grainMessage, setGrainMessage] = useState("")

  //only need to do this for the bins since if a bin is picked for the secondary we need to check its inventory control type,
  //if it is also hybrid a matching transaction must be selected
  const [bins, setBins] = useState<Map<string, Bin>>(new Map()) 
  const [binOptions, setBinOptions] = useState<Option[]>([])

  const [grainBagOptions, setGrainBagOptions] = useState<Option[]>([])

  const [fieldOptions, setFieldOptions] = useState<Option[]>([])

  const [contractOptions, setContractOptions] = useState<Option[]>([])

  const [openDialog, setOpenDialog] = useState(false)
  const [objectNames, setObjectNames] = useState(new Map<string, string>())
  const [selectedTransaction, setSelectedTransaction] = useState<Transaction>()
  const [selectedMatch, setselectedMatch] = useState<Transaction>()

  const [fromType, setFromType] = useState<pond.ObjectType>(pond.ObjectType.OBJECT_TYPE_UNKNOWN)
  const [fromOption, setFromOption] = useState<Option | undefined>()

  const [toType, setToType] = useState<pond.ObjectType>(pond.ObjectType.OBJECT_TYPE_UNKNOWN)
  const [toOption, setToOption] = useState<Option | undefined>()
  const [requiresMatch, setRequiresMatch] = useState(false)

  //load bins
  const loadBins = ()=>{
    //limit of 0 will load them all but this load will only happen once regardless of how many times the dropdown for type is changed
    binAPI.listBins(0, 0, "asc", "name").then(resp => {
      let options: Option[] = []
      let binMap: Map<string, Bin> = new Map()
      resp.data.bins.map(b => {
        let bin = Bin.create(b)
        if(bin.key() !== props.bin.key()){
          options.push({label: bin.name(), value: bin.key(), group: bin.grainName()})
          binMap.set(bin.key(), bin)
        }
      })
      setBinOptions(options)
      setBins(binMap)
    })
  }

  //load grainbags
  const loadGrainbags = ()=>{
    grainBagAPI.listGrainBags(0, 0, "asc", "name").then(resp => {
      setGrainBagOptions(resp.data.grainBags.map(g => {
        let grainBag = GrainBag.create(g)
        return {label: grainBag.name(), value: grainBag.key(), group: grainBag.grainName()}
      }))
    })
  }

  //load fields
  const loadFields = useCallback(()=>{
    fieldAPI.listFields(0,0, "asc", "field_name").then(resp => {
      setFieldOptions(resp.data.fields.map(f => {
        let field = Field.create(f)
        return {label: field.name(), value: field.key(), group: field.grainName()}
      }))
    })
  },[])

  //load contracts
  const loadContracts = useCallback(()=>{
    contractAPI.listContracts(0,0, "asc", "name").then(resp => {
      setContractOptions(resp.data.contracts.map(c => {
        let contract = Contract.create(c)
        return {label: contract.name(), value: contract.key(), group: contract.grainName()}
      }))
    })
  },[])



  const loadTransactionsFor = useCallback(()=>{
    // load transactions for bin with matching state (pending is default)
    transactionAPI.listTransactionsFor(tablePageSize, tablePage * tablePageSize, "asc", bin.key(), state).then(resp => {
      setTotal(resp.data.total)
      // console.log(resp.data)
      setTransactions(resp.data.transactions.map(t => Transaction.create(t)))
      //TODO: for now just use the bin for the name map, once a backend method of getting the names is implemented the response can be used to build the map
      let nameMap = new Map<string, string>()
      nameMap.set(bin.key(), bin.name())
      setObjectNames(nameMap)
    })
  },[state, bin, tablePage, tablePageSize])
  
  useEffect(()=>{
    loadTransactionsFor()
  },[state, bin, loadTransactionsFor])

  const closeDialog = () => {
    setPossibleTransactionMatches([])
    setselectedMatch(undefined)
    setRequiresMatch(false)
    setOpenDialog(false)
  }

  const submit = () => {
    let transaction = cloneDeep(selectedTransaction)
    if(transaction){
      //use the dropdowns to update the from/to for the transaction, meaning if the user added another object it will adjust its inventory as well
      transaction.transaction.fromObject = fromType
      transaction.transaction.fromKey = fromOption?.value ?? ""
      transaction.transaction.toObject = toType
      transaction.transaction.toKey = toOption?.value ?? ""
      //need to do a check to make sure the transaction data contains a grain transaction
      if(transaction.transaction.transaction && transaction.transaction.transaction.grainTransaction) {
        transaction.transaction.transaction.grainTransaction.message = grainMessage
      }
      
      //submit approval
      transactionAPI.submitApproval(transaction.transaction.key, true, transaction.transaction).then(resp => {
        //in the response if it succeeded remove (not reject but fully delete it) the matched transaction if one was selected
        if(selectedMatch){
          transactionAPI.removeTransaction(selectedMatch.transaction.key).then(resp => {
            openSnack("Updated and matched transactions")
          }).catch(err => {
            openSnack("Updated primary transaction but failed to remove matched transaction")
          })
        }else{
          openSnack("Updated Transaction")
        }
      }).catch(err => {
        openSnack("There was a problem submitting transaction approval")
      }).finally(() => {
        refresh()
      })
    }
  }

  const reject = () => {
    //reject the transaction
    if(selectedTransaction){
      transactionAPI.submitApproval(selectedTransaction.transaction.key, false, selectedTransaction.transaction).then(resp => {
        openSnack("Transaction rejected")
      }).catch(err => {
        openSnack("There was a problem rejecting the transaction")
      }).finally(() => {
        refresh()
      })
    }
  }

  const getOptions = (type: pond.ObjectType) => {
    switch(type){
      case pond.ObjectType.OBJECT_TYPE_BIN:
        return binOptions
      case pond.ObjectType.OBJECT_TYPE_GRAIN_BAG:
        return grainBagOptions      
      case pond.ObjectType.OBJECT_TYPE_FIELD:
        return fieldOptions      
      case pond.ObjectType.OBJECT_TYPE_CONTRACT:
        return contractOptions
      default:
        return []
    }
  }

  // dialog for approving a transaction
  const approvalDialog = () => {
    // get the display information for the transaction
    const grainTransaction = selectedTransaction?.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create()
    const bushels = grainTransaction.bushels ?? 0
    const weight = bushels / grainTransaction.bushelsPerTonne
    let graintype = grainTransaction.grainType ?? pond.Grain.GRAIN_INVALID
    const grainDisplay = graintype === pond.Grain.GRAIN_CUSTOM
      ? grainTransaction.customGrain
      : GrainDescriber(grainTransaction.grainType).name
    
    //determine which location (from/to) that can be edited
    let fromDisabled = selectedTransaction?.transaction.fromKey !== ""
    let toDisabled = selectedTransaction?.transaction.toKey !== ""
    return (
      <ResponsiveDialog open={openDialog} onClose={closeDialog}>
        <DialogTitle>Accept Transaction</DialogTitle>
        <DialogContent>
          <Box>
            <Typography>{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? weight + " mT" : bushels + " bushels"} of {grainTransaction.subtype} {grainDisplay} moved</Typography>
            <Typography margin={1} fontWeight={650}>From:</Typography>
            {/*Object type selection*/}
              <Autocomplete
                sx={{marginY: 1}}
                disabled={fromDisabled}
                id="autoFromTypes"
                value={fromType}
                options={[
                  pond.ObjectType.OBJECT_TYPE_BIN,
                  pond.ObjectType.OBJECT_TYPE_GRAIN_BAG,
                  pond.ObjectType.OBJECT_TYPE_FIELD

                ]}
                getOptionLabel={(option: pond.ObjectType) => ObjectDescriber(option).name}
                fullWidth
                onChange={(_, val) => {
                  setFromType(val ?? pond.ObjectType.OBJECT_TYPE_UNKNOWN)
                  //based on what it is changed to need to load data if it is not already loaded
                  if(val === pond.ObjectType.OBJECT_TYPE_BIN){
                    //if there is data already the initial load already happened
                    if(binOptions.length === 0){
                      //do the initial load of options
                      loadBins()
                    }
                  }else if (val === pond.ObjectType.OBJECT_TYPE_GRAIN_BAG){
                    if(grainBagOptions.length === 0){
                      loadGrainbags()
                    }
                  }else if (val === pond.ObjectType.OBJECT_TYPE_FIELD){
                    if (fieldOptions.length === 0){
                      loadFields()
                    }
                  }
                }}
                renderInput={params => <TextField {...params} label="Type" />}
              />
              {/* object selection */}
              <Autocomplete
                sx={{marginY: 1}}
                disabled={fromDisabled}
                id="autoFromObjects"
                value={fromOption}
                options={getOptions(fromType)}
                getOptionLabel={(option: Option) => option.label}
                fullWidth
                onChange={(_, val) => {
                  setFromOption(val ?? undefined)
                  //once one is selected get the transactions for it that are requiring approval
                  transactionAPI.listTransactionsFor(0, 0, "asc", val?.value, pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED).then(resp => {
                    let matches: Transaction[] = []
                    resp.data.transactions.forEach(transaction => {
                      //the transaction could be a match if the from is the object you selected and the to is unknown
                      if(transaction.fromKey === val?.value && transaction.toKey === ""){
                        matches.push(Transaction.create(transaction))
                      }
                    })
                    setPossibleTransactionMatches(matches)
                  })
                  //if the options are bin types check if the selected bin uses hybrid controls
                  if(fromType === pond.ObjectType.OBJECT_TYPE_BIN && val){
                    let bin = bins.get(val.value)
                    if(bin){
                      setRequiresMatch(
                        bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR ||
                        bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_CABLE)
                    }
                  }
                }}
                renderInput={params => <TextField {...params} label="From" />}
              />
            <Typography margin={1} fontWeight={650}>To:</Typography>
            <Autocomplete
                sx={{marginY: 1}}
                disabled={toDisabled}
                id="autoFromTypes"
                value={toType}
                options={[
                  pond.ObjectType.OBJECT_TYPE_BIN,
                  pond.ObjectType.OBJECT_TYPE_GRAIN_BAG,
                  pond.ObjectType.OBJECT_TYPE_CONTRACT
                ]}
                getOptionLabel={(option: pond.ObjectType) => ObjectDescriber(option).name}
                fullWidth
                onChange={(_, val) => {
                  setToType(val ?? pond.ObjectType.OBJECT_TYPE_UNKNOWN)
                  //based on what it is changed to need to load data if it is not already loaded
                  if(val === pond.ObjectType.OBJECT_TYPE_BIN){
                    //if there is data already the initial load already happened
                    if(binOptions.length === 0){
                      //do the initial load of options
                      loadBins()
                    }
                  }else if (val === pond.ObjectType.OBJECT_TYPE_GRAIN_BAG){
                    if(grainBagOptions.length === 0){
                      loadGrainbags()
                    }
                  }else if (val === pond.ObjectType.OBJECT_TYPE_CONTRACT){
                    if(contractOptions.length === 0){
                      loadContracts()
                    }
                  }
                }}
                renderInput={params => <TextField {...params} label="Type" />}
              />
              <Autocomplete
                sx={{marginY: 1}}
                disabled={toDisabled}
                groupBy={option => {
                  if (option.group) {
                    return option.group;
                  }
                  return "No Grain Type";
                }}
                id="autoFromObjects"
                value={toOption}
                options={getOptions(toType)}
                getOptionLabel={(option: Option) => option.label}
                fullWidth
                onChange={(_, val) => {
                  setToOption(val ?? undefined)
                  //once one is selected get the transactions for it that are requiring approval
                  transactionAPI.listTransactionsFor(0, 0, "asc", val?.value, pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED).then(resp => {
                    let matches: Transaction[] = []
                    resp.data.transactions.forEach(transaction => {
                      //the transaction could be a match if the to is the object selected and the from is unknown
                      if(transaction.toKey === val?.value && transaction.fromKey === ""){
                        matches.push(Transaction.create(transaction))
                      }
                    })
                    setPossibleTransactionMatches(matches)
                  })
                  //if the options are bin types check if the selected bin uses hybrid controls
                  if(fromType === pond.ObjectType.OBJECT_TYPE_BIN && val){
                    let bin = bins.get(val.value)
                    if(bin){
                      setRequiresMatch(
                        bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_LIDAR ||
                        bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_CABLE)
                    }
                  }
                }}
                renderInput={params => <TextField {...params} label="From" />}
              />
          </Box>
          <Box>
            <TextField
              label="Message(optional)"
              margin="dense"
              fullWidth
              variant="outlined"
              value={grainMessage}
              onChange={e => setGrainMessage(e.target.value)}
            />
          </Box>
          {toOption &&
          <Box paddingTop={1}>
            <Typography variant="h6">Possible matching Transactions</Typography>
            <Typography>{possibleTransactionMatches.length > 0 ? "Pick a transaction below if you want to match them together" : "No matching transactions found"}</Typography>
            {possibleTransactionMatches.map(t => {
              // get the display information for the transaction
              const grainTransaction = t.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create()
              const bushels = grainTransaction.bushels ?? 0
              const weight = bushels / grainTransaction.bushelsPerTonne
              let graintype = grainTransaction.grainType ?? pond.Grain.GRAIN_INVALID
              const grainDisplay = graintype === pond.Grain.GRAIN_CUSTOM
                ? grainTransaction.customGrain
                : GrainDescriber(grainTransaction.grainType).name
              return (
                <Card sx={{marginTop: 1, padding: 1}} key={t.transaction.key}>
                  <Box display="flex" justifyContent="space-between" height={40}>
                    <Typography sx={{fontWeight: 650, margin: "auto", marginLeft: 0}}>
                      {moment(t.transaction.timestamp).fromNow()}
                    </Typography>
                    <Box>
                      {selectedMatch?.transaction.key === t.transaction.key && <CheckOutlined htmlColor="green" />}
                    </Box>
                  </Box>
                  <Box display="flex" justifyContent="space-between">
                    <Typography sx={{margin: "auto", marginLeft: 0}}>{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? weight + " mT" : bushels + " bushels"} of {grainTransaction.subtype} {grainDisplay} moved</Typography>
                    <Button 
                      variant="contained"
                      disabled={selectedMatch?.transaction.key === t.transaction.key}
                      color="primary"
                      onClick={()=>{
                        setselectedMatch(t)
                      }}>
                        Set Match
                    </Button>
                  </Box>
                </Card>
              )
            })}
          </Box>
          }
        </DialogContent>
        <DialogActions>
          <Button variant="contained" onClick={closeDialog}>Close</Button>
          <Button 
            variant="contained" 
            color="error" 
            onClick={() => {
              reject()
              closeDialog()
            }}>
              Reject
            </Button>
          <Button 
            variant="contained" 
            disabled={requiresMatch && selectedMatch === undefined}
            color="primary"
            onClick={() => {
              submit()
              closeDialog()
              //re-load bin
            }}>
              Approve
            </Button>
        </DialogActions>
      </ResponsiveDialog>
    )
  }

  return (
    <React.Fragment>
    {approvalDialog()}
    <Card>
      {/* dropdown for transaction state 
          --FUTURE THING-- 
          when we can implement a better way to get the current names of objects involved
          other than loading everything up front
      */}

      {/* table to display transactions */}
      <ResponsiveTable<Transaction>
        columns={[
          {
            title: "Date",
            render: row => {
              return (
                <Box padding={2}>
                  <Typography>{moment(row.transaction.timestamp).format("YYYY-MM-DD")}</Typography>
                </Box>
              );
            }
          },
          {
            title: "From",
            render: row => {
              return (
                <Box padding={2}>
                  <Typography>{ObjectDescriber(row.transaction.fromObject).name}</Typography>
                  <Typography>
                    {objectNames ? objectNames.get(row.transaction.fromKey) : row.transaction.fromKey}
                  </Typography>
                </Box>
              );
            }
          },
          {
            title: "To",
            render: row => {
              return (
                <Box padding={2}>
                  <Typography>{ObjectDescriber(row.transaction.toObject).name}</Typography>
                  <Typography>
                    {objectNames ? objectNames.get(row.transaction.toKey) : row.transaction.toKey}
                  </Typography>
                </Box>
              );
            }
          },
          {
            title: "Transaction",
            render: row => {
              return (
                <Box padding={2}> 
                  <TransactionDataDisplay transaction={row} />
                </Box>
              );
            }
          }
        ]}
        title="Pending Transactions"
        subtitle={"Select a transaction to approve"}
        noDataMessage="No Pending Transactions Found"
        onRowClick={permissions.includes(pond.Permission.PERMISSION_WRITE) ? (row)=> {
            setSelectedTransaction(row)
            setFromType(row.transaction.fromObject)
            setFromOption({label: objectNames.get(row.transaction.fromKey) ?? "", value: row.transaction.fromKey})
            setToType(row.transaction.toObject)
            setToOption({label: objectNames.get(row.transaction.toKey) ?? "", value: row.transaction.toKey})
            setOpenDialog(true)
        } : undefined}
        rows={transactions}
        handleRowsPerPageChange={(e)=>{setTablePageSize(e.target.value)}}
        setPage={(page)=>{setTablePage(page)}}
        page={tablePage}
        pageSize={tablePageSize}
        total={total}
      />
    </Card>
    </React.Fragment>
  )
}