import DeleteIcon from "@mui/icons-material/Delete";
import DownloadIcon from "@mui/icons-material/Download";
import LoopIcon from "@mui/icons-material/Loop";
import MoreVertIcon from "@mui/icons-material/MoreVert";
import RefreshIcon from "@mui/icons-material/Refresh";
import {
  Alert,
  Button,
  CircularProgress,
  IconButton,
  LinearProgress,
  ListItemIcon,
  ListItemText,
  Menu,
  MenuItem,
  Tooltip,
  Typography,
} from "@mui/material";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import { filesize } from "filesize";
import React from "react";
import { DiskImage, DiskImageApi } from "../api/DiskImageApi";
import { ServerApi } from "../api/ServerApi";
import { ConvertDiskImageDialog } from "../dialogs/ConvertDiskImageDialog";
import { useAlert } from "../hooks/providers/AlertDialogProvider";
import { useConfirm } from "../hooks/providers/ConfirmDialogProvider";
import { useLoadingMessage } from "../hooks/providers/LoadingMessageProvider";
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
import { downloadBlob } from "../utils/FilesUtils";
import { AsyncWidget } from "../widgets/AsyncWidget";
import { DateWidget } from "../widgets/DateWidget";
import { FileInput } from "../widgets/forms/FileInput";
import { VirtWebPaper } from "../widgets/VirtWebPaper";
import { VirtWebRouteContainer } from "../widgets/VirtWebRouteContainer";

export function DiskImagesRoute(): React.ReactElement {
  const [list, setList] = React.useState<DiskImage[] | undefined>();

  const loadKey = React.useRef(1);

  const load = async () => {
    setList(await DiskImageApi.GetList());
  };

  const reload = () => {
    loadKey.current += 1;
    setList(undefined);
  };

  return (
    <VirtWebRouteContainer
      label="Disk images management"
      actions={
        <span>
          <Tooltip title="Refresh Disk images list">
            <IconButton onClick={reload}>
              <RefreshIcon />
            </IconButton>
          </Tooltip>
        </span>
      }
    >
      <AsyncWidget
        loadKey={loadKey.current}
        errMsg="Failed to load disk images list!"
        load={load}
        ready={list !== undefined}
        build={() => (
          <>
            <UploadDiskImageCard onFileUploaded={reload} />
            <DiskImageList list={list!} onReload={reload} />
          </>
        )}
      />
    </VirtWebRouteContainer>
  );
}

function UploadDiskImageCard(p: {
  onFileUploaded: () => void;
}): React.ReactElement {
  const alert = useAlert();
  const snackbar = useSnackbar();

  const [value, setValue] = React.useState<File | null>(null);
  const [uploadProgress, setUploadProgress] = React.useState<number | null>(
    null
  );

  const handleChange = (newValue: File | null) => {
    if (
      newValue &&
      newValue.size > ServerApi.Config.constraints.disk_image_max_size
    ) {
      alert(
        `The file is too big (max size allowed: ${filesize(
          ServerApi.Config.constraints.disk_image_max_size
        )}`
      );
      return;
    }

    if (
      newValue &&
      newValue.type.length > 0 &&
      !ServerApi.Config.disk_images_mimetypes.includes(newValue.type)
    ) {
      alert(`Selected file mimetype is not allowed! (${newValue.type})`);
      return;
    }

    setValue(newValue);
  };

  const upload = async () => {
    try {
      setUploadProgress(0);
      await DiskImageApi.Upload(value!, setUploadProgress);

      setValue(null);
      snackbar("The file was successfully uploaded!");

      p.onFileUploaded();
    } catch (e) {
      console.error(e);
      await alert(`Failed to perform file upload! ${e}`);
    }

    setUploadProgress(null);
  };

  if (uploadProgress !== null) {
    return (
      <VirtWebPaper label="File upload" noHorizontalMargin>
        <Typography variant="body1">
          Upload in progress ({Math.floor(uploadProgress * 100)}%)...
        </Typography>
        <LinearProgress variant="determinate" value={uploadProgress * 100} />
      </VirtWebPaper>
    );
  }

  return (
    <VirtWebPaper label="Disk image upload" noHorizontalMargin>
      <div style={{ display: "flex", alignItems: "center" }}>
        <FileInput
          value={value}
          onChange={handleChange}
          style={{ flex: 1 }}
          slotProps={{
            htmlInput: {
              accept: ServerApi.Config.disk_images_mimetypes.join(","),
            },
          }}
        />

        {value && <Button onClick={upload}>Upload</Button>}
      </div>
    </VirtWebPaper>
  );
}

