// Copyright 2020 Northern.tech AS
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router';

import { Delete as DeleteIcon, Settings, Square } from '@mui/icons-material';
import { IconButton, LinearProgress, linearProgressClasses } from '@mui/material';
import { PieChart as MuiPieChart } from '@mui/x-charts/PieChart';
import { makeStyles } from 'tss-react/mui';

import Confirm from '@northern.tech/common-ui/Confirm';
import Loader from '@northern.tech/common-ui/Loader';
import { ALL_DEVICES, TIMEOUTS, chartTypes, rootfsImageVersion, softwareIndicator, softwareTitleMap } from '@northern.tech/store/constants';
import { getDeviceReports, getGroupsById } from '@northern.tech/store/selectors';
import { useAppDispatch } from '@northern.tech/store/store';
import { getReportDataWithoutBackendSupport, updateReportData } from '@northern.tech/store/thunks';
import { ensureVersionString } from '@northern.tech/store/utils';
import { isEmpty, toggle } from '@northern.tech/utils/helpers';

import BaseWidget from './BaseWidget';
import { ChartEditWidget, Header } from './ChartAddition';

const seriesOther = '__OTHER__';

export const chartColorPalette = ['#1d8faf', '#a31773', '#00859e', '#14cfda', '#9bfff0', '#d5d5d5'];

const createColorClassName = hexColor => `color-${hexColor.slice(1)}`;

const useStyles = makeStyles()(theme => ({
  indicator: { fontSize: 10, minWidth: 'initial', marginLeft: 4 },
  pieChart: { aspectRatio: '1 / 1', maxWidth: 200, width: '100%' },
  legendItem: {
    alignItems: 'center',
    display: 'grid',
    gridTemplateColumns: '1fr max-content',
    columnGap: theme.spacing(2),
    '&.indicating': {
      gridTemplateColumns: 'min-content 1fr max-content',
      columnGap: theme.spacing()
    }
  },
  wrapper: {
    display: 'grid',
    gridTemplateColumns: '200px 1fr',
    columnGap: theme.spacing(2),
    marginBottom: theme.spacing(2),
    '&>.flexbox.column > *': {
      height: 20
    },
    '.barchart': {
      [`.${linearProgressClasses.root}`]: {
        backgroundColor: theme.palette.grey[400]
      },
      ...Object.values(chartColorPalette).reduce(
        (accu, color) => ({
          ...accu,
          [`.${createColorClassName(color)} .${linearProgressClasses.bar}`]: { backgroundColor: color }
        }),
        {
          [`.${createColorClassName(theme.palette.grey[400])} .${linearProgressClasses.bar}`]: { backgroundColor: theme.palette.grey[400] }
        }
      )
    }
  }
}));

const ChartLegend = ({ classes, data, onClick, showIndicators = true }) => (
  <div className="flexbox column">
    {data.map(({ fill, x, title, tip, value }) => (
      <div className={`clickable ${classes.legendItem} ${showIndicators ? 'indicating' : ''}`} key={x} onClick={() => onClick(x)} title={tip}>
        {showIndicators && <Square className={classes.indicator} style={{ fill }} />}
        <div className="text-overflow">{title}</div>
        <div>{value.toLocaleString()}</div>
      </div>
    ))}
  </div>
);

