import { Skeleton } from '@material-ui/lab';
import AlertTitle from '@mui/material/AlertTitle';
import Box from '@mui/material/Box';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { keyBy, pick } from 'es-toolkit/compat';
import React, { useCallback, useMemo } from 'react';
import { toast } from 'react-toastify';
import styled from 'styled-components';
import * as yup from 'yup';

import { FORM_TYPES } from '@tamanu/constants/forms';
import {
  Alert,
  ConditionalTooltip,
  ConfirmCancelRow,
  DateTimeField,
  Field,
  Form,
  TextField,
  TranslatedReferenceData,
  TranslatedText,
  useApi,
  useDateTime,
  useTranslation,
} from '@tamanu/ui-components';
import { useLabTestResultsQuery } from '../../../api/queries/useLabTestResultsQuery';
import { SuggesterSelectField } from '../../../components/Field';
import { FormModal } from '../../../components/FormModal';
import { TableFormFields } from '../../../components/Table';
import { BodyText, Heading4, SmallBodyText } from '../../../components/Typography';
import { Colors } from '../../../constants/styles';
import { useAuth } from '../../../contexts/Auth';
import { useLabRequest } from '../../../contexts/LabRequest';
import { AccessorField, LabResultAccessorField } from './AccessorField';

const TableContainer = styled.div`
  overflow-y: auto;
  max-height: 48vh;
  margin: 0px 30px;
`;

const StyledModal = styled(FormModal)`
  .MuiDialogActions-root {
    display: none;
  }
`;

const StyledTableFormFields = styled(TableFormFields)`
  thead tr th {
    text-align: left;
    background: ${Colors.white};
    font-size: 14px;
    font-weight: 500;
    color: ${Colors.midText};
    padding: 10px;
  }

  tbody tr td {
    font-size: 14px;
    vertical-align: top;
    padding: 8px;
    overflow: visible;
  }

  tbody tr td .MuiFormHelperText-root {
    white-space: nowrap;
    overflow: visible;
  }

  thead tr th:first-child,
  thead tr th:nth-child(3),
  tbody tr td:first-child,
  tbody tr td:nth-child(3) {
    padding-top: 20px;
    vertical-align: top;
  }

  thead tr th:first-child,
  tbody tr td:first-child {
    padding-left: 20px;
  }
`;

const StyledConfirmCancelRow = styled(ConfirmCancelRow)`
  padding-right: 30px;
  padding-top: 18px;
  margin-top: 20px;
  border-top: 1px solid ${Colors.outline};
`;

const SkeletonContainer = styled(Box)`
  padding: 0 30px;
`;

const SkeletonSection = styled(Box)`
  margin-bottom: 20px;
`;

const SkeletonTitle = styled(Skeleton)`
  font-size: 20px;
  margin-bottom: 4px;
`;

const SkeletonSubtitle = styled(Skeleton)`
  font-size: 12px;
`;

const SkeletonRect = styled(Skeleton)`
  border-radius: 4px;
`;

const ErrorContainer = styled(Box)`
  padding: 8px 30px 25px 30px;
`;

const FormHeaderSection = styled(Box)`
  margin: 0 30px;
  padding-bottom: 20px;
`;

const InterpretationFieldSection = styled(Box)`
  margin: 20px 30px;
`;

const StyledHeading4 = styled(Heading4)`
  margin-top: 8px;
  margin-bottom: 10px;
`;

const StyledSmallBodyText = styled(SmallBodyText)`
  margin-bottom: 12px;
  color: ${Colors.midText};
`;

const LAB_TEST_PROPERTIES = {
  COMPLETED_DATE: 'completedDate',
  ID: 'id',
  LAB_TEST_METHOD_ID: 'labTestMethodId',
  RESULT: 'result',
  SECONDARY_RESULT: 'secondaryResult',
  VERIFICATION: 'verification',
};

const AUTOFILL_FIELD_NAMES = [
  LAB_TEST_PROPERTIES.COMPLETED_DATE,
  LAB_TEST_PROPERTIES.LAB_TEST_METHOD_ID,
];

