import React, { useEffect, useMemo } from 'react';
import moment, { Moment } from 'moment-timezone';
import { keyBy, cloneDeep } from 'lodash';
import 'chartjs-adapter-moment';
import PropTypes from 'prop-types';

import { ThemeProvider } from 'styled-components';
import {
  Chart,
  ChartOptions,
  LineController,
  LineElement,
  PointElement,
  CategoryScale,
  LinearScale,
  TimeScale,
  Tooltip,
  Filler,
} from 'chart.js';
import gradient from '../utils/chart-js-plugins/gradient';

import { DEFAULT_TIMEZONE } from '../commons/constant';

import {
  TimedFormattedDatapoint,
  TimedRawData,
  TimeRangedLineGraphProps,
} from './interfaces';

import {
  Container,
  HeaderContainer,
  Title,
  ChartContainer,
  LegendContainer,
  RenderLegend,
  LegendLine,
  LegendLabel,
  CanvasChart,
} from './styledComponents';

import {
  DEFAULT_LINE_CHART_DATASET,
  getStandardLineChartOptions,
} from './utils/config';

import theme, { getTheme } from '../utils/theme';

Chart.register(
  LineController,
  LineElement,
  PointElement,
  CategoryScale,
  LinearScale,
  TimeScale,
  Tooltip,
  Filler,
  gradient
);

const DASHED_YEAR_MONTH_DAY = 'YYYY-MM-DD';

const timeRangeToDateArray = (
  startDate: string,
  endDate: string,
  format: string,
  timezone: string
): Moment[] => {
  const dateArray: Moment[] = [];
  const currentDate = moment(startDate, format).tz(timezone);
  const end = moment(endDate, format).tz(timezone);

  const diffDays = end.diff(currentDate, 'days');

  for (let i = 0; i <= diffDays; i += 1) {
    dateArray.push(moment(currentDate.format(format), format).tz(timezone));
    currentDate.add(1, 'day');
  }

  return dateArray;
};

const formatDataPointsOverTimeRange = (
  rawData: TimedRawData[],
  dateArray: Moment[],
  format: string,
  yAxisPropertyKey: string,
  xAxisPropertyKey: string,
  timezone: string
): TimedFormattedDatapoint[] => {
  const rawDataKeyByMatchingDateKey = keyBy(rawData, xAxisPropertyKey);

  return dateArray.map((currentDate) => {
    const formattedCurrentDate = moment
      .tz(currentDate, timezone)
      .format(format);
    const result: TimedFormattedDatapoint = {
      x: currentDate,
      y: null,
    };

    if (rawDataKeyByMatchingDateKey[formattedCurrentDate]) {
      Object.assign(result, {
        ...rawDataKeyByMatchingDateKey[formattedCurrentDate],
        y: rawDataKeyByMatchingDateKey[formattedCurrentDate][yAxisPropertyKey],
      });
    }

    return result;
  });
};

