import {
  ShowQrCodeCallbacks,
  ShowSasCallbacks,
  VerificationPhase,
  VerificationRequest,
  Verifier,
} from 'matrix-js-sdk/lib/crypto-api';
import React, { CSSProperties, useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { VerificationMethod } from 'matrix-js-sdk/lib/types';
import QRCode from 'qrcode';
import {
  Box,
  Button,
  config,
  Dialog,
  Header,
  Icon,
  IconButton,
  Icons,
  Overlay,
  OverlayBackdrop,
  OverlayCenter,
  Spinner,
  Text,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import {
  useVerificationRequestPhase,
  useVerificationRequestReceived,
  useVerifierCancel,
  useVerifierShowReciprocateQr,
  useVerifierShowSas,
} from '../hooks/useVerificationRequest';
import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback';
import { ContainerColor } from '../styles/ContainerColor.css';
import { useModalStyle } from '../hooks/useModalStyle';
import { QrScanner } from './QrScanner';

const DialogHeaderStyles: CSSProperties = {
  padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
  borderBottomWidth: config.borderWidth.B300,
};

type WaitingMessageProps = {
  message: string;
};
function WaitingMessage({ message }: WaitingMessageProps) {
  return (
    <Box alignItems="Center" gap="200">
      <Spinner variant="Secondary" size="200" />
      <Text size="T300">{message}</Text>
    </Box>
  );
}

type VerificationUnexpectedProps = { message: string; onClose: () => void };
function VerificationUnexpected({ message, onClose }: VerificationUnexpectedProps) {
  const { t } = useTranslation();
  return (
    <Box direction="Column" gap="400">
      <Text>{message}</Text>
      <Button variant="Secondary" fill="Soft" onClick={onClose}>
        <Text size="B400">{t('Organisms.DeviceVerification.close')}</Text>
      </Button>
    </Box>
  );
}

function VerificationWaitAccept() {
  const { t } = useTranslation();
  return (
    <Box direction="Column" gap="400">
      <Text>{t('Organisms.DeviceVerification.please_accept')}</Text>
      <WaitingMessage message={t('Organisms.DeviceVerification.waiting_accept')} />
    </Box>
  );
}

type VerificationAcceptProps = {
  onAccept: () => Promise<void>;
};
function VerificationAccept({ onAccept }: VerificationAcceptProps) {
  const { t } = useTranslation();
  const [acceptState, accept] = useAsyncCallback(onAccept);

  const accepting = acceptState.status === AsyncStatus.Loading;
  return (
    <Box direction="Column" gap="400">
      <Text>{t('Organisms.DeviceVerification.click_accept')}</Text>
      <Button
        variant="Primary"
        fill="Solid"
        onClick={accept}
        before={accepting && <Spinner size="100" variant="Primary" fill="Solid" />}
        disabled={accepting}
      >
        <Text size="B400">{t('Organisms.DeviceVerification.accept')}</Text>
      </Button>
    </Box>
  );
}

function CompareEmoji({ sasData }: { sasData: ShowSasCallbacks }) {
  const { t } = useTranslation();
  const [confirmState, confirm] = useAsyncCallback(useCallback(() => sasData.confirm(), [sasData]));

  const confirming =
    confirmState.status === AsyncStatus.Loading || confirmState.status === AsyncStatus.Success;

  return (
    <Box direction="Column" gap="400">
      <Text>{t('Organisms.DeviceVerification.confirm_emoji')}</Text>
      <Box
        className={ContainerColor({ variant: 'SurfaceVariant' })}
        style={{
          borderRadius: config.radii.R400,
          padding: config.space.S500,
        }}
        gap="700"
        wrap="Wrap"
        justifyContent="Center"
      >
        {sasData.sas.emoji?.map(([emoji, name], index) => (
          <Box
            key={`${emoji}${name}${index}`}
            direction="Column"
            gap="100"
            justifyContent="Center"
            alignItems="Center"
          >
            <Text size="H1">{emoji}</Text>
            <Text size="T200">{name}</Text>
          </Box>
        ))}
      </Box>
      <Box direction="Column" gap="200">
        <Button
          variant="Primary"
          fill="Soft"
          onClick={confirm}
          disabled={confirming}
          before={confirming && <Spinner size="100" variant="Primary" />}
        >
          <Text size="B400">{t('Organisms.DeviceVerification.they_match')}</Text>
        </Button>
        <Button
          variant="Primary"
          fill="Soft"
          onClick={() => sasData.mismatch()}
          disabled={confirming}
        >
          <Text size="B400">{t('Organisms.DeviceVerification.do_not_match')}</Text>
        </Button>
      </Box>
    </Box>
  );
}

type SasVerificationProps = {
  verifier: Verifier;
  onCancel: () => void;
};
function SasVerification({ verifier, onCancel }: SasVerificationProps) {
  const { t } = useTranslation();
  const [sasData, setSasData] = useState<ShowSasCallbacks>();

  useVerifierShowSas(verifier, setSasData);
  useVerifierCancel(verifier, onCancel);

  useEffect(() => {
    verifier.verify();
  }, [verifier]);

  if (sasData) {
    return <CompareEmoji sasData={sasData} />;
  }

  return (
    <Box direction="Column" gap="400">
      <WaitingMessage message={t('Organisms.DeviceVerification.starting_emoji')} />
    </Box>
  );
}

type VerificationDoneProps = {
  onExit: () => void;
};
function VerificationDone({ onExit }: VerificationDoneProps) {
  const { t } = useTranslation();
  return (
    <Box direction="Column" gap="400">
      <div>
        <Text>{t('Organisms.DeviceVerification.device_verified')}</Text>
      </div>
      <Button variant="Primary" fill="Solid" onClick={onExit}>
        <Text size="B400">{t('Organisms.DeviceVerification.okay')}</Text>
      </Button>
    </Box>
  );
}

type VerificationCanceledProps = {
  onClose: () => void;
};
function VerificationCanceled({ onClose }: VerificationCanceledProps) {
  const { t } = useTranslation();
  return (
    <Box direction="Column" gap="400">
      <Text>{t('Organisms.DeviceVerification.verification_canceled')}</Text>
      <Button variant="Secondary" fill="Soft" onClick={onClose}>
        <Text size="B400">{t('Organisms.DeviceVerification.close')}</Text>
      </Button>
    </Box>
  );
}

function QrCodeImage({ data }: { data: Uint8ClampedArray }) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    // Byte-mode so the raw verification bytes round-trip (a string value would
    // mangle high bytes via UTF-8).
    QRCode.toCanvas(canvas, [{ data: new Uint8Array(data), mode: 'byte' }], {
      width: 220,
      margin: 2,
      color: { dark: '#000000', light: '#ffffff' },
    }).catch(() => undefined);
  }, [data]);
  return (
    <Box justifyContent="Center">
      <canvas ref={canvasRef} style={{ borderRadius: config.radii.R300 }} />
    </Box>
  );
}

