/*
 * 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 AttachmentIcon from '@mui/icons-material/Attachment';
import Link from '@mui/material/Link';
import { styled } from '@mui/material/styles';
import type { Certificate, CertificatePrincipal } from '@sophie/shared';
import type { TFunction } from 'i18next';
import { observer } from 'mobx-react-lite';
import React, { type ReactNode } from 'react';
import { useTranslation } from 'react-i18next';

const SingleCertificateDetailsRoot = styled('table')(({ theme }) => ({
  width: '100%',
  borderSpacing: `${theme.spacing(2)} 0`,
}));

const Header = styled('th')({
  textAlign: 'right',
  whiteSpace: 'nowrap',
  verticalAlign: 'top',
});

const SectionHeader = styled(Header)(({ theme }) => ({
  padding: `${theme.spacing(2)} 0`,
}));

const SubHeader = styled(Header)(({ theme }) => ({
  color: theme.palette.text.secondary,
}));

const Cell = styled('td')({
  verticalAlign: 'top',
});

function Section({ id, label }: { id: string; label: string }): JSX.Element {
  return (
    <tr>
      <SectionHeader id={id} scope="colgroup">
        {label}
      </SectionHeader>
      <Cell aria-hidden="true" />
    </tr>
  );
}

function Row({
  id,
  label,
  headers,
  children,
}: {
  id: string;
  label: string;
  headers: string;
  children: ReactNode;
}): JSX.Element {
  return (
    <tr>
      <SubHeader id={id} scope="row">
        {label}
      </SubHeader>
      <Cell headers={`${headers} ${id}`}>{children}</Cell>
    </tr>
  );
}

function OptionalRow({
  id,
  label,
  value,
  headers,
}: {
  id: string;
  label: string;
  value: string;
  headers: string;
}): JSX.Element | null {
  if (value === '') {
    // eslint-disable-next-line unicorn/no-null -- React requires `null` to skip rendering.
    return null;
  }

  return (
    <Row id={id} label={label} headers={headers}>
      {value}
    </Row>
  );
}

function DateTimeRow({
  id,
  label,
  value,
  locales,
  headers,
}: {
  id: string;
  label: string;
  value: number;
  locales: readonly string[];
  headers: string;
}): JSX.Element {
  // Electron provides `validStart` and `validExpiry` in seconds,
  // but `Date` expects milliseconds.
  const date = new Date(value * 1000);
  const dateStr = new Intl.DateTimeFormat([...locales], {
    dateStyle: 'long',
    timeStyle: 'long',
  }).format(date);

  return (
    <Row id={id} label={label} headers={headers}>
      {dateStr}
    </Row>
  );
}

function Principal({
  id,
  label,
  principal: {
    country,
    state,
    locality,
    organizations,
    organizationUnits,
    commonName,
  },
  t,
  onIssuerClick,
}: {
  id: string;
  label: string;
  principal: CertificatePrincipal;
  t: TFunction;
  onIssuerClick?: (() => void) | undefined;
}): JSX.Element {
  return (
    <>
      <Section id={id} label={label} />
      <OptionalRow
        id={`${id}-country`}
        label={t<string>('country')}
        value={country}
        headers={id}
      />
      <OptionalRow
        id={`${id}-state`}
        label={t<string>('state')}
        value={state}
        headers={id}
      />
      <OptionalRow
        id={`${id}-locality`}
        label={t<string>('locality')}
        value={locality}
        headers={id}
      />
      {/*
        eslint-disable react/no-array-index-key --
        These entries can freely contain repetitions, so we can only tell them apart by index.
      */}
      {organizations.map((value, i) => (
        <OptionalRow
          key={i}
          id={`${id}-organization-${i}`}
          label={t<string>('organization')}
          value={value}
          headers={id}
        />
      ))}
      {organizationUnits.map((value, i) => (
        <OptionalRow
          key={i}
          id={`${id}-organizationUnit-${i}`}
          label={t<string>('organizationUnit')}
          value={value}
          headers={id}
        />
      ))}
      {/* eslint-enable react/no-array-index-key */}
      {commonName !== '' && (
        <Row
          id={`${id}-commonName`}
          label={t<string>('commonName')}
          headers={id}
        >
          {onIssuerClick === undefined ? (
            commonName
          ) : (
            // eslint-disable-next-line jsx-a11y/anchor-is-valid -- This is a `button`.
            <Link
              component="button"
              variant="body1"
              onClick={onIssuerClick}
              sx={{ verticalAlign: 'baseline' }}
            >
              {commonName}
            </Link>
          )}
        </Row>
      )}
    </>
  );
}