const getValidationSchema = getTranslation =>
  yup.object().shape({
    labTests: yup.lazy(labTestsObj => {
      if (!labTestsObj) return yup.object();
      const shape = {};
      Object.keys(labTestsObj).forEach(testId => {
        shape[testId] = yup.object().shape({
          result: yup.string().when('secondaryResult', {
            is: secondaryResult => secondaryResult && secondaryResult.trim() !== '',
            then: yup
              .string()
              .required(
                getTranslation(
                  'lab.validation.resultRequiredWhenSecondaryResultEntered',
                  'Result required when secondary result entered',
                ),
              ),
            otherwise: yup.string().nullable(),
          }),
          secondaryResult: yup.string().nullable(),
        });
      });
      return yup.object().shape(shape);
    }),
    resultsInterpretation: yup.string().nullable(),
  });

const getColumns = ({ labTestResults, onChangeResult, areLabTestResultsReadOnly }) => {
  const { count, data } = labTestResults || {};
  // Generate tab index for vertical tabbing through the table
  const tabIndex = (col, row) => count * col + row + 1;
  const showSecondaryResultColumn = data?.some(row => row.labTestType?.supportsSecondaryResults);
  return [
    {
      key: 'labTestType',
      title: <TranslatedText stringId="lab.test.label" fallback="Test" />,
      width: '120px',
      accessor: row => (
        <TranslatedReferenceData
          fallback={row.labTestType.name}
          value={row.labTestType.id}
          category="labTestType"
        />
      ),
    },
    {
      key: LAB_TEST_PROPERTIES.RESULT,
      title: <TranslatedText stringId="lab.results.table.column.result" fallback="Result" />,
      accessor: (row, i) => {
        const { resultType, options, id: labTestTypeId } = row.labTestType;
        return (
          <LabResultAccessorField
            resultType={resultType}
            options={options}
            disabled={areLabTestResultsReadOnly}
            name={LAB_TEST_PROPERTIES.RESULT}
            onChange={e => onChangeResult(e.target.value, row.id)}
            id={row.id}
            labTestTypeId={labTestTypeId}
            tabIndex={tabIndex(0, i)}
            data-testid="labresultaccessorfield-1r9h"
          />
        );
      },
    },
    {
      key: 'unit',
      title: <TranslatedText stringId="lab.results.table.column.unit" fallback="Units" />,
      width: '80px',
      accessor: row => (
        <BodyText color="textTertiary" data-testid="bodytext-uq3u">
          {row.labTestType.unit || 'N/A'}
        </BodyText>
      ),
    },
    ...(showSecondaryResultColumn
      ? [
          {
            key: LAB_TEST_PROPERTIES.SECONDARY_RESULT,
            title: (
              <TranslatedText
                stringId="lab.results.table.column.secondaryResult"
                fallback="Secondary result"
              />
            ),
            accessor: (row, i) => {
              const { supportsSecondaryResults } = row.labTestType;
              return (
                <ConditionalTooltip
                  visible={!supportsSecondaryResults}
                  maxWidth="140px"
                  title={
                    <TranslatedText
                      stringId="lab.results.tooltip.secondaryResultNotSupported"
                      fallback="Secondary result is not supported for this test."
                    />
                  }
                >
                  <AccessorField
                    id={row.id}
                    component={TextField}
                    name={LAB_TEST_PROPERTIES.SECONDARY_RESULT}
                    disabled={areLabTestResultsReadOnly || !supportsSecondaryResults}
                    tabIndex={tabIndex(1, i)}
                    data-testid="accessorfield-secondary-result"
                  />
                </ConditionalTooltip>
              );
            },
          },
        ]
      : []),
    {
      key: LAB_TEST_PROPERTIES.LAB_TEST_METHOD_ID,
      title: <TranslatedText stringId="lab.results.table.column.method" fallback="Method" />,
      width: '160px',
      accessor: (row, i) => (
        <AccessorField
          id={row.id}
          endpoint="labTestMethod"
          name={LAB_TEST_PROPERTIES.LAB_TEST_METHOD_ID}
          component={SuggesterSelectField}
          tabIndex={tabIndex(2, i)}
          clearValue={null}
          data-testid="accessorfield-ik1h"
        />
      ),
    },
    {
      key: LAB_TEST_PROPERTIES.VERIFICATION,
      title: (
        <TranslatedText stringId="lab.results.table.column.verification" fallback="Verification" />
      ),
      accessor: (row, i) => (
        <AccessorField
          id={row.id}
          component={TextField}
          name={LAB_TEST_PROPERTIES.VERIFICATION}
          tabIndex={tabIndex(3, i)}
          data-testid="accessorfield-jhrr"
        />
      ),
    },
    {
      key: LAB_TEST_PROPERTIES.COMPLETED_DATE,
      title: (
        <TranslatedText stringId="lab.results.table.column.completedDate" fallback="Completed" />
      ),
      width: '260px',
      accessor: (row, i) => (
        <AccessorField
          id={row.id}
          component={DateTimeField}
          name={LAB_TEST_PROPERTIES.COMPLETED_DATE}
          tabIndex={tabIndex(4, i)}
          data-testid="accessorfield-k5ef"
        />
      ),
    },
  ];
};

