import DeleteIcon from "@mui/icons-material/DeleteOutlined";
import DriveFileMoveOutlineIcon from "@mui/icons-material/DriveFileMoveOutline";
import LinkOffIcon from "@mui/icons-material/LinkOff";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import RefreshIcon from "@mui/icons-material/Refresh";
import {
  IconButton,
  ListItemIcon,
  ListItemText,
  TextField,
  Tooltip,
  Typography,
} from "@mui/material";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { DataGrid, GridColDef, GridRowSelectionModel } from "@mui/x-data-grid";
import React from "react";
import { useParams } from "react-router-dom";
import { UploadedFile } from "../api/FileApi";
import { Movement, MovementApi } from "../api/MovementsApi";
import { useAccounts } from "../hooks/AccountsListProvider";
import { useAlert } from "../hooks/context_providers/AlertDialogProvider";
import { useSelectAccount } from "../hooks/context_providers/ChooseAccountDialogProvider";
import { useConfirm } from "../hooks/context_providers/ConfirmDialogProvider";
import { useLoadingMessage } from "../hooks/context_providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/context_providers/SnackbarProvider";
import { AccountIconWidget } from "../widgets/AccountIconWidget";
import { AmountWidget } from "../widgets/AmountWidget";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { DateWidget } from "../widgets/DateWidget";
import { UploadFileButton } from "../widgets/forms/UploadFileButton";
import { MoneyMgrWebRouteContainer } from "../widgets/MoneyMgrWebRouteContainer";
import { NewMovementWidget } from "../widgets/NewMovementWidget";
import { UploadedFileWidget } from "../widgets/UploadedFileWidget";
import { NotFoundRoute } from "./NotFound";

export function AccountRoute(): React.ReactElement {
  const loadingMessage = useLoadingMessage();

  const { accountId } = useParams();

  const loadKey = React.useRef(0);

  const accounts = useAccounts();
  const account = accounts.get(Number(accountId));

  const [movements, setMovements] = React.useState<Movement[] | undefined>();

  const load = async () => {
    setMovements(await MovementApi.GetAccountMovements(account!.id));
  };

  const reload = async (skipMovements = false) => {
    try {
      accounts.reloadBalances();
      if (!skipMovements) {
        loadingMessage.show("Refreshing the list of movements");
        await load();
      }
    } catch (e) {
      console.error("Failed to load list of movements!", e);
      alert(`Failed to refresh the list of movements! ${e}`);
    } finally {
      loadingMessage.hide();
    }
  };

  if (account === null) return <NotFoundRoute />;

  return (
    <MoneyMgrWebRouteContainer
      label={
        <span style={{ display: "inline-flex", alignItems: "center" }}>
          <AccountIconWidget account={account} />
          &nbsp;
          <span style={{ display: "inline-flex", flexDirection: "column" }}>
            <span>{account.name}</span>
            <Typography component={"span"} variant="subtitle1">
              <AmountWidget amount={account.balance} />
            </Typography>
          </span>
        </span>
      }
    >
      <div style={{ display: "flex", flexDirection: "column", flex: 1 }}>
        <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
          <AsyncWidget
            loadKey={`${account.id}-${loadKey.current}`}
            load={load}
            ready={movements !== undefined}
            errMsg="Failed to load the list of movements!"
            build={() => (
              <MovementsTable
                needReload={reload}
                movements={movements!}
                onDeleteMovement={(del) => {
                  setMovements((movements) => {
                    return movements?.filter((m) => m.id !== del.id);
                  });
                }}
              />
            )}
          />
        </div>
        <NewMovementWidget account={account} onCreated={reload} />
      </div>
    </MoneyMgrWebRouteContainer>
  );
}