Principal.defaultProps = {
  onIssuerClick: undefined,
};

function parseFingerprint(fingerprint: string): {
  algorithm: string | undefined;
  formattedValue: string;
} {
  const separatorIndex = fingerprint.indexOf('/');
  if (separatorIndex <= 0) {
    return {
      algorithm: undefined,
      formattedValue: fingerprint,
    };
  }
  return {
    algorithm: fingerprint.slice(0, separatorIndex).toUpperCase(),
    formattedValue: fingerprint.slice(separatorIndex + 1),
  };
}

function Fingerprint({
  id,
  label,
  fallbackAlgorithmLabel,
  value,
}: {
  id: string;
  label: string;
  fallbackAlgorithmLabel: string;
  value: string;
}) {
  const { algorithm, formattedValue } = parseFingerprint(value);
  return (
    <>
      <Section id={id} label={label} />
      <OptionalRow
        id={`${id}-fingerprint`}
        label={algorithm ?? fallbackAlgorithmLabel}
        headers={id}
        value={formattedValue}
      />
    </>
  );
}

function SingleCertificateDetails({
  detailsId,
  certificate: {
    subject,
    issuer,
    validStart,
    validExpiry,
    fingerprint,
    serialNumber,
  },
  onIssuerClick,
  onDownloadClick,
}: {
  detailsId: string;
  certificate: Certificate;
  onIssuerClick?: (() => void) | undefined;
  onDownloadClick: () => void;
}): JSX.Element {
  const {
    t,
    i18n: { languages },
  } = useTranslation(undefined, {
    keyPrefix: 'error.certificateError.details',
  });

  const id = `${detailsId}-${fingerprint}-certificate`;

  return (
    <SingleCertificateDetailsRoot>
      <tbody>
        <Principal
          id={`${id}-subject`}
          label={t<string>('subject')}
          principal={subject}
          t={t}
        />
        <Principal
          id={`${id}-issuer`}
          label={t<string>('issuer')}
          principal={issuer}
          t={t}
          onIssuerClick={onIssuerClick}
        />
        <Section id={`${id}-validity`} label={t<string>('validity')} />
        <DateTimeRow
          id={`${id}-validity-validStart`}
          label={t<string>('validStart')}
          value={validStart}
          locales={languages}
          headers={`${id}-validity`}
        />
        <DateTimeRow
          id={`${id}-validity-validExpiry`}
          label={t<string>('validExpiry')}
          value={validExpiry}
          locales={languages}
          headers={`${id}-validity`}
        />
        <Section
          id={`${id}-miscellaneous`}
          label={t<string>('miscellaneous')}
        />
        <OptionalRow
          id={`${id}-miscellaneous-serialNumber`}
          label={t<string>('serialNumber')}
          value={serialNumber}
          headers={`${id}-miscellaneous`}
        />
        <Row
          id={`${id}-miscellaneous-download`}
          label={t<string>('download')}
          headers={`${id}-miscellaneous`}
        >
          {/* eslint-disable-next-line jsx-a11y/anchor-is-valid -- This is a `button`. */}
          <Link
            component="button"
            variant="body1"
            onClick={onDownloadClick}
            sx={{ verticalAlign: 'baseline' }}
          >
            <AttachmentIcon fontSize="inherit" /> {t('downloadPEM')}
          </Link>
        </Row>
        <Fingerprint
          id={`${id}-fingerprint`}
          label={t<string>('fingerprint')}
          fallbackAlgorithmLabel={t<string>('fingerprintUnknown')}
          value={fingerprint}
        />
      </tbody>
    </SingleCertificateDetailsRoot>
  );
}

SingleCertificateDetails.defaultProps = {
  onIssuerClick: undefined,
};

export default observer(SingleCertificateDetails);