const ResultsFormSkeleton = () => (
  <>
    <SkeletonContainer data-testid="box-40fc">
      <SkeletonSection data-testid="box-ccfc">
        <div>
          <SkeletonTitle variant="text" width={124} data-testid="skeleton-um2y" />
          <SkeletonSubtitle variant="text" width={270} data-testid="skeleton-llaz" />
        </div>
      </SkeletonSection>
      <SkeletonRect variant="rect" height={254} data-testid="skeleton-dl86" />
    </SkeletonContainer>
  </>
);

const ResultsFormError = ({ error }) => (
  <ErrorContainer data-testid="box-ta1e">
    <Alert severity="error" data-testid="alert-m6oy">
      <AlertTitle data-testid="alerttitle-kpbw">Error</AlertTitle>
      <b>Failed to load result with error:</b> {error.message}
    </Alert>
  </ErrorContainer>
);

const ResultsForm = ({
  labTestResults,
  isLoading,
  isError,
  error,
  values,
  setFieldValue,
  areLabTestResultsReadOnly,
}) => {
  const { getCurrentDateTime } = useDateTime();
  /**
   * On entering lab result field for a test some other fields are auto-filled optimistically
   * In the case of labTestMethod this occurs in the case that:
   * 1. The user has only entered a single unique value for this field across other rows
   * 2. The user has not already entered a value for this field in the current row
   */
  const onChangeResult = useCallback(
    (value, labTestId) => {
      if (!value) return;
      const rowValues = values.labTests?.[labTestId];

      AUTOFILL_FIELD_NAMES.forEach(name => {
        if (rowValues?.[name]) return;

        const otherRowsValues = Object.entries(values.labTests || {})
          .filter(([id, row]) => id !== labTestId && row[name])
          .map(([, row]) => row[name]);

        const uniqueValues = [...new Set(otherRowsValues)];
        const fieldName = `labTests.${labTestId}.${name}`;
        if (name === LAB_TEST_PROPERTIES.COMPLETED_DATE) {
          setFieldValue(fieldName, getCurrentDateTime());
        } else if (uniqueValues.length === 1) {
          setFieldValue(fieldName, uniqueValues[0]);
        }
      });
    },
    [values.labTests, setFieldValue, getCurrentDateTime],
  );

  const columns = useMemo(
    () => getColumns({ labTestResults, onChangeResult, areLabTestResultsReadOnly }),
    [labTestResults, onChangeResult, areLabTestResultsReadOnly],
  );

  if (isLoading) return <ResultsFormSkeleton data-testid="resultsformskeleton-ibqy" />;
  if (isError) return <ResultsFormError error={error} data-testid="resultsformerror-se9z" />;

  return (
    <Box data-testid="box-miwv">
      <FormHeaderSection data-testid="box-jcm4">
        <div>
          <StyledHeading4 data-testid="heading4-5541">
            <TranslatedText
              stringId="patient.lab.modal.enterResults.heading"
              fallback="Enter test results"
            />
          </StyledHeading4>
          <StyledSmallBodyText data-testid="smallbodytext-4j32">
            <TranslatedText
              stringId="patient.lab.modal.enterResults.subHeading"
              fallback="Please record test results, other test result details and any relevant notes."
            />
          </StyledSmallBodyText>
        </div>
      </FormHeaderSection>
      <TableContainer data-testid="tablecontainer-dyto">
        <StyledTableFormFields
          columns={columns}
          data={labTestResults?.data}
          data-testid="styledtableformfields-5s0u"
        />
      </TableContainer>
      <InterpretationFieldSection>
        <Field
          component={TextField}
          multiline
          disabled={areLabTestResultsReadOnly}
          rows={6}
          name="resultsInterpretation"
          label="Results Interpretation"
          data-testid="field-resultsinterpretation"
        />
      </InterpretationFieldSection>
    </Box>
  );
};

