import DeleteIcon from "@mui/icons-material/DeleteOutlined";
import LinkOffIcon from "@mui/icons-material/LinkOff";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import RefreshIcon from "@mui/icons-material/Refresh";
import {
  Checkbox,
  FormControlLabel,
  IconButton,
  ListItemIcon,
  ListItemText,
  Menu,
  MenuItem,
  Tooltip,
} from "@mui/material";
import { DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";
import React from "react";
import { InboxApi, InboxEntry } from "../api/InboxApi";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
import { useUnmatchedInboxEntriesCount } from "../hooks/UnmatchedInboxEntriesCountProvider";
import { AmountWidget } from "../widgets/AmountWidget";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { DateWidget } from "../widgets/DateWidget";
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
import { MovementWidget } from "../widgets/MovementWidget";
import { NewMovementWidget } from "../widgets/NewMovementWidget";
import { UploadedFileWidget } from "../widgets/UploadedFileWidget";

export function InboxRoute(): React.ReactElement {
  const loadingMessage = useLoadingMessage();
  const alert = useAlert();

  const unmatched = useUnmatchedInboxEntriesCount();

  const [entries, setEntries] = React.useState<InboxEntry[] | undefined>();
  const [includeAttached, setIncludeAttached] = React.useState(false);

  const loadKey = React.useRef(1);

  const load = async () => {
    setEntries(await InboxApi.GetList(includeAttached));
  };

  const reload = async (skipEntries: boolean) => {
    try {
      loadingMessage.show("Refreshing the list of inbox entries...");
      unmatched.reload();
      if (!skipEntries) await load();
    } catch (e) {
      console.error("Failed to load list of inbox entries!", e);
      alert(`Failed to refresh the list of inbox entries! ${e}`);
    } finally {
      loadingMessage.hide();
    }
  };

  return (
    <MoneyMgrWebRouteContainer
      label={"Inbox"}
      actions={
        <span>
          <FormControlLabel
            checked={includeAttached}
            control={
              <Checkbox
                onChange={(_e, value) => {
                  setIncludeAttached(value);
                }}
              />
            }
            label="Include attached"
          />
          <Tooltip title="Refresh table">
            <IconButton onClick={() => reload(false)}>
              <RefreshIcon />
            </IconButton>
          </Tooltip>
        </span>
      }
    >
      <div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
        <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
          <AsyncWidget
            loadKey={loadKey.current + String(includeAttached)}
            load={load}
            errMsg="Failed to load the content of inbox!"
            build={() => (
              <InboxTable
                entries={entries!}
                onDeleteEntry={(del) => {
                  setEntries((entries) => {
                    return entries?.filter((m) => m.id !== del.id);
                  });
                }}
                onReload={reload}
                showMovements={includeAttached}
              />
            )}
          />
          <NewMovementWidget isInbox onCreated={() => reload(false)} />
        </div>
      </div>
    </MoneyMgrWebRouteContainer>
  );
}

function InboxTable(p: {
  entries: InboxEntry[];
  onDeleteEntry: (entry: InboxEntry) => void;
  onReload: (skipEntries: boolean) => void;
  showMovements?: boolean;
}): React.ReactElement {
  const alert = useAlert();
  const confirm = useConfirm();
  const snackbar = useSnackbar();
  const loadingMessage = useLoadingMessage();

  const [rowSelectionModel, setRowSelectionModel] =
    React.useState<GridRowSelectionModel>([]);

  // Delete inbox entry
  const handleDeleteClick = async (entry: InboxEntry) => {
    try {
      if (
        !(await confirm(
          `Do you really want to delete this inbox entry ${
            entry.label ?? ""
          } (${entry.amount ?? 0} €)?`
        ))
      )
        return;

      await InboxApi.Delete(entry);

      p.onDeleteEntry(entry);

      p.onReload(true);
    } catch (e) {
      console.error("Failed to delete entry!", e);
      alert(`Failed to delete entry! ${e}`);
    }
  };

  // Detach inbox entry from movement
  const handleDetachClick = async (entry: InboxEntry) => {
    try {
      if (
        !(await confirm(
          `Do you really want to detach this inbox entry ${
            entry.label ?? ""
          } (${entry.amount ?? 0} €) from its movement?`,
          "Detach inbox entry from movement",
          "Detach"
        ))
      )
        return;

      entry.movement_id = undefined;

      await InboxApi.Update(entry);

      p.onReload(true);
    } catch (e) {
      console.error("Failed to detach movement from entry!", e);
      alert(`Failed to detach movement from entry! ${e}`);
    }
  };

  const columns: GridColDef<(typeof p.entries)[number]>[] = [
    {
      field: "time",
      headerName: "Date",
      width: 98 + 80,
      editable: true,
      type: "dateTime",
      valueGetter(_, m) {
        return new Date(m.time * 1000);
      },
      valueSetter(v, row) {
        row.time = Math.floor(v.getTime() / 1000);
        return row;
      },
      renderCell: (params) => {
        return <DateWidget time={params.row.time} />;
      },
    },
    {
      field: "label",
      headerName: "Label",
      flex: 3,
      editable: true,
      type: "string",
    },
    {
      field: "amount",
      headerName: "Amount",
      width: 110,
      editable: true,
      type: "number",
      align: "left",
      headerAlign: "left",
      renderCell: (params) => {
        if (params.row.amount)
          return <AmountWidget amount={params.row.amount} />;
        else return <i>Unspecified</i>;
      },
    },
    {
      field: "file",
      headerName: "File",
      editable: false,
      flex: 3,
      renderCell: (params) => {
        return <UploadedFileWidget file_id={params.row.file_id} />;
      },
    },
    {
      field: "movement_id",
      headerName: "Movement",
      editable: false,
      flex: 3,
      renderCell: (params) => {
        if (params.row.movement_id)
          return <MovementWidget id={params.row.movement_id} />;
      },
    },
    {
      field: "actions",
      type: "actions",
      headerName: "",
      width: 55,
      cellClassName: "actions",
      editable: false,
      getActions: (params) => {
        return [
          <InboxEntryActionMenu
            key="menu"
            entry={params.row}
            onDelete={handleDeleteClick}
            onDetach={handleDetachClick}
          />,
        ];
      },
    },
  ];

  return (
    <div style={{ flex: 1 }}>
      <DataGrid<InboxEntry>
        columns={columns}
        rows={p.entries}
        autoPageSize
        checkboxSelection
        initialState={{
          sorting: {
            sortModel: [{ field: "time", sort: "desc" }],
          },
          columns: {
            columnVisibilityModel: {
              movement_id: p.showMovements!!,
            },
          },
        }}
        onRowSelectionModelChange={(newRowSelectionModel) => {
          setRowSelectionModel(newRowSelectionModel);
        }}
        rowSelectionModel={rowSelectionModel}
        processRowUpdate={async (n) => {
          try {
            return await InboxApi.Update(n);
          } catch (e) {
            console.error("Failed to update movement information!", e);
            alert(`Failed to update row! ${e}`);
            throw e;
          } finally {
            p.onReload(true);
          }
        }}
      />
    </div>
  );
}

function InboxEntryActionMenu(p: {
  entry: InboxEntry;
  onDelete: (entry: InboxEntry) => void;
  onDetach: (entry: InboxEntry) => void;
}): React.ReactElement {
  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
  const open = Boolean(anchorEl);
  const handleClick = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
  };
  const handleClose = () => {
    setAnchorEl(null);
  };
  return (
    <>
      <IconButton
        aria-label="Actions"
        aria-haspopup="true"
        onClick={handleClick}
      >
        <MoreVertIcon />
      </IconButton>
      <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
        {/* Unlink entry */}
        {p.entry.movement_id && (
          <MenuItem
            onClick={() => {
              handleClose();
              p.onDetach(p.entry);
            }}
          >
            <ListItemIcon>
              <LinkOffIcon />
            </ListItemIcon>
            <ListItemText secondary={"Detach the entry from its movement"}>
              Detach from movement
            </ListItemText>
          </MenuItem>
        )}

        {/* Delete */}
        <MenuItem
          onClick={() => {
            handleClose();
            p.onDelete(p.entry);
          }}
        >
          <ListItemIcon>
            <DeleteIcon color="error" />
          </ListItemIcon>
          <ListItemText secondary={"Delete the entry"}>Delete</ListItemText>
        </MenuItem>
      </Menu>
    </>
  );
}