function MovementsTable(p: {
  movements: Movement[];
  needReload: (skipMovements: boolean) => void;
  onDeleteMovement: (movement: Movement) => void;
}): React.ReactElement {
  const accounts = useAccounts();
  const alert = useAlert();
  const confirm = useConfirm();
  const snackbar = useSnackbar();
  const loadingMessage = useLoadingMessage();

  const chooseAccount = useSelectAccount();

  const [filter, setFilter] = React.useState("");

  const filteredList = React.useMemo(() => {
    return p.movements.filter(
      (m) =>
        m.label.toLowerCase().includes(filter.toLowerCase()) ||
        m.amount.toString().includes(filter)
    );
  }, [p.movements, filter]);

  const [rowSelectionModel, setRowSelectionModel] =
    React.useState<GridRowSelectionModel>({ type: "include", ids: new Set() });

  // Set uploaded file
  const setUploadedFile = async (
    m: Movement,
    file: UploadedFile | undefined
  ) => {
    try {
      await MovementApi.Update({ ...m, file_id: file?.id ?? undefined });

      p.needReload(false);
    } catch (e) {
      console.error("Failed to attach file to movement!", e);
      alert("Failed to attach uploaded file to movement!");
    }
  };

  // Change account of movement
  const handleMoveClick = async (movement: Movement) => {
    const targetAccount = await chooseAccount(
      "Transfer movement",
      `Please select the target account that will receive the movement: ${movement.label} (${movement.amount} €)`,
      "Transfer movement",
      [accounts.get(movement.account_id)!]
    );

    if (!targetAccount) return;

    try {
      movement.account_id = targetAccount.id;

      await MovementApi.Update(movement);

      snackbar("The movement has been successfully moved!");

      p.needReload(false);
    } catch (e) {
      console.error("Failed to update movement information!", e);
      alert(`Failed to change movemlent account! ${e}`);
    }
  };

  // Detach file from movement
  const handleDetachFile = async (movement: Movement) => {
    try {
      if (
        !(await confirm(
          `Do you really want to detach the file attached to the movement ${movement.label} (${movement.amount} €)? The associated file will be automatically deleted within the day if it is not referenced anywhere else!`,
          `Detach file from movement`,
          `Detach file`
        ))
      )
        return;

      await setUploadedFile(movement, undefined);
    } catch (e) {
      console.error("Failed to detach file from movement!", e);
      alert(`Failed to detach file from movement! ${e}`);
    }
  };

  // Delete movement
  const handleDeleteClick = async (movement: Movement) => {
    try {
      if (
        !(await confirm(
          `Do you really want to delete the movement ${movement.label} (${movement.amount} €)?`
        ))
      )
        return;

      await MovementApi.Delete(movement);

      p.onDeleteMovement(movement);

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

  // Move multiple movements
  const moveMultiple = async () => {
    try {
      const movements = p.movements.filter((m) =>
        rowSelectionModel.ids.has(m.id)
      );

      const targetAccount = await chooseAccount(
        "Transfer movements",
        <>
          Please select the target account that will receive the selected
          movements:
          <ul>
            {movements.map((m) => (
              <li key={m.id}>
                {m.label} ({m.amount} €)
              </li>
            ))}
          </ul>
        </>,
        "Transfer movement",
        [accounts.get(movements[0].account_id)!]
      );

      if (!targetAccount) return;

      for (const [num, m] of movements.entries()) {
        loadingMessage.show(`Moveing movement ${num}/${movements.length}`);

        m.account_id = targetAccount.id;
        await MovementApi.Update(m);
      }

      snackbar("The movements have been successfully moved!");

      p.needReload(false);
    } catch (e) {
      console.error("Failed to delete multiple movements!", e);
      alert(`Failed to delete multiple movements! ${e}`);
    } finally {
      loadingMessage.hide();
    }
  };

  // Delete multiple movements
  const deleteMultiple = async () => {
    try {
      const movements = p.movements.filter((m) =>
        rowSelectionModel.ids.has(m.id)
      );

      if (
        !(await confirm(
          <>
            Do you really want to delete the following movements:
            <ul>
              {movements.map((m) => (
                <li key={m.id}>
                  {m.label} ({m.amount} €)
                </li>
              ))}
            </ul>
          </>
        ))
      )
        return;

      for (const [num, m] of movements.entries()) {
        loadingMessage.show(`Deleting movement ${num}/${movements.length}`);

        await MovementApi.Delete(m);
      }

      snackbar("The movements have been successfully deleted!");

      p.needReload(false);
    } catch (e) {
      console.error("Failed to delete multiple movements!", e);
      alert(`Failed to delete multiple movements! ${e}`);
    } finally {
      loadingMessage.hide();
    }
  };

  const columns: GridColDef<(typeof p.movements)[number]>[] = [
    {
      field: "checked",
      headerName: "Checked",
      width: 50,
      type: "boolean",
      editable: true,
    },
    {
      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: 6,
      editable: true,
      type: "string",
    },
    {
      field: "amount",
      headerName: "Amount",
      width: 110,
      editable: true,
      type: "number",
      align: "left",
      headerAlign: "left",
      renderCell: (params) => {
        return <AmountWidget amount={params.row.amount} />;
      },
    },
    {
      field: "file",
      headerName: "File",
      editable: false,
      flex: 3,
      renderCell: (params) => {
        if (!params.row.file_id)
          return (
            <UploadFileButton
              label="Attach file"
              tooltip="Attach a file to this movement"
              onUploaded={(f) => setUploadedFile(params.row, f)}
            />
          );
        else return <UploadedFileWidget file_id={params.row.file_id} />;
      },
    },
    {
      field: "actions",
      type: "actions",
      headerName: "",
      width: 55,
      cellClassName: "actions",
      editable: false,
      getActions: (params) => {
        return [
          <MovementActionMenu
            key="menu"
            movement={params.row}
            onDelete={handleDeleteClick}
            onMove={handleMoveClick}
            onDetachFile={handleDetachFile}
          />,
        ];
      },
    },
  ];

  return (
    <>
      <div style={{ display: "flex" }}>
        <TextField
          placeholder="Filter by label or amount"
          variant="standard"
          size="small"
          value={filter}
          onChange={(e) => {
            setFilter(e.target.value);
          }}
          style={{ padding: "0px", flex: 1 }}
        />
        <span style={{ flex: 1 }}></span>
        <Tooltip title="Refresh table">
          <IconButton
            onClick={() => {
              p.needReload(false);
            }}
          >
            <RefreshIcon />
          </IconButton>
        </Tooltip>
        <Tooltip title="Move all the selected entries to another account">
          <IconButton
            disabled={
              rowSelectionModel.ids.size === 0 ||
              rowSelectionModel.ids.size === p.movements.length
            }
            onClick={moveMultiple}
          >
            <DriveFileMoveOutlineIcon />
          </IconButton>
        </Tooltip>
        <Tooltip title="Delete all the selected entries">
          <IconButton
            disabled={
              rowSelectionModel.ids.size === 0 ||
              rowSelectionModel.ids.size === p.movements.length
            }
            onClick={deleteMultiple}
          >
            <DeleteIcon />
          </IconButton>
        </Tooltip>
      </div>
      <div style={{ flex: 1 }}>
        <DataGrid<Movement>
          columns={columns}
          rows={filteredList}
          autoPageSize
          checkboxSelection
          initialState={{
            sorting: {
              sortModel: [{ field: "time", sort: "desc" }],
            },
            columns: {
              columnVisibilityModel: {
                checked: false,
              },
            },
          }}
          onRowSelectionModelChange={(newRowSelectionModel) => {
            setRowSelectionModel(newRowSelectionModel);
          }}
          rowSelectionModel={rowSelectionModel}
          processRowUpdate={async (n) => {
            try {
              return await MovementApi.Update(n);
            } catch (e) {
              console.error("Failed to update movement information!", e);
              alert(`Failed to update row! ${e}`);
              throw e;
            } finally {
              p.needReload(true);
            }
          }}
        />
      </div>
    </>
  );
}

function MovementActionMenu(p: {
  movement: Movement;
  onDetachFile: (m: Movement) => void;
  onMove: (m: Movement) => void;
  onDelete: (m: Movement) => 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}>
        {/* Detach file */}
        {p.movement.file_id && (
          <MenuItem
            onClick={() => {
              handleClose();
              p.onDetachFile(p.movement);
            }}
          >
            <ListItemIcon>
              <LinkOffIcon />
            </ListItemIcon>
            <ListItemText secondary={"Detach linked file"}>
              Detach file
            </ListItemText>
          </MenuItem>
        )}
        {/* Move to another account */}
        <MenuItem
          onClick={() => {
            handleClose();
            p.onMove(p.movement);
          }}
        >
          <ListItemIcon>
            <DriveFileMoveOutlineIcon />
          </ListItemIcon>
          <ListItemText secondary={"Move to another account"}>
            Move
          </ListItemText>
        </MenuItem>
        {/* Delete */}
        <MenuItem
          onClick={() => {
            handleClose();
            p.onDelete(p.movement);
          }}
        >
          <ListItemIcon>
            <DeleteIcon color="error" />
          </ListItemIcon>
          <ListItemText secondary={"Delete the movement"}>Delete</ListItemText>
        </MenuItem>
      </Menu>
    </>
  );
}
