import * as React from "react";
import { useContext, useState } from "react";
import {
  Alert,
  CardActions,
  CardContent,
  Chip,
  FormControl,
  FormControlLabel,
  LinearProgress,
  Link,
  Portal,
  Radio,
  RadioGroup,
  Select,
  Snackbar,
  Stack,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableRow,
  useMediaQuery,
  Tooltip,
  Typography,
  Container,
  Card,
  Button,
  Dialog,
  DialogTitle,
  DialogContent,
  TextField,
  IconButton,
  MenuItem,
  DialogContentText,
  useTheme,
} from "@mui/material";
import EditIcon from "@mui/icons-material/Edit";
import { Trans, useTranslation } from "react-i18next";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import humanizeDuration from "humanize-duration";
import CelebrationIcon from "@mui/icons-material/Celebration";
import CloseIcon from "@mui/icons-material/Close";
import { ContentCopy, Public } from "@mui/icons-material";
import AddIcon from "@mui/icons-material/Add";
import routes from "./routes";
import { copyToClipboard, formatBytes, formatShortDate, formatShortDateTime, openUrl } from "../app/utils";
import accountApi, { LimitBasis, Role, SubscriptionInterval, SubscriptionStatus } from "../app/AccountApi";
import { Pref, PrefGroup } from "./Pref";
import db from "../app/db";
import UpgradeDialog from "./UpgradeDialog";
import { AccountContext } from "./App";
import DialogFooter from "./DialogFooter";
import { Paragraph } from "./styles";
import { EmailVerificationCodeInvalidError, IncorrectPasswordError, UnauthorizedError } from "../app/errors";
import { ProChip } from "./SubscriptionPopup";
import session from "../app/Session";

const Account = () => {
  if (!session.exists()) {
    window.location.href = routes.app;
    return <></>;
  }
  return (
    <Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}>
      <Stack spacing={3}>
        <Basics />
        <Stats />
        <Tokens />
        <Delete />
      </Stack>
    </Container>
  );
};

const Basics = () => {
  const { t } = useTranslation();
  return (
    <Card sx={{ p: 3 }} aria-label={t("account_basics_title")}>
      <Typography variant="h5" sx={{ marginBottom: 2 }}>
        {t("account_basics_title")}
      </Typography>
      <PrefGroup>
        <Username />
        <ChangePassword />
        <Emails />
        <PhoneNumbers />
        <AccountType />
      </PrefGroup>
    </Card>
  );
};

const Username = () => {
  const { t } = useTranslation();
  const { account } = useContext(AccountContext);
  const labelId = "prefUsername";

  return (
    <Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
      <div aria-labelledby={labelId}>
        {session.username()}
        {account?.role === Role.ADMIN && (
          <>
            {" "}
            <Tooltip title={t("account_basics_username_admin_tooltip")}>
              <span style={{ cursor: "default" }}>👑</span>
            </Tooltip>
          </>
        )}
      </div>
    </Pref>
  );
};

