/**
 * @file SignWorkItemsModal.tsx - Modal dialog for digitally signing one or more work items, part of the LPNU digital signage feature.
 * The user uploads a certificate file, selects a certificate issuer from a configured list, and enters the certificate password.
 * The signature payload is then submitted to the IWS5 API via {@link signWorkItemList}.
 * Work items that are locked by another user are automatically excluded from the signable set.
 */

import { type ChangeEvent, type FC, useEffect, useRef, useState } from 'react';
import type { WorkItemTableData } from '../../../api/workItem.ts';
import { useReauthModalSuspension } from '../../../hooks/modals/useReauthModalSuspension.ts';
import {
  Alert,
  Button,
  FloatingLabel,
  Form,
  Modal,
  Spinner,
} from 'react-bootstrap';
import { useAuth } from '../../../contexts/AuthContext.tsx';
import { toast } from 'react-toastify';
import { Key } from 'ts-key-enum';
import { SIGNAGE_ACCEPTED_CERTIFICATE_FILE_EXTENSIONS } from '../../../constants.ts';
import { signWorkItemList } from '../../../api/signature.ts';
import { useTranslation } from 'react-i18next';
import { CustomFileInput } from '../../CustomFileInput/CustomFileInput.tsx';

/**
 * Props for the SignWorkItemsModal component.
 */
interface SignWorkItemsModalProps {
  /** Whether the modal is currently visible */
  show: boolean;

  /** Callback invoked when the modal is dismissed */
  onHide: (refresh?: boolean) => void;

  /** One or more work items selected for signage */
  selectedWorkItems: WorkItemTableData | WorkItemTableData[] | undefined;
}

/**
 * Modal for digitally signing one or more work items using the LPNU signage flow.
 *
 * Collects three inputs:
 * - A certificate file (accepted extensions configured via SIGNAGE_ACCEPTED_CERTIFICATE_FILE_EXTENSIONS)
 * - A certificate issuer selected from a dropdown populated by the global app configuration (`LPNU_WORK_ITEM_SIGNAGE_CERTIFICATE_ISSUERS`)
 * - A password for the certificate
 *
 * Only work items that are either unlocked or locked by the current user are eligible for signing.
 * If no eligible items remain, an alert is displayed instead of the form.
 * After a successful API call, any work items that were skipped (e.g., missing documents) are reported via a warning toast.
 *
 * Pressing Enter anywhere except the file picker triggers submission.
 * The modal is suspended when a reauthentication prompt is active via useReauthModalSuspension.
 */