type VerificationReadyProps = {
  request: VerificationRequest;
  onStartSas: () => void;
  onScanned: (bytes: Uint8ClampedArray) => void;
};
function VerificationReady({ request, onStartSas, onScanned }: VerificationReadyProps) {
  const [myQr, setMyQr] = useState<Uint8ClampedArray>();
  const [scanning, setScanning] = useState(false);
  const canShowMine = request.otherPartySupportsMethod(VerificationMethod.ScanQrCode);
  const canScanTheirs = request.otherPartySupportsMethod(VerificationMethod.ShowQrCode);

  useEffect(() => {
    if (!canShowMine) return;
    request
      .generateQRCode()
      .then((bytes) => {
        if (bytes) setMyQr(bytes);
      })
      .catch(() => undefined);
  }, [request, canShowMine]);

  if (scanning) {
    return <QrScanner onScan={onScanned} onCancel={() => setScanning(false)} />;
  }

  return (
    <Box direction="Column" gap="400">
      {myQr && (
        <Box direction="Column" gap="200">
          <Text size="T300">Scan this code with your other device to verify.</Text>
          <QrCodeImage data={myQr} />
        </Box>
      )}
      <Box direction="Column" gap="200">
        {canScanTheirs && (
          <Button variant="Primary" fill="Solid" onClick={() => setScanning(true)}>
            <Text size="B400">Scan their QR code</Text>
          </Button>
        )}
        <Button variant="Secondary" fill="Soft" onClick={onStartSas}>
          <Text size="B400">Verify with emoji instead</Text>
        </Button>
      </Box>
    </Box>
  );
}

