import { Box, styled, Tooltip } from '@mui/material';
import { ReactElement, ReactNode, useMemo } from 'react';

import INPUT_TYPES from '../../lib/consts/INPUT_TYPES';

import FlexBox from '../FlexBox';
import InputWithRef from '../InputWithRef';
import OutlinedInputWithLabel from '../OutlinedInputWithLabel';
import { ExpandablePanel } from '../Panels';
import SelectWithLabel from '../SelectWithLabel';
import SwitchWithLabel from '../SwitchWithLabel';
import {
  buildIPAddressTestBatch,
  buildNumberTestBatch,
  buildPeacefulStringTestBatch,
  testNotBlank,
} from '../../lib/test_input';
import { BodyText } from '../Text';

const CHECKED_STATES: Array<string | undefined> = ['1', 'on'];

const INPUT_ID_SEPARATOR = '-';

const getStringParamInputTestBatch = <M extends MapToInputTestID>({
  formUtils: { buildFinishInputTestBatchFunction, setMessage },
  id,
  label,
}: {
  formUtils: FormUtils<M>;
  id: string;
  label: string;
}) => {
  const onFinishBatch = buildFinishInputTestBatchFunction(id);

  const onSuccess = () => {
    setMessage(id);
  };

  return label.toLowerCase() === 'ip'
    ? buildIPAddressTestBatch(
        label,
        onSuccess,
        { onFinishBatch },
        (message) => {
          setMessage(id, { children: message });
        },
      )
    : {
        defaults: {
          onSuccess,
        },
        onFinishBatch,
        tests: [{ test: testNotBlank }],
      };
};

const buildNumberParamInput = <M extends MapToInputTestID>(
  args: FenceParameterInputBuilderParameters<M>,
): ReactElement => {
  const { formUtils, id, isRequired, label = '', name = id, value } = args;

  const {
    buildFinishInputTestBatchFunction,
    buildInputFirstRenderFunction,
    setMessage,
  } = formUtils;

  return (
    <InputWithRef
      key={`${id}-wrapper`}
      input={
        <OutlinedInputWithLabel
          id={id}
          label={label}
          name={name}
          value={value}
        />
      }
      inputTestBatch={buildNumberTestBatch(
        label,
        () => {
          setMessage(id);
        },
        { onFinishBatch: buildFinishInputTestBatchFunction(id) },
        (message) => {
          setMessage(id, { children: message });
        },
      )}
      onFirstRender={buildInputFirstRenderFunction(id)}
      required={isRequired}
      valueType="number"
    />
  );
};

const MAP_TO_INPUT_BUILDER: MapToInputBuilder<Record<string, string>> = {
  boolean: (args) => {
    const { id, isChecked = false, label, name = id } = args;

    return (
      <InputWithRef
        key={`${id}-wrapper`}
        input={
          <SwitchWithLabel
            checked={isChecked}
            id={id}
            label={label}
            name={name}
          />
        }
        valueType="boolean"
      />
    );
  },
  integer: buildNumberParamInput,
  second: buildNumberParamInput,
  select: (args) => {
    const {
      formUtils,
      id,
      isRequired,
      label,
      name = id,
      selectOptions = [],
      value = '',
    } = args;

    const {
      buildFinishInputTestBatchFunction,
      buildInputFirstRenderFunction,
      setMessage,
    } = formUtils;

    return (
      <InputWithRef
        key={`${id}-wrapper`}
        input={
          <SelectWithLabel
            id={id}
            label={label}
            name={name}
            selectItems={selectOptions}
            value={value}
          />
        }
        inputTestBatch={{
          defaults: {
            onSuccess: () => {
              setMessage(id);
            },
          },
          onFinishBatch: buildFinishInputTestBatchFunction(id),
          tests: [{ test: testNotBlank }],
        }}
        onFirstRender={buildInputFirstRenderFunction(id)}
        required={isRequired}
      />
    );
  },
  string: (args) => {
    const {
      formUtils,
      id,
      isRequired,
      isSensitive = false,
      label = '',
      name = id,
      value,
    } = args;

    const { buildInputFirstRenderFunction } = formUtils;

    let inputType;

    if (isSensitive) {
      inputType = INPUT_TYPES.password;
    }

    return (
      <InputWithRef
        key={`${id}-wrapper`}
        input={
          <OutlinedInputWithLabel
            id={id}
            label={label}
            name={name}
            type={inputType}
            value={value}
          />
        }
        inputTestBatch={getStringParamInputTestBatch({ formUtils, id, label })}
        onFirstRender={buildInputFirstRenderFunction(id)}
        required={isRequired}
      />
    );
  },
};

const combineIds = (...pieces: string[]) => pieces.join(INPUT_ID_SEPARATOR);

const FenceInputWrapper = styled(FlexBox)({
  margin: '.4em 0',
});