export const LabTestResultsModal = ({ labRequest, refreshLabTestTable, onClose, open }) => {
  const api = useApi();
  const queryClient = useQueryClient();
  const { ability } = useAuth();
  const { loadLabRequest } = useLabRequest();
  const { getTranslation } = useTranslation();
  const canWriteLabTestResult = ability?.can('write', 'LabTestResult');
  const areLabTestResultsReadOnly = !canWriteLabTestResult;

  const {
    data: labTestResults = { data: [], count: 0 },
    isLoading,
    error,
    isError,
  } = useLabTestResultsQuery(labRequest.id);
  const { displayId } = labRequest;

  const { mutate: updateTests, isLoading: isSavingTests } = useMutation(
    payload => api.put(`labRequest/${labRequest.id}/tests`, payload),
    {
      onSuccess: labTestRes => {
        toast.success(
          <TranslatedText
            stringId="patient.lab.modal.notification.testsUpdatedSuccess"
            fallback={`Successfully updated ${labTestRes.length} tests for request ${displayId}`}
            replacements={{ length: labTestRes.length, displayId }}
          />,
        );
        queryClient.invalidateQueries(['labTestResults', labRequest.id]);
        loadLabRequest(labRequest.id);
        refreshLabTestTable();
        onClose();
      },
      onError: err => {
        toast.error(
          <TranslatedText
            stringId="patient.lab.modal.notification.testsUpdatedFailed"
            fallback={`Failed to update tests for request ${displayId}: ${err.message}`}
            replacements={{ message: err.message, displayId }}
          />,
        );
      },
    },
  );

  // Select editable values to prefill the form on edit
  const initialData = useMemo(
    () => ({
      labTests: keyBy(
        labTestResults?.data.map(data => pick(data, Object.values(LAB_TEST_PROPERTIES))),
        LAB_TEST_PROPERTIES.ID,
      ),
      resultsInterpretation: labRequest.resultsInterpretation,
    }),
    [labTestResults, labRequest.resultsInterpretation],
  );

  return (
    <StyledModal
      width="lg"
      title={
        <TranslatedText
          stringId="patient.lab.modal.enterResults.title"
          fallback="Enter results | Request ID :testId"
          replacements={{ testId: displayId }}
        />
      }
      open={open}
      onClose={onClose}
      overrideContentPadding
      data-testid="styledmodal-ou57"
    >
      <Form
        initialValues={initialData}
        formType={labTestResults ? FORM_TYPES.EDIT : FORM_TYPES.CREATE}
        enableReinitialize
        validationSchema={getValidationSchema(getTranslation)}
        onSubmit={updateTests}
        render={({ submitForm, isSubmitting, dirty, ...props }) => {
          const confirmDisabled = isLoading || isError || isSavingTests || isSubmitting || !dirty;
          return (
            <>
              <ResultsForm
                labTestResults={labTestResults}
                onClose={onClose}
                isLoading={isLoading}
                areLabTestResultsReadOnly={areLabTestResultsReadOnly}
                isError={isError}
                error={error}
                {...props}
                data-testid="resultsform-xeh7"
              />
              <StyledConfirmCancelRow
                onCancel={onClose}
                onConfirm={submitForm}
                confirmDisabled={confirmDisabled}
                data-testid="styledconfirmcancelrow-5osn"
              />
            </>
          );
        }}
        data-testid="form-z03z"
      />
    </StyledModal>
  );
};
