import {
  Box as MUIBox,
  BoxProps as MUIBoxProps,
  iconButtonClasses as muiIconButtonClasses,
  useMediaQuery,
  useTheme,
} from '@mui/material';
import {
  Add as MUIAddIcon,
  Check as MUICheckIcon,
  DragHandle as MUIDragHandleIcon,
} from '@mui/icons-material';
import {
  DataGrid as MUIDataGrid,
  DataGridProps as MUIDataGridProps,
  gridClasses as muiGridClasses,
} from '@mui/x-data-grid';
import { FC, useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';

import API_BASE_URL from '../lib/consts/API_BASE_URL';
import { BLUE, GREY } from '../lib/consts/DEFAULT_THEME';

import BriefNetworkInterface from './BriefNetworkInterface';
import Decorator from './Decorator';
import DropArea from './DropArea';
import OutlinedInputWithLabel from './OutlinedInputWithLabel';
import { InnerPanel, InnerPanelHeader } from './Panels';
import periodicFetch from '../lib/fetchers/periodicFetch';
import SelectWithLabel from './SelectWithLabel';
import Spinner from './Spinner';
import sumstring from '../lib/sumstring';
import { BodyText, DataGridCellText } from './Text';
import IconButton from './IconButton';

type NetworkInput = {
  inputUUID: string;
  interfaces: (NetworkInterfaceOverviewMetadata | undefined)[];
  ipAddress: string;
  name: string;
  subnetMask: string;
  type: string;
};

type NetworkInterfaceInputMap = Record<
  string,
  {
    isApplied?: boolean;
  }
>;

const MOCK_NICS: NetworkInterfaceOverviewMetadata[] = [
  {
    networkInterfaceUUID: 'fe299134-c8fe-47bd-ab7a-3aa95eada1f6',
    networkInterfaceMACAddress: '52:54:00:d2:31:36',
    networkInterfaceName: 'ens10',
    networkInterfaceState: 'up',
    networkInterfaceSpeed: 10000,
    networkInterfaceOrder: 1,
  },
  {
    networkInterfaceUUID: 'a652bfd5-61ac-4495-9881-185be8a2ac74',
    networkInterfaceMACAddress: '52:54:00:d4:4d:b5',
    networkInterfaceName: 'ens11',
    networkInterfaceState: 'up',
    networkInterfaceSpeed: 10000,
    networkInterfaceOrder: 2,
  },
  {
    networkInterfaceUUID: 'b8089b40-0969-49c3-ad65-2470ddb420ef',
    networkInterfaceMACAddress: '52:54:00:ba:f5:a3',
    networkInterfaceName: 'ens3',
    networkInterfaceState: 'up',
    networkInterfaceSpeed: 10000,
    networkInterfaceOrder: 3,
  },
  {
    networkInterfaceUUID: '42a17465-31b1-4e47-9a91-f803f22ffcc1',
    networkInterfaceMACAddress: '52:54:00:ae:31:70',
    networkInterfaceName: 'ens9',
    networkInterfaceState: 'up',
    networkInterfaceSpeed: 10000,
    networkInterfaceOrder: 4,
  },
];

const NETWORK_TYPES: Record<string, string> = {
  bcn: 'Back-Channel Network',
  ifn: 'Internet-Facing Network',
};

const REQUIRED_NETWORKS = [
  {
    inputUUID: '30dd2ac5-8024-4a7e-83a1-6a3df7218972',
    interfaces: [],
    ipAddress: '10.200.1.1',
    name: `${NETWORK_TYPES.bcn} 01`,
    subnetMask: '255.255.0.0',
    type: 'bcn',
  },
  {
    inputUUID: 'e7ef3af5-5602-440c-87f8-69c242e3d7f3',
    interfaces: [],
    ipAddress: '10.201.1.1',
    name: `${NETWORK_TYPES.ifn} 01`,
    subnetMask: '255.255.0.0',
    type: 'ifn',
  },
];

const MAX_INTERFACES_PER_NETWORK = 2;
const NETWORK_INTERFACE_TEMPLATE = Array.from(
  { length: MAX_INTERFACES_PER_NETWORK },
  (unused, index) => index + 1,
);

const createNetworkInterfaceTableColumns = (
  handleDragMouseDown: (
    row: NetworkInterfaceOverviewMetadata,
    ...eventArgs: Parameters<Exclude<MUIBoxProps['onMouseDown'], undefined>>
  ) => void,
  networkInterfaceInputMap: NetworkInterfaceInputMap,
): MUIDataGridProps['columns'] => [
  {
    align: 'center',
    field: '',
    renderCell: ({ row }) => {
      const { isApplied } =
        networkInterfaceInputMap[row.networkInterfaceUUID] ?? false;

      let cursor = 'grab';
      let handleMouseDown: MUIBoxProps['onMouseDown'] = (...eventArgs) => {
        handleDragMouseDown(row, ...eventArgs);
      };
      let icon = <MUIDragHandleIcon />;

      if (isApplied) {
        cursor = 'auto';
        handleMouseDown = undefined;
        icon = <MUICheckIcon sx={{ color: BLUE }} />;
      }

      return (
        <MUIBox
          onMouseDown={handleMouseDown}
          sx={{
            alignItems: 'center',
            display: 'flex',
            flexDirection: 'row',

            '&:hover': { cursor },
          }}
        >
          {icon}
        </MUIBox>
      );
    },
    sortable: false,
    width: 1,
  },
  {
    field: 'networkInterfaceName',
    flex: 1,
    headerName: 'Name',
    renderCell: ({ row: { networkInterfaceState } = {}, value }) => (
      <MUIBox
        sx={{
          display: 'flex',
          flexDirection: 'row',
          '& > :not(:first-child)': { marginLeft: '.5em' },
        }}
      >
        <Decorator
          colour={networkInterfaceState === 'up' ? 'ok' : 'off'}
          sx={{ height: 'auto' }}
        />
        <DataGridCellText text={value} />
      </MUIBox>
    ),
    sortComparator: (v1, v2) => sumstring(v1) - sumstring(v2),
  },
  {
    field: 'networkInterfaceMACAddress',
    flex: 1,
    headerName: 'MAC',
    renderCell: ({ value }) => <DataGridCellText monospaced text={value} />,
  },
  {
    field: 'networkInterfaceState',
    flex: 1,
    headerName: 'State',
    renderCell: ({ value }) => {
      const state = String(value);

      return (
        <DataGridCellText
          text={`${state.charAt(0).toUpperCase()}${state.substring(1)}`}
        />
      );
    },
  },
  {
    field: 'networkInterfaceSpeed',
    flex: 1,
    headerName: 'Speed',
    renderCell: ({ value }) => (
      <DataGridCellText text={`${parseFloat(value).toLocaleString()} Mbps`} />
    ),
  },
  {
    field: 'networkInterfaceOrder',
    flex: 1,
    headerName: 'Order',
  },
];

const NetworkInitForm: FC = () => {
  const theme = useTheme();
  const breakpointMedium = useMediaQuery(theme.breakpoints.up('md'));
  const breakpointLarge = useMediaQuery(theme.breakpoints.up('lg'));

  const [dragMousePosition, setDragMousePosition] = useState<{
    x: number;
    y: number;
  }>({ x: 0, y: 0 });
  const [networkInterfaceInputMap, setNetworkInterfaceInputMap] =
    useState<NetworkInterfaceInputMap>({});
  const [networkInputs, setNetworkInputs] =
    useState<NetworkInput[]>(REQUIRED_NETWORKS);
  const [networkInterfaceHeld, setNetworkInterfaceHeld] = useState<
    NetworkInterfaceOverviewMetadata | undefined
  >();

  const optionalNetworkInputsLength = networkInputs.length - 2;

  const { data: networkInterfaces = MOCK_NICS, isLoading } = periodicFetch<
    NetworkInterfaceOverviewMetadata[]
  >(`${API_BASE_URL}/network-interface`, {
    refreshInterval: 2000,
    onSuccess: (data) => {
      if (data instanceof Array) {
        const map = data.reduce<NetworkInterfaceInputMap>(
          (reduceContainer, { networkInterfaceUUID }) => {
            reduceContainer[networkInterfaceUUID] =
              networkInterfaceInputMap[networkInterfaceUUID] ?? {};

            return reduceContainer;
          },
          {},
        );

        setNetworkInterfaceInputMap(map);
      }
    },
  });

  const clearNetworkInterfaceHeld = () => {
    setNetworkInterfaceHeld(undefined);
  };

  const createNetwork = () => {
    networkInputs.unshift({
      inputUUID: uuidv4(),
      interfaces: [],
      ipAddress: '',
      name: '',
      subnetMask: '',
      type: '',
    });

    setNetworkInputs([...networkInputs]);
  };

  const getNetworkTypeCount = (targetType: string, lastIndex = 0) => {
    let count = 0;

    for (let index = networkInputs.length - 1; index >= lastIndex; index -= 1) {
      if (networkInputs[index].type === targetType) {
        count += 1;
      }
    }

    return count;
  };

  let createDropMouseUpHandler: (
    interfaces: (NetworkInterfaceOverviewMetadata | undefined)[],
    interfaceIndex: number,
  ) => MUIBoxProps['onMouseUp'];
  let dragAreaDraggingSx: MUIBoxProps['sx'] = {};
  let floatingNetworkInterface: JSX.Element = <></>;
  let handleDragAreaMouseLeave: MUIBoxProps['onMouseLeave'];
  let handleDragAreaMouseMove: MUIBoxProps['onMouseMove'];
  let handleDragAreaMouseUp: MUIBoxProps['onMouseUp'];

  if (networkInterfaceHeld) {
    const { networkInterfaceUUID } = networkInterfaceHeld;

    createDropMouseUpHandler =
      (
        interfaces: (NetworkInterfaceOverviewMetadata | undefined)[],
        interfaceIndex: number,
      ) =>
      () => {
        const { networkInterfaceUUID: previousNetworkInterfaceUUID } =
          interfaces[interfaceIndex] ?? {};

        if (
          previousNetworkInterfaceUUID &&
          previousNetworkInterfaceUUID !== networkInterfaceUUID
        ) {
          networkInterfaceInputMap[previousNetworkInterfaceUUID].isApplied =
            false;
        }

        interfaces[interfaceIndex] = networkInterfaceHeld;
        networkInterfaceInputMap[networkInterfaceUUID].isApplied = true;
      };

    dragAreaDraggingSx = { cursor: 'grabbing' };

    floatingNetworkInterface = (
      <BriefNetworkInterface
        isFloating
        networkInterface={networkInterfaceHeld}
        sx={{
          left: `calc(${dragMousePosition.x}px + .4em)`,
          position: 'absolute',
          top: `calc(${dragMousePosition.y}px - 1.6em)`,
          zIndex: 20,
        }}
      />
    );

    handleDragAreaMouseLeave = () => {
      clearNetworkInterfaceHeld();
    };

    handleDragAreaMouseMove = ({
      currentTarget,
      nativeEvent: { clientX, clientY },
    }) => {
      const { left, top } = currentTarget.getBoundingClientRect();

      setDragMousePosition({
        x: clientX - left,
        y: clientY - top,
      });
    };

    handleDragAreaMouseUp = () => {
      clearNetworkInterfaceHeld();
    };
  }

  useEffect(() => {
    const map = networkInterfaces.reduce<NetworkInterfaceInputMap>(
      (reduceContainer, { networkInterfaceUUID }) => {
        reduceContainer[networkInterfaceUUID] =
          networkInterfaceInputMap[networkInterfaceUUID] ?? {};

        return reduceContainer;
      },
      {},
    );

    setNetworkInterfaceInputMap(map);

    // This block inits the input map for the MOCK_NICS.
    // TODO: remove after testing.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return isLoading ? (
    <Spinner />
  ) : (
    <MUIBox
      onMouseDown={({ clientX, clientY, currentTarget }) => {
        const { left, top } = currentTarget.getBoundingClientRect();

        setDragMousePosition({
          x: clientX - left,
          y: clientY - top,
        });
      }}
      onMouseLeave={handleDragAreaMouseLeave}
      onMouseMove={handleDragAreaMouseMove}
      onMouseUp={handleDragAreaMouseUp}
      sx={{ position: 'relative', ...dragAreaDraggingSx }}
    >
      {floatingNetworkInterface}
      <MUIBox
        sx={{
          display: 'flex',
          flexDirection: 'column',

          '& > :not(:first-child, :last-child)': {
            marginTop: '1em',
          },
        }}
      >
        <MUIDataGrid
          autoHeight
          columns={createNetworkInterfaceTableColumns((row) => {
            setNetworkInterfaceHeld(row);
          }, networkInterfaceInputMap)}
          disableColumnMenu
          disableSelectionOnClick
          getRowId={({ networkInterfaceUUID }) => networkInterfaceUUID}
          hideFooter
          rows={networkInterfaces}
          sx={{
            color: GREY,

            [`& .${muiIconButtonClasses.root}`]: {
              color: 'inherit',
            },

            [`& .${muiGridClasses.cell}:focus`]: {
              outline: 'none',
            },
          }}
        />
        <MUIBox
          sx={{
            display: 'flex',
            flexDirection: 'row',
            justifyContent: 'center',
          }}
        >
          <IconButton
            disabled={
              networkInputs.length >= networkInterfaces.length ||
              Object.values(networkInterfaceInputMap).every(
                ({ isApplied }) => isApplied,
              )
            }
            onClick={createNetwork}
          >
            <MUIAddIcon />
          </IconButton>
        </MUIBox>
        <MUIBox
          sx={{
            alignItems: 'strech',
            display: 'flex',
            flexDirection: 'row',
            overflowX: 'auto',
            paddingLeft: '.3em',

            '& > *': {
              marginBottom: '1em',
              marginTop: '1em',
              minWidth: '13em',
              width: '25%',
            },

            '& > :not(:first-child)': {
              marginLeft: '1em',
            },
          }}
        >
          {networkInputs.map((networkInput, networkIndex) => {
            const { inputUUID, interfaces, ipAddress, subnetMask, type } =
              networkInput;
            const isNetworkOptional =
              networkIndex < optionalNetworkInputsLength;

            return (
              <InnerPanel key={`network-${inputUUID}`}>
                <InnerPanelHeader>
                  <SelectWithLabel
                    id={`network-${inputUUID}-name`}
                    isReadOnly={!isNetworkOptional}
                    label="Network name"
                    selectItems={Object.entries(NETWORK_TYPES).map(
                      ([networkType, networkTypeName]) => {
                        let count = getNetworkTypeCount(
                          networkType,
                          networkIndex,
                        );

                        if (networkType !== type) {
                          count += 1;
                        }

                        const displayValue = `${networkTypeName} ${count}`;

                        return { value: networkType, displayValue };
                      },
                    )}
                    selectProps={{
                      onChange: ({ target: { value } }) => {
                        networkInput.type = String(value);

                        setNetworkInputs([...networkInputs]);
                      },
                      renderValue: breakpointLarge
                        ? undefined
                        : (value) =>
                            `${String(
                              value,
                            ).toUpperCase()} ${getNetworkTypeCount(
                              type,
                              networkIndex,
                            )}`,
                      value: type,
                    }}
                  />
                </InnerPanelHeader>
                <MUIBox
                  sx={{
                    display: 'flex',
                    flexDirection: 'column',
                    margin: '.6em',

                    '& > :not(:first-child)': {
                      marginTop: '1em',
                    },
                  }}
                >
                  {NETWORK_INTERFACE_TEMPLATE.map((linkNumber) => {
                    const linkName = `Link ${linkNumber}`;
                    const networkInterfaceIndex = linkNumber - 1;
                    const networkInterface = interfaces[networkInterfaceIndex];
                    const { networkInterfaceUUID = '' } =
                      networkInterface ?? {};

                    const emptyDropAreaContent = breakpointMedium ? (
                      <BodyText text="Drop to add interface." />
                    ) : (
                      <MUIAddIcon
                        sx={{
                          alignSelf: 'center',
                          color: GREY,
                        }}
                      />
                    );

                    return (
                      <MUIBox
                        key={`network-${inputUUID}-link-${linkNumber}`}
                        sx={{
                          alignItems: 'center',
                          display: 'flex',
                          flexDirection: 'row',

                          '& > :not(:first-child)': {
                            marginLeft: '1em',
                          },

                          '& > :last-child': {
                            flexGrow: 1,
                          },
                        }}
                      >
                        <BodyText
                          sx={{ whiteSpace: 'nowrap' }}
                          text={linkName}
                        />
                        <DropArea
                          onMouseUp={createDropMouseUpHandler?.call(
                            null,
                            interfaces,
                            networkInterfaceIndex,
                          )}
                        >
                          {networkInterface ? (
                            <BriefNetworkInterface
                              key={`network-interface-${networkInterfaceUUID}`}
                              networkInterface={networkInterface}
                              onClose={() => {
                                interfaces[networkInterfaceIndex] = undefined;
                                networkInterfaceInputMap[
                                  networkInterfaceUUID
                                ].isApplied = false;

                                setNetworkInterfaceInputMap({
                                  ...networkInterfaceInputMap,
                                });
                              }}
                            />
                          ) : (
                            emptyDropAreaContent
                          )}
                        </DropArea>
                      </MUIBox>
                    );
                  })}
                  <OutlinedInputWithLabel
                    id={`network-${inputUUID}-ip-address`}
                    label="IP address"
                    onChange={({ target: { value } }) => {
                      networkInput.ipAddress = String(value);

                      setNetworkInputs([...networkInputs]);
                    }}
                    value={ipAddress}
                  />
                  <OutlinedInputWithLabel
                    id={`network-${inputUUID}-subnet-mask`}
                    label="Subnet mask"
                    onChange={({ target: { value } }) => {
                      networkInput.subnetMask = String(value);

                      setNetworkInputs([...networkInputs]);
                    }}
                    value={subnetMask}
                  />
                </MUIBox>
              </InnerPanel>
            );
          })}
        </MUIBox>
      </MUIBox>
    </MUIBox>
  );
};

export type { NetworkInput, NetworkInterfaceInputMap };

export default NetworkInitForm;