const CommonFenceInputGroup = <M extends Record<string, string>>({
  fenceId,
  fenceParameterTooltipProps,
  fenceTemplate,
  formUtils,
  previousFenceName,
  previousFenceParameters,
}: CommonFenceInputGroupProps<M>): ReactElement => {
  const {
    buildFinishInputTestBatchFunction,
    buildInputFirstRenderFunction,
    setMessage,
  } = formUtils;

  const fenceParameterElements = useMemo(() => {
    let result: ReactNode;

    if (fenceTemplate && fenceId) {
      const { parameters: fenceParameters } = fenceTemplate[fenceId];

      let mapToPreviousFenceParameterValues: FenceParameters = {};

      if (previousFenceParameters) {
        mapToPreviousFenceParameterValues = Object.entries(
          previousFenceParameters,
        ).reduce<FenceParameters>((previous, [parameterId, parameterValue]) => {
          const newKey = combineIds(fenceId, parameterId);

          previous[newKey] = parameterValue;

          return previous;
        }, {});
      }

      const { optional: optionalInputs, required: requiredInputs } =
        Object.entries(fenceParameters)
          .sort(([a], [b]) => (a > b ? 1 : -1))
          .reduce<{
            optional: ReactElement[];
            required: ReactElement[];
          }>(
            (
              previous,
              [
                parameterId,
                {
                  content_type: parameterType,
                  default: parameterDefault,
                  deprecated: rawParameterDeprecated,
                  description: parameterDescription,
                  options: parameterSelectOptions,
                  required: rawParameterRequired,
                },
              ],
            ) => {
              const isParameterDeprecated =
                String(rawParameterDeprecated) === '1';

              if (isParameterDeprecated) return previous;

              const { optional, required } = previous;
              const buildInput =
                MAP_TO_INPUT_BUILDER[parameterType] ??
                MAP_TO_INPUT_BUILDER.string;
              const fenceJoinParameterId = combineIds(fenceId, parameterId);

              const initialValue =
                mapToPreviousFenceParameterValues[fenceJoinParameterId] ??
                parameterDefault;
              const isParameterRequired = String(rawParameterRequired) === '1';
              const isParameterSensitive = /passw/i.test(parameterId);

              const parameterInput = buildInput({
                formUtils,
                id: fenceJoinParameterId,
                isChecked: CHECKED_STATES.includes(initialValue),
                isRequired: isParameterRequired,
                isSensitive: isParameterSensitive,
                label: parameterId,
                selectOptions: parameterSelectOptions,
                value: initialValue,
              });
              const parameterInputWithTooltip = (
                <Tooltip
                  componentsProps={{
                    tooltip: {
                      sx: {
                        maxWidth: { md: '62.6em' },
                      },
                    },
                  }}
                  disableInteractive
                  key={`${fenceJoinParameterId}-tooltip`}
                  placement="top-start"
                  title={<BodyText>{parameterDescription}</BodyText>}
                  {...fenceParameterTooltipProps}
                >
                  <Box>{parameterInput}</Box>
                </Tooltip>
              );

              if (isParameterRequired) {
                required.push(parameterInputWithTooltip);
              } else {
                optional.push(parameterInputWithTooltip);
              }

              return previous;
            },
            {
              optional: [],
              required: [],
            },
          );

      const inputIdFenceName = combineIds(fenceId, 'name');
      const inputLabelFenceName = 'Fence device name';

      result = (
        <FlexBox
          sx={{
            '& > div:first-child': { marginTop: 0 },
            '& > div': { marginBottom: 0 },
          }}
        >
          <ExpandablePanel expandInitially header="Required parameters">
            <FenceInputWrapper>
              <InputWithRef
                key={`${inputIdFenceName}-wrapper`}
                input={
                  <OutlinedInputWithLabel
                    id={inputIdFenceName}
                    label={inputLabelFenceName}
                    name={inputIdFenceName}
                    value={previousFenceName}
                  />
                }
                inputTestBatch={buildPeacefulStringTestBatch(
                  inputLabelFenceName,
                  () => {
                    setMessage(inputIdFenceName);
                  },
                  {
                    onFinishBatch:
                      buildFinishInputTestBatchFunction(inputIdFenceName),
                  },
                  (message) => {
                    setMessage(inputIdFenceName, { children: message });
                  },
                )}
                onFirstRender={buildInputFirstRenderFunction(inputIdFenceName)}
                required
              />
              {requiredInputs}
            </FenceInputWrapper>
          </ExpandablePanel>
          <ExpandablePanel header="Optional parameters">
            <FenceInputWrapper>{optionalInputs}</FenceInputWrapper>
          </ExpandablePanel>
        </FlexBox>
      );
    }

    return result;
  }, [
    buildFinishInputTestBatchFunction,
    buildInputFirstRenderFunction,
    fenceId,
    fenceParameterTooltipProps,
    fenceTemplate,
    formUtils,
    previousFenceName,
    previousFenceParameters,
    setMessage,
  ]);

  return <>{fenceParameterElements}</>;
};

export { INPUT_ID_SEPARATOR };

export default CommonFenceInputGroup;
