/*
 * Copyright (C)  2022 Kristóf Marussy <kristof@marussy.com>
 *
 * This file is part of Sophie.
 *
 * Sophie is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: AGPL-3.0-only
 */

import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import NoEncryptionIcon from '@mui/icons-material/NoEncryptionOutlined';
import SentimentVeryDissatisfiedIcon from '@mui/icons-material/SentimentVeryDissatisfied';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import type { TFunction } from 'i18next';
import { observer } from 'mobx-react-lite';
import React from 'react';
import { useTranslation } from 'react-i18next';

import type Service from '../../stores/Service.js';

import CertificateDetails from './CertificateDetails.js';

interface ErrorDetails {
  icon: JSX.Element;
  title: string;
  description: string;
  errorCode: string | undefined;
}

function formatError(service: Service, t: TFunction): ErrorDetails {
  const {
    settings: { name: serviceName },
    state,
  } = service;
  const { type } = state;
  switch (type) {
    case 'failed': {
      const { errorCode, errorDesc } = state;
      const errorCodeStr = errorCode.toString(10);
      return {
        icon: <ErrorOutlineIcon fontSize="inherit" />,
        title: t(
          [
            `error.failed.${errorCodeStr}.title`,
            'error.failed.unspecific.title',
          ],
          { serviceName },
        ),
        description: t(
          [
            `error.failed.${errorCodeStr}.description`,
            'error.failed.unspecific.description',
          ],
          { serviceName },
        ),
        errorCode:
          errorDesc.length > 0
            ? `${errorDesc} (${errorCodeStr})`
            : errorCodeStr,
      };
    }
    case 'certificateError': {
      const { errorCode } = state;
      // Avoid i18next namespace separators in the error code.
      const errorCodeSafe = errorCode.replaceAll(':', '_');
      return {
        icon: <NoEncryptionIcon fontSize="inherit" />,
        title: t(
          [
            `error.certificateError.${errorCodeSafe}.title`,
            'error.certificateError.unspecific.title',
          ],
          { serviceName },
        ),
        description: t(
          [
            `error.certificateError.${errorCodeSafe}.description`,
            'error.certificateError.unspecific.description',
          ],
          { serviceName },
        ),
        errorCode,
      };
    }
    case 'crashed': {
      const { reason, exitCode } = state;
      return {
        icon: <SentimentVeryDissatisfiedIcon fontSize="inherit" />,
        title: t(
          [`error.crashed.${reason}.title`, 'error.crashed.unspecific.title'],
          { serviceName },
        ),
        description: t(
          [
            `error.crashed.${reason}.description`,
            'error.crashed.unspecific.description',
          ],
          { serviceName },
        ),
        errorCode: `${reason} (${exitCode})`,
      };
    }
    default:
      return {
        icon: <ErrorOutlineIcon fontSize="inherit" />,
        title: t('error.unknown.title', { serviceName }),
        description: t('error.unknown.description', { serviceName }),
        errorCode: undefined,
      };
  }
}

function ErrorPage({ service }: { service: Service }): JSX.Element | null {
  const { t } = useTranslation(undefined);

  if (!service.hasError) {
    // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering.
    return null;
  }

  const {
    settings: { name: serviceName },
    state: { type: errorType },
  } = service;
  const { icon, title, description, errorCode } = formatError(service, t);

  return (
    <Box p={2}>
      <Box component="section" maxWidth={800} mx="auto" my={{ md: '10vh' }}>
        <Typography
          aria-hidden
          fontSize={{ xs: 48, md: 96 }}
          lineHeight={1}
          mb={2}
        >
          {icon}
        </Typography>
        <Typography
          component="h1"
          variant="h3"
          fontSize={{ xs: '2rem', md: '3rem' }}
          mb={2}
        >
          {title}
        </Typography>
        <Typography mb={4}>{description}</Typography>
        {errorCode !== undefined && (
          <Typography variant="body2" color="text.secondary" mb={2}>
            {t('error.errorCode', { errorCode })}
          </Typography>
        )}
        <Box display="flex" flexDirection="row" gap={1} mb={2}>
          <Button onClick={() => service.goHome()} variant="contained">
            {t('error.home', { serviceName })}
          </Button>
          {errorType !== 'certificateError' && (
            <Button onClick={() => service.reload()} variant="outlined">
              {t('error.reload')}
            </Button>
          )}
        </Box>
        <CertificateDetails service={service} />
      </Box>
    </Box>
  );
}

export default observer(ErrorPage);