const BarChart = ({ data, onClick }) => {
  const [chartData, setChartData] = useState([]);
  const timer = useRef();

  useEffect(() => {
    setChartData(data.map(item => ({ ...item, y: 0 })));
    clearTimeout(timer.current);
    timer.current = setTimeout(() => setChartData(data), TIMEOUTS.debounceDefault);
    return () => {
      clearTimeout(timer.current);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [JSON.stringify(data)]);

  return (
    <div className="flexbox column">
      {chartData.map(({ fill, x, y, tip }) => (
        <div className="clickable flexbox column barchart" key={x} onClick={() => onClick(x)} title={tip} style={{ justifyContent: 'center' }}>
          <LinearProgress className={createColorClassName(fill)} variant="determinate" value={y} style={{ height: 8 }} />
        </div>
      ))}
    </div>
  );
};

const ChartContainer = ({ className, children }) => <div className={className}>{children}</div>;

const BarChartContainer = ({ classes, data, onClick }) => (
  <ChartContainer className={classes.wrapper}>
    <ChartLegend classes={classes} data={data} onClick={onClick} showIndicators={false} />
    <BarChart data={data} onClick={onClick} />
  </ChartContainer>
);

const PieChart = ({ classes, data, onClick }) => (
  <div className={classes.pieChart}>
    <MuiPieChart
      className="clickable"
      hideLegend
      onItemClick={(e, { dataIndex }) => onClick(data[dataIndex].x)}
      series={[{ data: data.map(({ fill, title, value, x }) => ({ color: fill, id: x, label: title, value })) }]}
    />
  </div>
);

const PieChartContainer = ({ classes, ...chartProps }) => (
  <ChartContainer className={classes.wrapper}>
    <ChartLegend {...chartProps} classes={classes} />
    <PieChart {...chartProps} classes={classes} />
  </ChartContainer>
);

const chartTypeComponentMap = {
  [chartTypes.bar.key]: BarChartContainer,
  [chartTypes.pie.key]: PieChartContainer
};

const initDistribution = ({ data }) => {
  const { items, otherCount, total } = data;
  const numberOfItems = items.length > chartColorPalette.length ? chartColorPalette.length - 1 : items.length;
  const colors = chartColorPalette.slice(0, numberOfItems).reverse();
  const distribution = items
    .slice(0, colors.length)
    .map(({ key, count }, index) => ({
      x: key || '-',
      y: (count / total) * 100,
      title: key || '-',
      tip: key || '-',
      fill: chartColorPalette[index],
      value: count
    }))
    .reverse();
  if (items.length > chartColorPalette.length || otherCount) {
    distribution.splice(0, 0, {
      x: seriesOther,
      title: 'Other',
      tip: 'Other',
      y: (otherCount / total) * 100,
      fill: chartColorPalette[chartColorPalette.length - 1],
      value: otherCount
    });
  }
  distribution.sort((pairA, pairB) => pairB.y - pairA.y);
  return distribution;
};

interface DistributionReport {
  attribute: string;
  chartType: string;
  group: string;
  index: number;
  software: string;
  type: string;
}

type SoftwareLayer = {
  [key: string]: SoftwareLayer | string;
};

interface DistributionReportProps {
  onClick?: () => void;
  onSave: (selection: Partial<DistributionReport>) => void;
  selection?: Partial<DistributionReport>;
  software?: SoftwareLayer;
}

const cleanSoftwareTitle = (software: string) => {
  let cleanedSoftware = softwareTitleMap[software] ? softwareTitleMap[software].title : software;
  cleanedSoftware = cleanedSoftware.endsWith(softwareIndicator)
    ? cleanedSoftware.substring(0, cleanedSoftware.lastIndexOf(softwareIndicator))
    : cleanedSoftware;
  return cleanedSoftware;
};

export const DistributionReport = ({ onClick, onSave, selection = {}, software: softwareTree }: DistributionReportProps) => {
  const { attribute, chartType = chartTypes.bar.key, group, index: reportIndex, software: softwareSelection } = selection;
  const software = softwareSelection || attribute || rootfsImageVersion;
  const [editing, setEditing] = useState(false);
  const [removing, setRemoving] = useState(false);
  const navigate = useNavigate();
  const { classes } = useStyles();
  const reportsData = useSelector(getDeviceReports);
  const groupsById = useSelector(getGroupsById);
  const dispatch = useAppDispatch();
  const hasGroupDefinition = !!groupsById[group];
  const report = reportsData[reportIndex];
  const hasData = !isEmpty(report);

  useEffect(() => {
    if (group && !hasGroupDefinition) {
      return;
    }
    // this will retrieve device data and on repeat renders of the widget rely on stored data only - this increases the risk to miss data from the backend or
    // device initiated data changes, but assumes this is preferable over repeat queries to the backend causing rate limiting
    if (!hasData) {
      dispatch(getReportDataWithoutBackendSupport(reportIndex));
      return;
    }
    dispatch(updateReportData(reportIndex));
  }, [dispatch, reportIndex, group, hasData, hasGroupDefinition]);

  const distribution = useMemo(() => {
    if (isEmpty(report)) {
      return [];
    }
    return initDistribution({ data: report });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [JSON.stringify(report), JSON.stringify(selection)]);

  const onSliceClick = useCallback(
    target => {
      if (target === seriesOther) {
        return;
      }
      navigate(`/devices/accepted?inventory=${group ? `group:eq:${group}&` : ''}${ensureVersionString(software, attribute)}:eq:${target}`, {
        state: { internal: true }
      });
    },
    [attribute, group, navigate, software]
  );

  const toggleRemoving = () => setRemoving(toggle);

  const onToggleEditClick = () => setEditing(toggle);

  const onSaveClick = selection => {
    onSave(selection);
    setEditing(false);
  };

  const Chart = chartTypeComponentMap[chartType];
  const chartProps = {
    classes,
    data: distribution,
    onClick: onSliceClick
  };
  const { deviceIds = [], filters = [] } = groupsById[group] || {};
  const couldHaveDevices = !group || deviceIds.length > 0 || filters.length > 0;
  if (editing) {
    return <ChartEditWidget groups={groupsById} onSave={onSaveClick} onCancel={onToggleEditClick} selection={selection} software={softwareTree} />;
  }
  if (!report) {
    return <BaseWidget className="chart-widget flexbox centered" main={<Loader show />} />;
  }
  return (
    <div className="widget chart-widget relative">
      {removing && (
        <Confirm
          classes="flexbox centered confirmation-overlay"
          cancel={toggleRemoving}
          action={onClick}
          style={{ justifyContent: 'center' }}
          type="chartRemoval"
        />
      )}
      <div className="margin-bottom-small">
        <div className="flexbox space-between margin-bottom-small">
          <Header chartType={chartType} />
          <div className="flexbox align-items-center" style={{ zIndex: 1 }}>
            <IconButton onClick={onToggleEditClick} size="small">
              <Settings fontSize="small" />
            </IconButton>
            <IconButton onClick={toggleRemoving} size="small">
              <DeleteIcon fontSize="small" />
            </IconButton>
          </div>
        </div>
        <div className="flexbox space-between slightly-smaller">
          <div>{cleanSoftwareTitle(software)}</div>
          <div>{group || ALL_DEVICES}</div>
        </div>
      </div>
      {distribution.length ? (
        <Chart {...chartProps} />
      ) : couldHaveDevices ? (
        <div className="muted flexbox centered">There are no devices that match the selected criteria.</div>
      ) : (
        <Loader show={true} />
      )}
    </div>
  );
};

export default DistributionReport;