function DiskImageList(p: {
  list: DiskImage[];
  onReload: () => void;
}): React.ReactElement {
  const alert = useAlert();
  const snackbar = useSnackbar();
  const confirm = useConfirm();
  const loadingMessage = useLoadingMessage();

  const [dlProgress, setDlProgress] = React.useState<undefined | number>();

  const [currConversion, setCurrConversion] = React.useState<
    DiskImage | undefined
  >();

  // Download disk image file
  const downloadDiskImage = async (entry: DiskImage) => {
    setDlProgress(0);

    try {
      const blob = await DiskImageApi.Download(entry, setDlProgress);

      downloadBlob(blob, entry.file_name);
    } catch (e) {
      console.error(e);
      alert(`Failed to download disk image file! ${e}`);
    }

    setDlProgress(undefined);
  };

  // Convert disk image file
  const convertDiskImage = (entry: DiskImage) => {
    setCurrConversion(entry);
  };

  // Delete disk image
  const deleteDiskImage = async (entry: DiskImage) => {
    if (
      !(await confirm(
        `Do you really want to delete this disk image (${entry.file_name}) ?`
      ))
    )
      return;

    loadingMessage.show("Deleting disk image file...");

    try {
      await DiskImageApi.Delete(entry);
      snackbar("The disk image has been successfully deleted!");
      p.onReload();
    } catch (e) {
      console.error(e);
      alert(`Failed to delete disk image!\n${e}`);
    }

    loadingMessage.hide();
  };

  if (p.list.length === 0)
    return (
      <Typography variant="body1" style={{ textAlign: "center" }}>
        No disk image uploaded for now.
      </Typography>
    );

  const columns: GridColDef<(typeof p.list)[number]>[] = [
    { field: "file_name", headerName: "File name", flex: 3, editable: true },
    {
      field: "format",
      headerName: "Format",
      flex: 1,
      renderCell(params) {
        let content = params.row.format;

        if (params.row.format === "Raw") {
          content += params.row.is_sparse ? " (Sparse)" : " (Fixed)";
        }

        return content;
      },
    },
    {
      field: "file_size",
      headerName: "File size",
      flex: 1,
      renderCell(params) {
        let res = filesize(params.row.file_size);

        if (params.row.format === "QCow2") {
          res += ` (${filesize(params.row.virtual_size!)})`;
        }

        return res;
      },
    },
    {
      field: "created",
      headerName: "Created",
      flex: 1,
      renderCell(params) {
        return <DateWidget time={params.row.created} />;
      },
    },
    {
      field: "actions",
      type: "actions",
      headerName: "",
      width: 55,
      cellClassName: "actions",
      editable: false,
      getActions: (params) => {
        return [
          <DiskImageActionMenu
            key="menu"
            diskImage={params.row}
            onDownload={downloadDiskImage}
            onConvert={convertDiskImage}
            onDelete={deleteDiskImage}
          />,
        ];
      },
    },
  ];

  return (
    <>
      {/* Download notification */}
      {dlProgress !== undefined && (
        <Alert severity="info">
          <div
            style={{
              display: "flex",
              flexDirection: "row",
              alignItems: "center",
              overflow: "hidden",
            }}
          >
            <Typography variant="body1">
              Downloading... {dlProgress}%
            </Typography>
            <CircularProgress
              variant="determinate"
              size={"1.5rem"}
              style={{ marginLeft: "10px" }}
              value={dlProgress}
            />
          </div>
        </Alert>
      )}

      {/* Disk image conversion dialog */}
      {currConversion && (
        <ConvertDiskImageDialog
          image={currConversion}
          onCancel={() => {
            setCurrConversion(undefined);
          }}
          onFinished={() => {
            setCurrConversion(undefined);
            p.onReload();
          }}
        />
      )}

      {/* The table itself */}
      <DataGrid<DiskImage>
        getRowId={(c) => c.file_name}
        rows={p.list}
        columns={columns}
        processRowUpdate={async (n, o) => {
          try {
            await DiskImageApi.Rename(o, n.file_name);
            return n;
          } catch (e) {
            console.error("Failed to rename disk image!", e);
            alert(`Failed to rename disk image! ${e}`);
            throw e;
          } finally {
            p.onReload();
          }
        }}
      />
    </>
  );
}

function DiskImageActionMenu(p: {
  diskImage: DiskImage;
  onDownload: (d: DiskImage) => void;
  onConvert: (d: DiskImage) => void;
  onDelete: (d: DiskImage) => 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}>
        {/* Download disk image */}
        <MenuItem
          onClick={() => {
            handleClose();
            p.onDownload(p.diskImage);
          }}
        >
          <ListItemIcon>
            <DownloadIcon />
          </ListItemIcon>
          <ListItemText secondary={"Download disk image"}>
            Download
          </ListItemText>
        </MenuItem>

        {/* Convert disk image */}
        <MenuItem
          onClick={() => {
            handleClose();
            p.onConvert(p.diskImage);
          }}
        >
          <ListItemIcon>
            <LoopIcon />
          </ListItemIcon>
          <ListItemText secondary={"Convert disk image"}>Convert</ListItemText>
        </MenuItem>

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