export const SignWorkItemsModal: FC<SignWorkItemsModalProps> = ({
  show,
  onHide,
  selectedWorkItems,
}) => {
  const { username } = useAuth();

  // Derive the subset of selected work items eligible for signing
  // A work item is signable only if it is unlocked (null) or locked by the current user
  const [signableWorkItems, setSignableWorkItems] = useState<
    WorkItemTableData[]
  >([]);
  useEffect(() => {
    if (selectedWorkItems) {
      // Normalize single item to array for uniform handling
      let signable = Array.isArray(selectedWorkItems)
        ? selectedWorkItems
        : [selectedWorkItems];
      // Exclude work items locked by a different user
      signable = signable.filter((workItem) =>
        [null, username].includes(workItem.lockedBy)
      );

      setSignableWorkItems(signable);
    } else {
      setSignableWorkItems([]);
    }
  }, [selectedWorkItems]);

  // Bootstrap form validation state
  // Reset when the modal is hidden so validation feedback does not persist across open/close cycles
  const formRef = useRef<HTMLFormElement | null>(null);
  const [validated, setValidated] = useState(false);
  useEffect(() => {
    if (!show) {
      setValidated(false);
    }
  }, [show]);

  // Reference to the hidden file input used for certificate upload
  const fileControlRef = useRef<HTMLInputElement | null>(null);

  /**
   * Reads the uploaded certificate file as an ArrayBuffer and converts it to a byte array (`number[]`) suitable for the signing API payload.
   *
   * @param file The certificate file selected by the user
   * @returns A promise resolving to the file content as an array of byte values
   */
  const readUploadedCertificate = async (file: File): Promise<number[]> => {
    const reader = new FileReader();

    // Wrap FileReader operations in Promise so that function can be awaited
    return new Promise((resolve, reject) => {
      reader.onload = () => {
        const array = new Uint8Array(reader.result as ArrayBuffer);
        const fileBytes = Array.from(array);

        resolve(fileBytes);
      };

      reader.onerror = () => {
        toast.error(
          t('functions.read_uploaded_certificate.errors.failed_to_read')
        );
        reject();
      };

      reader.readAsArrayBuffer(file);
    });
  };

  // Load available certificate issuers from app runtime configuration on mount
  // The list is fetched asynchronously because the config value is a function (LPNU_WORK_ITEM_SIGNAGE_CERTIFICATE_ISSUERS)
  // The first issuer is pre-selected by default
  const [certIssuers, setCertIssuers] = useState<string[]>([]);
  useEffect(() => {
    const retrieveCertIssuers = async () => {
      const issuers =
        await window.__APP_CONFIG__?.LPNU_WORK_ITEM_SIGNAGE_CERTIFICATE_ISSUERS();
      setCertIssuers(issuers || []);

      // Auto-select the first issuer if available
      if (issuers && issuers[0]) {
        setSelectedCertIssuer(issuers[0]);
      }
    };

    retrieveCertIssuers();
  }, []);

  // Currently selected certificate issuer value bound to the dropdown
  const [selectedCertIssuer, setSelectedCertIssuer] = useState<string>('');
  const updateSelectedCertIssuer = (event: ChangeEvent<HTMLSelectElement>) => {
    setSelectedCertIssuer(event.target.value);
  };

  // Reference to the certificate password input
  const passwordRef = useRef<HTMLInputElement | null>(null);

  /**
   * Validates the uploaded certificate file extension, reads the file bytes, and submits the signing request to the IWS5 API.
   * If the API reports any skipped work items, a warning toast lists them. Otherwise, a success toast is shown and the modal is closed.
   */
  const signWorkItems = async () => {
    const files = Array.from(fileControlRef.current?.files as FileList);
    const file = files[0];
    // Extract file extension for allow-list validation
    const extension = '.' + file.name.split('.').slice(-1).join('');

    // Validate the certificate file extension against the configured allow-list
    const extensionMatch = SIGNAGE_ACCEPTED_CERTIFICATE_FILE_EXTENSIONS.find(
      (ext) => ext === extension
    );
    if (!extensionMatch) {
      toast.error(
        t('functions.sign_work_items.errors.disallowed_extension', {
          file_name: file.name,
        })
      );
      return;
    }

    setProcessing(true);

    // Read certificate bytes and submit signing request to the IWS5 API
    const fileBytes = await readUploadedCertificate(file);
    signWorkItemList(
      signableWorkItems.map((workItem) => Number(workItem.workItemReferenceId)),
      fileBytes,
      passwordRef.current?.value as string,
      selectedCertIssuer
    )
      .then((response) => {
        // The API may return skipped work items (e.g. items without documents)
        // Display them in a warning toast with a longer auto-close delay
        if (response.skippedWorkItems.length) {
          const skippedWorkItems = response.skippedWorkItems
            .map((item) => String(item))
            .join(', ');
          toast.warn(
            `${t('functions.sign_work_items.warnings.missing_documents')}: ` +
              skippedWorkItems,
            {
              autoClose: 1e4,
            }
          );
        } else {
          toast.success(t('functions.sign_work_items.success'));
        }

        onHide(true);
      })
      .catch(() => {
        toast.error(t('functions.sign_work_items.errors.failed_to_sign'));
      })
      .finally(() => {
        setProcessing(false);
      });
  };

  // Ref to the submit button so the Enter key handler can programmatically click it
  const signWorkItemsButton = useRef<HTMLButtonElement | null>(null);
  // Tracks whether an API submission is in-flight (disables button and shows spinner)
  const [processing, setProcessing] = useState(false);

  // Suspend this modal when a reauthentication dialog is active
  const modalSuspensionState = useReauthModalSuspension(show);

  useEffect(() => {
    if (!show || modalSuspensionState === 'suspended') {
      return;
    }

    // Enter key triggers sign action
    // Except when file picker is active element
    const handleKeyDown = (event: KeyboardEvent) => {
      if (
        event.key === Key.Enter &&
        fileControlRef.current !== document.activeElement
      ) {
        event.preventDefault();
        signWorkItemsButton.current?.click();
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [show, modalSuspensionState]);

  const { t } = useTranslation(undefined, {
    keyPrefix: 'components.home.modals.sign_work_items',
  });

  // Guard: render nothing when no work items are selected
  if (!selectedWorkItems) {
    return;
  }

  return (
    <Modal
      centered
      show={modalSuspensionState !== 'hidden'}
      onHide={onHide}
      className={modalSuspensionState === 'suspended' ? 'suspended' : ''}
    >
      <Modal.Header closeButton>
        <Modal.Title>{t('modal.header.title')}</Modal.Title>
      </Modal.Header>
      <Modal.Body>
        <div>
          <p className={'mb-0'}>{t('modal.body.paragraph_text')}</p>
          <small>
            <i>{t('modal.body.small_text')}</i>
          </small>
        </div>
        <hr />
        {signableWorkItems.length ? (
          <Form ref={formRef} noValidate validated={validated}>
            <Form.Group>
              <CustomFileInput
                ref={fileControlRef}
                accept={SIGNAGE_ACCEPTED_CERTIFICATE_FILE_EXTENSIONS.join(',')}
                required
                validated={validated}
              />
              <Form.Control.Feedback type={'invalid'}>
                {t('modal.body.inputs.certificate_file.invalid_text')}
              </Form.Control.Feedback>
            </Form.Group>
            <hr />
            <Form.Group>
              <FloatingLabel
                controlId={'certificate-issuer-select'}
                label={
                  <span>
                    {t('modal.body.inputs.certificate_issuer.label')}{' '}
                    <span className={'text-danger'}>*</span>
                  </span>
                }
              >
                <Form.Select
                  value={selectedCertIssuer}
                  onChange={updateSelectedCertIssuer}
                  disabled={!certIssuers.length}
                  required
                >
                  {certIssuers.length ? (
                    <option value={''} disabled>
                      {t(
                        'modal.body.inputs.certificate_issuer.options.default'
                      )}
                    </option>
                  ) : (
                    <option value={''} disabled>
                      {t('modal.body.inputs.certificate_issuer.options.none')}
                    </option>
                  )}
                  {certIssuers.map((issuer) => (
                    <option key={issuer} value={issuer}>
                      {issuer}
                    </option>
                  ))}
                </Form.Select>
                <Form.Control.Feedback type={'invalid'}>
                  {t('modal.body.inputs.certificate_issuer.invalid_text')}
                </Form.Control.Feedback>
              </FloatingLabel>
            </Form.Group>
            <hr />
            <Form.Group>
              <FloatingLabel
                controlId={'certificate-password-input'}
                label={
                  <span>
                    {t('modal.body.inputs.password.label')}:{' '}
                    <span className={'text-danger'}>*</span>
                  </span>
                }
              >
                <Form.Control
                  ref={passwordRef}
                  type={'password'}
                  placeholder={t('modal.body.inputs.password.label')}
                  required
                />
                <Form.Control.Feedback type={'invalid'}>
                  {t('modal.body.inputs.password.invalid_text')}
                </Form.Control.Feedback>
              </FloatingLabel>
            </Form.Group>
          </Form>
        ) : (
          <Alert variant={'danger'} className={'text-center mb-0'}>
            {t('modal.body.alerts.no_valid_work_items')}
          </Alert>
        )}
      </Modal.Body>
      <Modal.Footer>
        <Button
          variant={'secondary'}
          onClick={() => {
            onHide();
          }}
        >
          {t('modal.footer.buttons.cancel.button_text')}
        </Button>
        <Button
          ref={signWorkItemsButton}
          variant={'primary'}
          onClick={() => {
            if (formRef.current?.checkValidity()) {
              signWorkItems();
            }
            setValidated(true);
          }}
          disabled={
            !certIssuers.length || !signableWorkItems.length || processing
          }
        >
          {processing ? (
            <div className={'d-flex justify-content-center align-items-center'}>
              <Spinner
                as={'span'}
                size={'sm'}
                animation={'border'}
                role={'status'}
                aria-hidden={'true'}
                className={'me-2'}
              />
              {t('modal.footer.buttons.sign_work_items.signing_text')}...
            </div>
          ) : (
            <>{t('modal.footer.buttons.sign_work_items.default')}</>
          )}
        </Button>
      </Modal.Footer>
    </Modal>
  );
};