const ChangePassword = () => {
  const { t } = useTranslation();
  const [dialogKey, setDialogKey] = useState(0);
  const [dialogOpen, setDialogOpen] = useState(false);
  const { account } = useContext(AccountContext);
  const labelId = "prefChangePassword";

  const handleDialogOpen = () => {
    setDialogKey((prev) => prev + 1);
    setDialogOpen(true);
  };

  const handleDialogClose = () => {
    setDialogOpen(false);
  };

  return (
    <Pref labelId={labelId} title={t("account_basics_password_title")} description={t("account_basics_password_description")}>
      <div aria-labelledby={labelId}>
        <Typography color="gray" sx={{ float: "left", fontSize: "0.7rem", lineHeight: "3.5" }}>
          ⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤
        </Typography>
        {!account?.provisioned ? (
          <Tooltip title={t("account_basics_password_description")}>
            <IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
              <EditIcon />
            </IconButton>
          </Tooltip>
        ) : (
          <Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
            <span>
              <IconButton disabled>
                <EditIcon />
              </IconButton>
            </span>
          </Tooltip>
        )}
      </div>
      <ChangePasswordDialog key={`changePasswordDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
    </Pref>
  );
};

const ChangePasswordDialog = (props) => {
  const theme = useTheme();
  const { t } = useTranslation();
  const [error, setError] = useState("");
  const [currentPassword, setCurrentPassword] = useState("");
  const [newPassword, setNewPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");
  const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));

  const handleDialogSubmit = async () => {
    try {
      console.debug(`[Account] Changing password`);
      await accountApi.changePassword(currentPassword, newPassword);
      props.onClose();
    } catch (e) {
      console.log(`[Account] Error changing password`, e);
      if (e instanceof IncorrectPasswordError) {
        setError(t("account_basics_password_dialog_current_password_incorrect"));
      } else if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else {
        setError(e.message);
      }
    }
  };

  return (
    <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
      <DialogTitle>{t("account_basics_password_dialog_title")}</DialogTitle>
      <DialogContent>
        <TextField
          margin="dense"
          id="current-password"
          label={t("account_basics_password_dialog_current_password_label")}
          aria-label={t("account_basics_password_dialog_current_password_label")}
          type="password"
          value={currentPassword}
          onChange={(ev) => setCurrentPassword(ev.target.value)}
          fullWidth
          variant="standard"
        />
        <TextField
          margin="dense"
          id="new-password"
          label={t("account_basics_password_dialog_new_password_label")}
          aria-label={t("account_basics_password_dialog_new_password_label")}
          type="password"
          value={newPassword}
          onChange={(ev) => setNewPassword(ev.target.value)}
          fullWidth
          variant="standard"
        />
        <TextField
          margin="dense"
          id="confirm"
          label={t("account_basics_password_dialog_confirm_password_label")}
          aria-label={t("account_basics_password_dialog_confirm_password_label")}
          type="password"
          value={confirmPassword}
          onChange={(ev) => setConfirmPassword(ev.target.value)}
          fullWidth
          variant="standard"
        />
      </DialogContent>
      <DialogFooter status={error}>
        <Button onClick={props.onClose}>{t("common_cancel")}</Button>
        <Button
          onClick={handleDialogSubmit}
          disabled={newPassword.length === 0 || currentPassword.length === 0 || newPassword !== confirmPassword}
        >
          {t("account_basics_password_dialog_button_submit")}
        </Button>
      </DialogFooter>
    </Dialog>
  );
};

const AccountType = () => {
  const { t, i18n } = useTranslation();
  const { account } = useContext(AccountContext);
  const [upgradeDialogKey, setUpgradeDialogKey] = useState(0);
  const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false);
  const [showPortalError, setShowPortalError] = useState(false);

  if (!account) {
    return <></>;
  }

  const handleUpgradeClick = () => {
    setUpgradeDialogKey((k) => k + 1);
    setUpgradeDialogOpen(true);
  };

  const handleManageBilling = async () => {
    try {
      const response = await accountApi.createBillingPortalSession();
      window.open(response.redirect_url, "billing_portal");
    } catch (e) {
      console.log(`[Account] Error opening billing portal`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else {
        setShowPortalError(true);
      }
    }
  };

  let accountType;
  if (account.role === Role.ADMIN) {
    const tierSuffix = account.tier
      ? t("account_basics_tier_admin_suffix_with_tier", {
          tier: account.tier.name,
        })
      : t("account_basics_tier_admin_suffix_no_tier");
    accountType = `${t("account_basics_tier_admin")} ${tierSuffix}`;
  } else if (!account.tier) {
    accountType = config.enable_payments ? t("account_basics_tier_free") : t("account_basics_tier_basic");
  } else {
    accountType = account.tier.name;
    if (account.billing?.interval === SubscriptionInterval.MONTH) {
      accountType += ` (${t("account_basics_tier_interval_monthly")})`;
    } else if (account.billing?.interval === SubscriptionInterval.YEAR) {
      accountType += ` (${t("account_basics_tier_interval_yearly")})`;
    }
  }

  return (
    <Pref
      alignTop={account.billing?.status === SubscriptionStatus.PAST_DUE || account.billing?.cancel_at > 0}
      title={t("account_basics_tier_title")}
      description={t("account_basics_tier_description")}
    >
      <div>
        {accountType}
        {account.billing?.paid_until && !account.billing?.cancel_at && (
          <Tooltip
            title={t("account_basics_tier_paid_until", {
              date: formatShortDate(account.billing?.paid_until, i18n.language),
            })}
          >
            <span>
              <InfoIcon />
            </span>
          </Tooltip>
        )}
        {config.enable_payments && account.role === Role.USER && !account.billing?.subscription && (
          <Button
            variant="outlined"
            size="small"
            startIcon={<CelebrationIcon sx={{ color: "#55b86e" }} />}
            onClick={handleUpgradeClick}
            sx={{ ml: 1 }}
          >
            {t("account_basics_tier_upgrade_button")}
          </Button>
        )}
        {config.enable_payments && account.role === Role.USER && account.billing?.subscription && (
          <Button variant="outlined" size="small" onClick={handleUpgradeClick} sx={{ ml: 1 }}>
            {t("account_basics_tier_change_button")}
          </Button>
        )}
        {config.enable_payments && account.role === Role.USER && account.billing?.customer && (
          <Button variant="outlined" size="small" onClick={handleManageBilling} sx={{ ml: 1 }}>
            {t("account_basics_tier_manage_billing_button")}
          </Button>
        )}
        {config.enable_payments && (
          <UpgradeDialog
            key={`upgradeDialogFromAccount${upgradeDialogKey}`}
            open={upgradeDialogOpen}
            onCancel={() => setUpgradeDialogOpen(false)}
          />
        )}
      </div>
      {account.billing?.status === SubscriptionStatus.PAST_DUE && (
        <Alert severity="error" sx={{ mt: 1 }}>
          {t("account_basics_tier_payment_overdue")}
        </Alert>
      )}
      {account.billing?.cancel_at > 0 && (
        <Alert severity="warning" sx={{ mt: 1 }}>
          {t("account_basics_tier_canceled_subscription", {
            date: formatShortDate(account.billing.cancel_at, i18n.language),
          })}
        </Alert>
      )}
      <Portal>
        <Snackbar
          open={showPortalError}
          autoHideDuration={3000}
          onClose={() => setShowPortalError(false)}
          message={t("account_usage_cannot_create_portal_session")}
        />
      </Portal>
    </Pref>
  );
};

const Emails = () => {
  const { t } = useTranslation();
  const { account } = useContext(AccountContext);
  const [dialogKey, setDialogKey] = useState(0);
  const [dialogOpen, setDialogOpen] = useState(false);
  const [snackOpen, setSnackOpen] = useState(false);
  const labelId = "prefVerifiedEmails";

  const handleDialogOpen = () => {
    setDialogKey((prev) => prev + 1);
    setDialogOpen(true);
  };

  const handleDialogClose = () => {
    setDialogOpen(false);
  };

  const handleCopy = (email) => {
    copyToClipboard(email);
    setSnackOpen(true);
  };

  const handleDelete = async (email) => {
    try {
      await accountApi.deleteEmail(email);
    } catch (e) {
      console.log(`[Account] Error deleting email`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      }
    }
  };

  if (!config.enable_email_verify) {
    return null;
  }

  if (account?.limits.emails === 0) {
    return (
      <Pref
        title={
          <>
            {t("account_basics_emails_title")}
            {config.enable_payments && <ProChip />}
          </>
        }
        description={t("account_basics_emails_description")}
      >
        <em>{t("account_usage_emails_none")}</em>
      </Pref>
    );
  }

  return (
    <Pref labelId={labelId} title={t("account_basics_emails_title")} description={t("account_basics_emails_description")}>
      <div aria-labelledby={labelId}>
        {account?.emails?.map((email) => (
          <Chip
            key={email}
            label={
              <Tooltip title={t("common_copy_to_clipboard")}>
                <span>{email}</span>
              </Tooltip>
            }
            variant="outlined"
            onClick={() => handleCopy(email)}
            onDelete={() => handleDelete(email)}
          />
        ))}
        {!account?.emails && <em>{t("account_basics_emails_no_emails_yet")}</em>}
        <IconButton onClick={handleDialogOpen}>
          <AddIcon />
        </IconButton>
      </div>
      <AddEmailDialog key={`addEmailDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
      <Portal>
        <Snackbar
          open={snackOpen}
          autoHideDuration={3000}
          onClose={() => setSnackOpen(false)}
          message={t("account_basics_emails_copied_to_clipboard")}
        />
      </Portal>
    </Pref>
  );
};

const AddEmailDialog = (props) => {
  const theme = useTheme();
  const { t } = useTranslation();
  const [error, setError] = useState("");
  const [email, setEmail] = useState("");
  const [code, setCode] = useState("");
  const [sending, setSending] = useState(false);
  const [verificationCodeSent, setVerificationCodeSent] = useState(false);
  const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));

  const verifyEmail = async () => {
    try {
      setSending(true);
      await accountApi.verifyEmail(email);
      setVerificationCodeSent(true);
    } catch (e) {
      console.log(`[Account] Error sending email verification`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else {
        setError(e.message);
      }
    } finally {
      setSending(false);
    }
  };

  const checkVerifyEmail = async () => {
    try {
      setSending(true);
      await accountApi.addEmail(email, code);
      props.onClose();
    } catch (e) {
      console.log(`[Account] Error confirming email verification`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else if (e instanceof EmailVerificationCodeInvalidError) {
        setError(t("account_basics_emails_dialog_code_invalid"));
      } else {
        setError(e.message);
      }
    } finally {
      setSending(false);
    }
  };

  const handleDialogSubmit = async () => {
    if (!verificationCodeSent) {
      await verifyEmail();
    } else {
      await checkVerifyEmail();
    }
  };

  const handleCancel = () => {
    if (verificationCodeSent) {
      setVerificationCodeSent(false);
      setCode("");
    } else {
      props.onClose();
    }
  };

  return (
    <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
      <DialogTitle>{t("account_basics_emails_dialog_title")}</DialogTitle>
      <DialogContent>
        <DialogContentText>{t("account_basics_emails_dialog_description")}</DialogContentText>
        {!verificationCodeSent && (
          <TextField
            margin="dense"
            label={t("account_basics_emails_dialog_email_label")}
            aria-label={t("account_basics_emails_dialog_email_label")}
            placeholder={t("account_basics_emails_dialog_email_placeholder")}
            type="email"
            value={email}
            onChange={(ev) => setEmail(ev.target.value)}
            fullWidth
            variant="standard"
          />
        )}
        {verificationCodeSent && (
          <TextField
            margin="dense"
            label={t("account_basics_emails_dialog_code_label")}
            aria-label={t("account_basics_emails_dialog_code_label")}
            placeholder={t("account_basics_emails_dialog_code_placeholder")}
            type="text"
            value={code}
            onChange={(ev) => setCode(ev.target.value)}
            fullWidth
            inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
            variant="standard"
          />
        )}
      </DialogContent>
      <DialogFooter status={error}>
        <Button onClick={handleCancel}>{verificationCodeSent ? t("common_back") : t("common_cancel")}</Button>
        <Button onClick={handleDialogSubmit} disabled={sending || !/^[^\s,;]+@[^\s,;]+$/.test(email)}>
          {!verificationCodeSent && t("account_basics_emails_dialog_verify_button")}
          {verificationCodeSent && t("account_basics_emails_dialog_check_verification_button")}
        </Button>
      </DialogFooter>
    </Dialog>
  );
};

const PhoneNumbers = () => {
  const { t } = useTranslation();
  const { account } = useContext(AccountContext);
  const [dialogKey, setDialogKey] = useState(0);
  const [dialogOpen, setDialogOpen] = useState(false);
  const [snackOpen, setSnackOpen] = useState(false);
  const labelId = "prefPhoneNumbers";

  const handleDialogOpen = () => {
    setDialogKey((prev) => prev + 1);
    setDialogOpen(true);
  };

  const handleDialogClose = () => {
    setDialogOpen(false);
  };

  const handleCopy = (phoneNumber) => {
    copyToClipboard(phoneNumber);
    setSnackOpen(true);
  };

  const handleDelete = async (phoneNumber) => {
    try {
      await accountApi.deletePhoneNumber(phoneNumber);
    } catch (e) {
      console.log(`[Account] Error deleting phone number`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      }
    }
  };

  if (!config.enable_calls) {
    return null;
  }

  if (account?.limits.calls === 0) {
    return (
      <Pref
        title={
          <>
            {t("account_basics_phone_numbers_title")}
            {config.enable_payments && <ProChip />}
          </>
        }
        description={t("account_basics_phone_numbers_description")}
      >
        <em>{t("account_usage_calls_none")}</em>
      </Pref>
    );
  }

  return (
    <Pref labelId={labelId} title={t("account_basics_phone_numbers_title")} description={t("account_basics_phone_numbers_description")}>
      <div aria-labelledby={labelId}>
        {account?.phone_numbers?.map((phoneNumber) => (
          <Chip
            label={
              <Tooltip title={t("common_copy_to_clipboard")}>
                <span>{phoneNumber}</span>
              </Tooltip>
            }
            variant="outlined"
            onClick={() => handleCopy(phoneNumber)}
            onDelete={() => handleDelete(phoneNumber)}
          />
        ))}
        {!account?.phone_numbers && <em>{t("account_basics_phone_numbers_no_phone_numbers_yet")}</em>}
        <IconButton onClick={handleDialogOpen}>
          <AddIcon />
        </IconButton>
      </div>
      <AddPhoneNumberDialog key={`addPhoneNumberDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
      <Portal>
        <Snackbar
          open={snackOpen}
          autoHideDuration={3000}
          onClose={() => setSnackOpen(false)}
          message={t("account_basics_phone_numbers_copied_to_clipboard")}
        />
      </Portal>
    </Pref>
  );
};

const AddPhoneNumberDialog = (props) => {
  const theme = useTheme();
  const { t } = useTranslation();
  const [error, setError] = useState("");
  const [phoneNumber, setPhoneNumber] = useState("");
  const [channel, setChannel] = useState("sms");
  const [code, setCode] = useState("");
  const [sending, setSending] = useState(false);
  const [verificationCodeSent, setVerificationCodeSent] = useState(false);
  const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));

  const verifyPhone = async () => {
    try {
      setSending(true);
      await accountApi.verifyPhoneNumber(phoneNumber, channel);
      setVerificationCodeSent(true);
    } catch (e) {
      console.log(`[Account] Error sending verification`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else {
        setError(e.message);
      }
    } finally {
      setSending(false);
    }
  };

  const checkVerifyPhone = async () => {
    try {
      setSending(true);
      await accountApi.addPhoneNumber(phoneNumber, code);
      props.onClose();
    } catch (e) {
      console.log(`[Account] Error confirming verification`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else {
        setError(e.message);
      }
    } finally {
      setSending(false);
    }
  };

  const handleDialogSubmit = async () => {
    if (!verificationCodeSent) {
      await verifyPhone();
    } else {
      await checkVerifyPhone();
    }
  };

  const handleCancel = () => {
    if (verificationCodeSent) {
      setVerificationCodeSent(false);
      setCode("");
    } else {
      props.onClose();
    }
  };

  return (
    <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
      <DialogTitle>{t("account_basics_phone_numbers_dialog_title")}</DialogTitle>
      <DialogContent>
        <DialogContentText>{t("account_basics_phone_numbers_dialog_description")}</DialogContentText>
        {!verificationCodeSent && (
          <div style={{ display: "flex" }}>
            <TextField
              margin="dense"
              label={t("account_basics_phone_numbers_dialog_number_label")}
              aria-label={t("account_basics_phone_numbers_dialog_number_label")}
              placeholder={t("account_basics_phone_numbers_dialog_number_placeholder")}
              type="tel"
              value={phoneNumber}
              onChange={(ev) => setPhoneNumber(ev.target.value)}
              inputProps={{ inputMode: "tel", pattern: "+[0-9]*" }}
              variant="standard"
              sx={{ flexGrow: 1 }}
            />
            <FormControl sx={{ flexWrap: "nowrap" }}>
              <RadioGroup row sx={{ flexGrow: 1, marginTop: "8px", marginLeft: "5px" }}>
                <FormControlLabel
                  value="sms"
                  control={<Radio checked={channel === "sms"} onChange={(e) => setChannel(e.target.value)} />}
                  label={t("account_basics_phone_numbers_dialog_channel_sms")}
                />
                <FormControlLabel
                  value="call"
                  control={<Radio checked={channel === "call"} onChange={(e) => setChannel(e.target.value)} />}
                  label={t("account_basics_phone_numbers_dialog_channel_call")}
                  sx={{ marginRight: 0 }}
                />
              </RadioGroup>
            </FormControl>
          </div>
        )}
        {verificationCodeSent && (
          <TextField
            margin="dense"
            label={t("account_basics_phone_numbers_dialog_code_label")}
            aria-label={t("account_basics_phone_numbers_dialog_code_label")}
            placeholder={t("account_basics_phone_numbers_dialog_code_placeholder")}
            type="text"
            value={code}
            onChange={(ev) => setCode(ev.target.value)}
            fullWidth
            inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
            variant="standard"
          />
        )}
      </DialogContent>
      <DialogFooter status={error}>
        <Button onClick={handleCancel}>{verificationCodeSent ? t("common_back") : t("common_cancel")}</Button>
        <Button onClick={handleDialogSubmit} disabled={sending || !/^\+\d+$/.test(phoneNumber)}>
          {!verificationCodeSent && channel === "sms" && t("account_basics_phone_numbers_dialog_verify_button_sms")}
          {!verificationCodeSent && channel === "call" && t("account_basics_phone_numbers_dialog_verify_button_call")}
          {verificationCodeSent && t("account_basics_phone_numbers_dialog_check_verification_button")}
        </Button>
      </DialogFooter>
    </Dialog>
  );
};

const Stats = () => {
  const { t, i18n } = useTranslation();
  const { account } = useContext(AccountContext);

  if (!account) {
    return <></>;
  }

  const normalize = (value, max) => Math.min((value / max) * 100, 100);

  return (
    <Card sx={{ p: 3 }} aria-label={t("account_usage_title")}>
      <Typography variant="h5" sx={{ marginBottom: 2 }}>
        {t("account_usage_title")}
      </Typography>
      <PrefGroup>
        {(account.role === Role.ADMIN || account.limits.reservations > 0) && (
          <Pref title={t("account_usage_reservations_title")}>
            <div>
              <Typography variant="body2" sx={{ float: "left" }}>
                {account.stats.reservations.toLocaleString()}
              </Typography>
              <Typography variant="body2" sx={{ float: "right" }}>
                {account.role === Role.USER
                  ? t("account_usage_of_limit", {
                      limit: account.limits.reservations.toLocaleString(),
                    })
                  : t("account_usage_unlimited")}
              </Typography>
            </div>
            <LinearProgress
              variant="determinate"
              value={
                account.role === Role.USER && account.limits.reservations > 0
                  ? normalize(account.stats.reservations, account.limits.reservations)
                  : 100
              }
            />
          </Pref>
        )}
        <Pref
          title={
            <>
              {t("account_usage_messages_title")}
              <Tooltip title={t("account_usage_limits_reset_daily")}>
                <span>
                  <InfoIcon />
                </span>
              </Tooltip>
            </>
          }
        >
          <div>
            <Typography variant="body2" sx={{ float: "left" }}>
              {account.stats.messages.toLocaleString()}
            </Typography>
            <Typography variant="body2" sx={{ float: "right" }}>
              {account.role === Role.USER
                ? t("account_usage_of_limit", {
                    limit: account.limits.messages.toLocaleString(),
                  })
                : t("account_usage_unlimited")}
            </Typography>
          </div>
          <LinearProgress
            variant="determinate"
            value={account.role === Role.USER ? normalize(account.stats.messages, account.limits.messages) : 100}
          />
        </Pref>
        {config.enable_emails && (
          <Pref
            title={
              <>
                {t("account_usage_emails_title")}
                <Tooltip title={t("account_usage_limits_reset_daily")}>
                  <span>
                    <InfoIcon />
                  </span>
                </Tooltip>
              </>
            }
          >
            <div>
              <Typography variant="body2" sx={{ float: "left" }}>
                {account.stats.emails.toLocaleString()}
              </Typography>
              <Typography variant="body2" sx={{ float: "right" }}>
                {account.role === Role.USER
                  ? t("account_usage_of_limit", {
                      limit: account.limits.emails.toLocaleString(),
                    })
                  : t("account_usage_unlimited")}
              </Typography>
            </div>
            <LinearProgress
              variant="determinate"
              value={account.role === Role.USER ? normalize(account.stats.emails, account.limits.emails) : 100}
            />
          </Pref>
        )}
        {config.enable_calls && (account.role === Role.ADMIN || account.limits.calls > 0) && (
          <Pref
            title={
              <>
                {t("account_usage_calls_title")}
                <Tooltip title={t("account_usage_limits_reset_daily")}>
                  <span>
                    <InfoIcon />
                  </span>
                </Tooltip>
              </>
            }
          >
            <div>
              <Typography variant="body2" sx={{ float: "left" }}>
                {account.stats.calls.toLocaleString()}
              </Typography>
              <Typography variant="body2" sx={{ float: "right" }}>
                {account.role === Role.USER
                  ? t("account_usage_of_limit", {
                      limit: account.limits.calls.toLocaleString(),
                    })
                  : t("account_usage_unlimited")}
              </Typography>
            </div>
            <LinearProgress
              variant="determinate"
              value={account.role === Role.USER && account.limits.calls > 0 ? normalize(account.stats.calls, account.limits.calls) : 100}
            />
          </Pref>
        )}
        <Pref
          alignTop
          title={t("account_usage_attachment_storage_title")}
          description={t("account_usage_attachment_storage_description", {
            filesize: formatBytes(account.limits.attachment_file_size),
            expiry: humanizeDuration(account.limits.attachment_expiry_duration * 1000, {
              language: i18n.resolvedLanguage,
              fallbacks: ["en"],
            }),
          })}
        >
          <div>
            <Typography variant="body2" sx={{ float: "left" }}>
              {formatBytes(account.stats.attachment_total_size)}
            </Typography>
            <Typography variant="body2" sx={{ float: "right" }}>
              {account.role === Role.USER
                ? t("account_usage_of_limit", {
                    limit: formatBytes(account.limits.attachment_total_size),
                  })
                : t("account_usage_unlimited")}
            </Typography>
          </div>
          <LinearProgress
            variant="determinate"
            value={account.role === Role.USER ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100}
          />
        </Pref>
        {config.enable_reservations && account.role === Role.USER && account.limits.reservations === 0 && (
          <Pref
            title={
              <>
                {t("account_usage_reservations_title")}
                {config.enable_payments && <ProChip />}
              </>
            }
          >
            <em>{t("account_usage_reservations_none")}</em>
          </Pref>
        )}
        {config.enable_calls && account.role === Role.USER && account.limits.calls === 0 && (
          <Pref
            title={
              <>
                {t("account_usage_calls_title")}
                {config.enable_payments && <ProChip />}
              </>
            }
          >
            <em>{t("account_usage_calls_none")}</em>
          </Pref>
        )}
      </PrefGroup>
      {account.role === Role.USER && account.limits.basis === LimitBasis.IP && (
        <Typography variant="body1" sx={{ pt: 3 }}>
          {t("account_usage_basis_ip_description")}
        </Typography>
      )}
    </Card>
  );
};

const InfoIcon = () => (
  <InfoOutlinedIcon
    sx={{
      verticalAlign: "middle",
      width: "18px",
      marginLeft: "4px",
      color: "gray",
    }}
  />
);

const Tokens = () => {
  const { t } = useTranslation();
  const { account } = useContext(AccountContext);
  const [dialogKey, setDialogKey] = useState(0);
  const [dialogOpen, setDialogOpen] = useState(false);
  const tokens = account?.tokens || [];

  const handleCreateClick = () => {
    setDialogKey((prev) => prev + 1);
    setDialogOpen(true);
  };

  const handleDialogClose = () => {
    setDialogOpen(false);
  };

  return (
    <Card sx={{ padding: 1 }} aria-label={t("prefs_users_title")}>
      <CardContent sx={{ paddingBottom: 1 }}>
        <Typography variant="h5" sx={{ marginBottom: 2 }}>
          {t("account_tokens_title")}
        </Typography>
        <Paragraph>
          <Trans
            i18nKey="account_tokens_description"
            components={{
              Link: <Link href="/docs/publish/#access-tokens" />,
            }}
          />
        </Paragraph>
        <div style={{ width: "100%", overflowX: "auto" }}>{tokens?.length > 0 && <TokensTable tokens={tokens} />}</div>
      </CardContent>
      <CardActions>
        <Button onClick={handleCreateClick}>{t("account_tokens_table_create_token_button")}</Button>
      </CardActions>
      <TokenDialog key={`tokenDialogCreate${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
    </Card>
  );
};

const TokensTable = (props) => {
  const { t, i18n } = useTranslation();
  const [snackOpen, setSnackOpen] = useState(false);
  const [upsertDialogKey, setUpsertDialogKey] = useState(0);
  const [upsertDialogOpen, setUpsertDialogOpen] = useState(false);
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
  const [selectedToken, setSelectedToken] = useState(null);

  const tokens = (props.tokens || []).sort((a, b) => {
    if (a.token === session.token()) {
      return -1;
    }
    if (b.token === session.token()) {
      return 1;
    }
    return a.token.localeCompare(b.token);
  });

  const handleEditClick = (token) => {
    setUpsertDialogKey((prev) => prev + 1);
    setSelectedToken(token);
    setUpsertDialogOpen(true);
  };

  const handleDialogClose = () => {
    setUpsertDialogOpen(false);
    setDeleteDialogOpen(false);
    setSelectedToken(null);
  };

  const handleDeleteClick = async (token) => {
    setSelectedToken(token);
    setDeleteDialogOpen(true);
  };

  const handleCopy = async (token) => {
    copyToClipboard(token);
    setSnackOpen(true);
  };

  return (
    <Table size="small" aria-label={t("account_tokens_title")}>
      <TableHead>
        <TableRow>
          <TableCell sx={{ paddingLeft: 0 }}>{t("account_tokens_table_token_header")}</TableCell>
          <TableCell>{t("account_tokens_table_label_header")}</TableCell>
          <TableCell>{t("account_tokens_table_expires_header")}</TableCell>
          <TableCell>{t("account_tokens_table_last_access_header")}</TableCell>
          <TableCell />
        </TableRow>
      </TableHead>
      <TableBody>
        {tokens.map((token) => {
          const hasLastAccess = Number.isFinite(token.last_access) && token.last_access > 0;
          const hasLastOrigin = !!token.last_origin;

          return (
            <TableRow key={token.token} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
              <TableCell
                component="th"
                scope="row"
                sx={{ paddingLeft: 0, whiteSpace: "nowrap" }}
                aria-label={t("account_tokens_table_token_header")}
              >
                <span>
                  <span style={{ fontFamily: "Monospace", fontSize: "0.9rem" }}>{token.token.slice(0, 12)}</span>
                  ...
                  <Tooltip title={t("common_copy_to_clipboard")} placement="right">
                    <IconButton onClick={() => handleCopy(token.token)}>
                      <ContentCopy />
                    </IconButton>
                  </Tooltip>
                </span>
              </TableCell>
              <TableCell aria-label={t("account_tokens_table_label_header")}>
                {token.token === session.token() && <em>{t("account_tokens_table_current_session")}</em>}
                {token.token !== session.token() && (token.label || "-")}
              </TableCell>
              <TableCell sx={{ whiteSpace: "nowrap" }} aria-label={t("account_tokens_table_expires_header")}>
                {token.expires ? formatShortDateTime(token.expires, i18n.language) : <em>{t("account_tokens_table_never_expires")}</em>}
              </TableCell>
              <TableCell sx={{ whiteSpace: "nowrap" }} aria-label={t("account_tokens_table_last_access_header")}>
                <div style={{ display: "flex", alignItems: "center" }}>
                  {hasLastAccess ? <span>{formatShortDateTime(token.last_access, i18n.language)}</span> : <em>-</em>}
                  {hasLastOrigin && (
                    <Tooltip
                      title={t("account_tokens_table_last_origin_tooltip", {
                        ip: token.last_origin,
                      })}
                    >
                      <IconButton onClick={() => openUrl(`https://whatismyipaddress.com/ip/${token.last_origin}`)}>
                        <Public />
                      </IconButton>
                    </Tooltip>
                  )}
                </div>
              </TableCell>
              <TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
                {token.token !== session.token() && !token.provisioned && (
                  <>
                    <Tooltip title={t("account_tokens_dialog_title_edit")}>
                      <IconButton onClick={() => handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}>
                        <EditIcon />
                      </IconButton>
                    </Tooltip>
                    <Tooltip title={t("account_tokens_dialog_title_delete")}>
                      <IconButton onClick={() => handleDeleteClick(token)} aria-label={t("account_tokens_dialog_title_delete")}>
                        <CloseIcon />
                      </IconButton>
                    </Tooltip>
                  </>
                )}
                {token.token === session.token() && (
                  <Tooltip title={t("account_tokens_table_cannot_delete_or_edit")}>
                    <span>
                      <IconButton disabled>
                        <EditIcon />
                      </IconButton>
                      <IconButton disabled>
                        <CloseIcon />
                      </IconButton>
                    </span>
                  </Tooltip>
                )}
                {token.provisioned && (
                  <Tooltip title={t("account_tokens_table_cannot_delete_or_edit_provisioned_token")}>
                    <span>
                      <IconButton disabled>
                        <EditIcon />
                      </IconButton>
                      <IconButton disabled>
                        <CloseIcon />
                      </IconButton>
                    </span>
                  </Tooltip>
                )}
              </TableCell>
            </TableRow>
          );
        })}
      </TableBody>
      <Portal>
        <Snackbar
          open={snackOpen}
          autoHideDuration={3000}
          onClose={() => setSnackOpen(false)}
          message={t("account_tokens_table_copied_to_clipboard")}
        />
      </Portal>
      <TokenDialog key={`tokenDialogEdit${upsertDialogKey}`} open={upsertDialogOpen} token={selectedToken} onClose={handleDialogClose} />
      <TokenDeleteDialog open={deleteDialogOpen} token={selectedToken} onClose={handleDialogClose} />
    </Table>
  );
};

const TokenDialog = (props) => {
  const theme = useTheme();
  const { t } = useTranslation();
  const [error, setError] = useState("");
  const [label, setLabel] = useState(props.token?.label || "");
  const [expires, setExpires] = useState(props.token ? -1 : 0);
  const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
  const editMode = !!props.token;

  const handleSubmit = async () => {
    try {
      if (editMode) {
        await accountApi.updateToken(props.token.token, label, expires);
      } else {
        await accountApi.createToken(label, expires);
      }
      props.onClose();
    } catch (e) {
      console.log(`[Account] Error creating token`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else {
        setError(e.message);
      }
    }
  };

  return (
    <Dialog open={props.open} onClose={props.onClose} maxWidth="sm" fullWidth fullScreen={fullScreen}>
      <DialogTitle>{editMode ? t("account_tokens_dialog_title_edit") : t("account_tokens_dialog_title_create")}</DialogTitle>
      <DialogContent>
        <TextField
          margin="dense"
          id="token-label"
          label={t("account_tokens_dialog_label")}
          aria-label={t("account_delete_dialog_label")}
          type="text"
          value={label}
          onChange={(ev) => setLabel(ev.target.value)}
          fullWidth
          variant="standard"
        />
        <FormControl fullWidth variant="standard" sx={{ mt: 1 }}>
          <Select value={expires} onChange={(ev) => setExpires(ev.target.value)} aria-label={t("account_tokens_dialog_expires_label")}>
            {editMode && <MenuItem value={-1}>{t("account_tokens_dialog_expires_unchanged")}</MenuItem>}
            <MenuItem value={0}>{t("account_tokens_dialog_expires_never")}</MenuItem>
            <MenuItem value={21600}>{t("account_tokens_dialog_expires_x_hours", { hours: 6 })}</MenuItem>
            <MenuItem value={43200}>{t("account_tokens_dialog_expires_x_hours", { hours: 12 })}</MenuItem>
            <MenuItem value={259200}>{t("account_tokens_dialog_expires_x_days", { days: 3 })}</MenuItem>
            <MenuItem value={604800}>{t("account_tokens_dialog_expires_x_days", { days: 7 })}</MenuItem>
            <MenuItem value={2592000}>{t("account_tokens_dialog_expires_x_days", { days: 30 })}</MenuItem>
            <MenuItem value={7776000}>{t("account_tokens_dialog_expires_x_days", { days: 90 })}</MenuItem>
            <MenuItem value={15552000}>{t("account_tokens_dialog_expires_x_days", { days: 180 })}</MenuItem>
          </Select>
        </FormControl>
      </DialogContent>
      <DialogFooter status={error}>
        <Button onClick={props.onClose}>{t("account_tokens_dialog_button_cancel")}</Button>
        <Button onClick={handleSubmit}>
          {editMode ? t("account_tokens_dialog_button_update") : t("account_tokens_dialog_button_create")}
        </Button>
      </DialogFooter>
    </Dialog>
  );
};

const TokenDeleteDialog = (props) => {
  const { t } = useTranslation();
  const [error, setError] = useState("");

  const handleSubmit = async () => {
    try {
      await accountApi.deleteToken(props.token.token);
      props.onClose();
    } catch (e) {
      console.log(`[Account] Error deleting token`, e);
      if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else {
        setError(e.message);
      }
    }
  };

  return (
    <Dialog open={props.open} onClose={props.onClose}>
      <DialogTitle>{t("account_tokens_delete_dialog_title")}</DialogTitle>
      <DialogContent>
        <DialogContentText>
          <Trans i18nKey="account_tokens_delete_dialog_description" />
        </DialogContentText>
      </DialogContent>
      <DialogFooter status={error}>
        <Button onClick={props.onClose}>{t("common_cancel")}</Button>
        <Button onClick={handleSubmit} color="error">
          {t("account_tokens_delete_dialog_submit_button")}
        </Button>
      </DialogFooter>
    </Dialog>
  );
};

const Delete = () => {
  const { t } = useTranslation();
  return (
    <Card sx={{ p: 3 }} aria-label={t("account_delete_title")}>
      <Typography variant="h5" sx={{ marginBottom: 2 }}>
        {t("account_delete_title")}
      </Typography>
      <PrefGroup>
        <DeleteAccount />
      </PrefGroup>
    </Card>
  );
};

const DeleteAccount = () => {
  const { t } = useTranslation();
  const [dialogKey, setDialogKey] = useState(0);
  const [dialogOpen, setDialogOpen] = useState(false);
  const { account } = useContext(AccountContext);

  const handleDialogOpen = () => {
    setDialogKey((prev) => prev + 1);
    setDialogOpen(true);
  };

  const handleDialogClose = () => {
    setDialogOpen(false);
  };

  return (
    <Pref title={t("account_delete_title")} description={t("account_delete_description")}>
      <div>
        {!account?.provisioned ? (
          <Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
            {t("account_delete_title")}
          </Button>
        ) : (
          <Tooltip title={t("account_basics_cannot_edit_or_delete_provisioned_user")}>
            <span>
              <Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} disabled>
                {t("account_delete_title")}
              </Button>
            </span>
          </Tooltip>
        )}
      </div>
      <DeleteAccountDialog key={`deleteAccountDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
    </Pref>
  );
};

const DeleteAccountDialog = (props) => {
  const theme = useTheme();
  const { t } = useTranslation();
  const { account } = useContext(AccountContext);
  const [error, setError] = useState("");
  const [password, setPassword] = useState("");
  const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));

  const handleSubmit = async () => {
    try {
      await accountApi.delete(password);
      await db().delete();
      console.debug(`[Account] Account deleted`);
      await session.resetAndRedirect(routes.app);
    } catch (e) {
      console.log(`[Account] Error deleting account`, e);
      if (e instanceof IncorrectPasswordError) {
        setError(t("account_basics_password_dialog_current_password_incorrect"));
      } else if (e instanceof UnauthorizedError) {
        await session.resetAndRedirect(routes.login);
      } else {
        setError(e.message);
      }
    }
  };

  return (
    <Dialog open={props.open} onClose={props.onClose} fullScreen={fullScreen}>
      <DialogTitle>{t("account_delete_title")}</DialogTitle>
      <DialogContent>
        <Typography variant="body1">{t("account_delete_dialog_description")}</Typography>
        <TextField
          margin="dense"
          id="account-delete-confirm"
          label={t("account_delete_dialog_label")}
          aria-label={t("account_delete_dialog_label")}
          type="password"
          value={password}
          onChange={(ev) => setPassword(ev.target.value)}
          fullWidth
          variant="standard"
        />
        {account?.billing?.subscription && (
          <Alert severity="warning" sx={{ mt: 1 }}>
            {t("account_delete_dialog_billing_warning")}
          </Alert>
        )}
      </DialogContent>
      <DialogFooter status={error}>
        <Button onClick={props.onClose}>{t("account_delete_dialog_button_cancel")}</Button>
        <Button onClick={handleSubmit} color="error" disabled={password.length === 0}>
          {t("account_delete_dialog_button_submit")}
        </Button>
      </DialogFooter>
    </Dialog>
  );
};

export default Account;