type ReciprocateVerificationProps = {
  verifier: Verifier;
  onCancel: () => void;
};
function ReciprocateVerification({ verifier, onCancel }: ReciprocateVerificationProps) {
  const [qrCallbacks, setQrCallbacks] = useState<ShowQrCodeCallbacks>();
  const [confirmState, confirm] = useAsyncCallback(
    useCallback(async () => qrCallbacks?.confirm(), [qrCallbacks]),
  );

  useVerifierShowReciprocateQr(verifier, setQrCallbacks);
  useVerifierCancel(verifier, onCancel);

  const confirming =
    confirmState.status === AsyncStatus.Loading || confirmState.status === AsyncStatus.Success;

  // The showing side gets ShowReciprocateQr callbacks after the other device
  // scans; the scanning side never does (it already called verify()) and just
  // waits for completion.
  if (!qrCallbacks) {
    return (
      <Box direction="Column" gap="400">
        <WaitingMessage message="Verifying…" />
      </Box>
    );
  }

  return (
    <Box direction="Column" gap="400">
      <Text>The other device scanned this code. Confirm it now shows as verified.</Text>
      <Box direction="Column" gap="200">
        <Button variant="Primary" fill="Soft" onClick={confirm} disabled={confirming}>
          <Text size="B400">Confirm</Text>
        </Button>
        <Button
          variant="Primary"
          fill="Soft"
          onClick={() => qrCallbacks.cancel()}
          disabled={confirming}
        >
          <Text size="B400">Cancel</Text>
        </Button>
      </Box>
    </Box>
  );
}

type DeviceVerificationProps = {
  request: VerificationRequest;
  onExit: () => void;
};
export function DeviceVerification({ request, onExit }: DeviceVerificationProps) {
  const phase = useVerificationRequestPhase(request);
  const modalStyle = useModalStyle(480);

  const handleCancel = useCallback(() => {
    if (request.phase !== VerificationPhase.Done && request.phase !== VerificationPhase.Cancelled) {
      request.cancel();
    }
    onExit();
  }, [request, onExit]);

  const handleAccept = useCallback(() => request.accept(), [request]);
  const handleStart = useCallback(async () => {
    await request.startVerification(VerificationMethod.Sas);
  }, [request]);
  const handleScanned = useCallback(
    async (bytes: Uint8ClampedArray) => {
      try {
        const verifier = await request.scanQRCode(bytes);
        await verifier.verify();
      } catch {
        // A bad/mismatched scan cancels the request; the Cancelled phase renders.
      }
    },
    [request],
  );

  return (
    <Overlay open backdrop={<OverlayBackdrop />}>
      <OverlayCenter>
        <FocusTrap
          focusTrapOptions={{
            initialFocus: false,
            clickOutsideDeactivates: false,
            escapeDeactivates: false,
          }}
        >
          <Dialog variant="Surface" style={modalStyle}>
            <Header style={DialogHeaderStyles} variant="Surface" size="500">
              <Box grow="Yes">
                <Text as="h2" size="H4">
                  Device Verification
                </Text>
              </Box>
              <IconButton
                size="300"
                radii="300"
                onClick={handleCancel}
                aria-label="Cancel verification"
              >
                <Icon src={Icons.Cross} />
              </IconButton>
            </Header>
            <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
              {phase === VerificationPhase.Requested &&
                (request.initiatedByMe ? (
                  <VerificationWaitAccept />
                ) : (
                  <VerificationAccept onAccept={handleAccept} />
                ))}
              {phase === VerificationPhase.Ready && (
                <VerificationReady
                  request={request}
                  onStartSas={handleStart}
                  onScanned={handleScanned}
                />
              )}
              {phase === VerificationPhase.Started &&
                (request.verifier ? (
                  request.chosenMethod === VerificationMethod.Reciprocate ? (
                    <ReciprocateVerification verifier={request.verifier} onCancel={handleCancel} />
                  ) : (
                    <SasVerification verifier={request.verifier} onCancel={handleCancel} />
                  )
                ) : (
                  <VerificationUnexpected
                    message="Unexpected Error! Verification is started but verifier is missing."
                    onClose={handleCancel}
                  />
                ))}
              {phase === VerificationPhase.Done && <VerificationDone onExit={onExit} />}
              {phase === VerificationPhase.Cancelled && (
                <VerificationCanceled onClose={handleCancel} />
              )}
            </Box>
          </Dialog>
        </FocusTrap>
      </OverlayCenter>
    </Overlay>
  );
}

export function ReceiveSelfDeviceVerification() {
  const [request, setRequest] = useState<VerificationRequest>();

  useVerificationRequestReceived(setRequest);

  const handleExit = useCallback(() => {
    setRequest(undefined);
  }, []);

  if (!request) return null;

  return <DeviceVerification request={request} onExit={handleExit} />;
}