const TimeRangedLineGraph: React.FC<TimeRangedLineGraphProps> = (
  props: TimeRangedLineGraphProps
) => {
  const {
    rawData,
    startDate,
    endDate,
    title,
    yAxisPropertyKey,
    xAxisPropertyKey,
    defaultLineChartDataset,
    legend,
    renderTooltipHeader,
    renderTooltipBody,
    renderEmptyState,
    tooltipTitlePropertyKey,
    tooltipMinWidth,
    chartName,
    timezone,
  } = props;

  const dateArray = timeRangeToDateArray(
    startDate,
    endDate,
    DASHED_YEAR_MONTH_DAY,
    timezone || DEFAULT_TIMEZONE
  );

  const formattedDataPoints = formatDataPointsOverTimeRange(
    rawData,
    dateArray,
    DASHED_YEAR_MONTH_DAY,
    yAxisPropertyKey,
    xAxisPropertyKey,
    timezone || DEFAULT_TIMEZONE
  );

  const normalizeChartStartAndEndValue = (dataPoints) => {
    if (!dataPoints.length) {
      return [];
    }

    // Use clone deep to avoid reverse of value after formatting
    const clonedDataPoints = cloneDeep(dataPoints);

    const firstValidDataPoint = clonedDataPoints.find(
      (dataPoint) => dataPoint.y != null && dataPoint.y >= 0
    );

    const lastValidDataPoint = clonedDataPoints
      .reverse()
      .find((dataPoint) => dataPoint.y != null && dataPoint.y >= 0);

    if (firstValidDataPoint) {
      Object.assign(dataPoints[0], { y: firstValidDataPoint.y });
    }

    if (lastValidDataPoint) {
      Object.assign(dataPoints[dataPoints.length - 1], {
        y: lastValidDataPoint.y,
      });
    }
    return dataPoints;
  };

  const chartData = useMemo(
    () => ({
      labels: dateArray,
      datasets: [
        {
          ...defaultLineChartDataset,
          data: normalizeChartStartAndEndValue(formattedDataPoints),
        },
      ],
    }),
    [dateArray, defaultLineChartDataset, formattedDataPoints]
  );

  // Used as function to be able to pass function who going to render the tooltip on next step
  const chartOptions: ChartOptions<'line'> = getStandardLineChartOptions(
    startDate,
    endDate,
    timezone || DEFAULT_TIMEZONE,
    tooltipTitlePropertyKey,
    renderTooltipHeader,
    renderTooltipBody,
    xAxisPropertyKey,
    tooltipMinWidth
  );

  useEffect(() => {
    if ((!chartOptions && !chartData) || !!renderEmptyState) {
      return;
    }

    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    const ctx = document.getElementById(chartName).getContext('2d');

    // eslint-disable-next-line no-new
    const customChart = new Chart(ctx, {
      type: 'line',
      data: chartData,
      options: chartOptions,
      plugins: {
        // Required disable TsLint because gradient plugin of librairie doesn't match the required type
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        gradient,
      },
    });

    // eslint-disable-next-line consistent-return
    return () => {
      customChart.destroy();
    };
  });

  return (
    <>
      <ThemeProvider theme={getTheme(theme)}>
        <Container>
          {!!renderEmptyState && renderEmptyState()}
          {!renderEmptyState && (
            <>
              <HeaderContainer>
                <Title>{title}</Title>
              </HeaderContainer>
              <ChartContainer>
                <CanvasChart id={chartName} />
              </ChartContainer>
              {!!legend && (
                <LegendContainer>
                  <RenderLegend>
                    <LegendLine />
                    <LegendLabel>{legend}</LegendLabel>
                  </RenderLegend>
                </LegendContainer>
              )}
            </>
          )}
        </Container>
      </ThemeProvider>
    </>
  );
};

TimeRangedLineGraph.propTypes = {
  startDate: PropTypes.string.isRequired,
  endDate: PropTypes.string.isRequired,
  yAxisPropertyKey: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  xAxisPropertyKey: PropTypes.string.isRequired,
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  defaultLineChartDataset: PropTypes.shape({
    backgroundColor: PropTypes.string,
    borderColor: PropTypes.string,
    spanGaps: PropTypes.bool,
    data: PropTypes.arrayOf(PropTypes.shape({})),
  }),
  legend: PropTypes.string,
  renderTooltipHeader: PropTypes.func,
  renderTooltipBody: PropTypes.func,
  renderEmptyState: PropTypes.func,
  tooltipTitlePropertyKey: PropTypes.string,
  tooltipMinWidth: PropTypes.string,
  chartName: PropTypes.string.isRequired,
  timezone: PropTypes.string,
};

TimeRangedLineGraph.defaultProps = {
  defaultLineChartDataset: DEFAULT_LINE_CHART_DATASET,
  legend: '',
  renderTooltipHeader: undefined,
  renderTooltipBody: undefined,
  renderEmptyState: undefined,
  tooltipTitlePropertyKey: '',
  tooltipMinWidth: undefined,
  timezone: DEFAULT_TIMEZONE,
};

TimeRangedLineGraph.displayName = 'TimeRangedLineGraph';

export default TimeRangedLineGraph;
