import { PanelOptions, ExportTask, DashboardQuery, DatasourceType, ExportStatus, PanelStatus, Dashboard } from '../types';

import { convertTimestampToDate, convertTimeZoneTypeToName, getDashboardUid } from '../../../utils';
import { CLOSE_ICON_BASE_64, DOWNLOAD_ICON_BASE_64, SELECT_ICON_BASE_64, UNSELECT_ICON_BASE_64 } from '../../../icons';

import { deleteTask, getStaticFile, getTasks, queryApi } from '../../../services/api_service';
import { getDashboardByUid, getDatasources } from '../../../services/grafana_backend_service';

import { contextSrv } from 'grafana/app/core/core';
import { css } from '@emotion/css';

import {
  Table,
  Button,
  HorizontalGroup,
  VerticalGroup,
  Modal,
  LoadingPlaceholder,
  TimeRangeInput,
  useStyles2,
} from '@grafana/ui';
import {
  PanelProps,
  toDataFrame,
  FieldType,
  applyFieldOverrides,
  createTheme,
  DataFrame,
  DataLinkClickEvent,
  PanelModel,
  DataQuery,
  DataSourceSettings,
  TimeRange,
  OrgRole,
} from '@grafana/data';
import { RefreshEvent } from '@grafana/runtime';

import React, { useState, useEffect } from 'react';
import * as _ from 'lodash';

const PANEL_ID = 'corpglory-dataexporter-panel';
const APP_ID = 'corpglory-dataexporter-app';

interface Props extends PanelProps<PanelOptions> {}

export function Panel({ width, height, timeRange, eventBus, timeZone }: Props) {
  // TODO: Dashboard type
  const [dashboard, setDashboard] = useState<Dashboard | null>(null);
  const [datasources, setDatasources] = useState<DataSourceSettings[] | null>(null);

  const [tasks, setTasks] = useState<ExportTask[] | null>(null);
  const [queries, setQueries] = useState<DashboardQuery[] | null>(null);

  const [tasksDataFrame, setTasksDataFrame] = useState<DataFrame | null>(null);
  const [queriesDataFrame, setQueriesDataFrame] = useState<DataFrame | null>(null);

  const [isModalOpen, setModalVisibility] = useState<boolean>(false);

  const [selectedTimeRange, setTimeRange] = useState<TimeRange>(timeRange);

  const [panelStatus, setPanelStatus] = useState<PanelStatus>(PanelStatus.LOADING);
  function setPanelStatusWithValidate(status: PanelStatus): void {
    if (panelStatus === PanelStatus.PERMISSION_ERROR) {
      return;
    }
    return setPanelStatus(status);
  }

  const [errorMessage, setErrorMessage] = useState<string | null>(null);

  const timeZoneName = convertTimeZoneTypeToName(timeZone);

  if (contextSrv.user.orgRole !== OrgRole.Admin) {
    setPanelStatusWithValidate(PanelStatus.PERMISSION_ERROR);
  }

  useEffect(() => {
    async function getCurrentDashboard(): Promise<any> {
      const currentDashboardUid = getDashboardUid(window.location.toString());

      return getDashboardByUid(currentDashboardUid);
    }

    getCurrentDashboard()
      .then((dash) => setDashboard(dash.dashboard))
      .catch((err) => console.error(err));
  }, []);

  useEffect(() => {
    getDatasources()
      .then((datasources) => setDatasources(datasources))
      .catch((err) => console.error(err));
  }, []);

  useEffect(() => {
    if (tasks === null) {
      return;
    }
    const dataFrame = getDataFrameForTaskTable(tasks);
    setTasksDataFrame(dataFrame);
  }, [tasks]); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(fetchTasks, []); // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    if (queries === null) {
      return;
    }
    setQueriesDataFrame(getDataFrameForQueriesTable(queries));
  }, [queries]); // eslint-disable-line react-hooks/exhaustive-deps

  async function updateQueries(dashboard: Dashboard | null, datasources: DataSourceSettings[] | null): Promise<void> {
    if (!dashboard || !datasources) {
      console.warn(`Can't update queries if there is no dashboard or datasources`);
      return;
    }

    const queries: DashboardQuery[] = [];
    dashboard.panels?.forEach((panel: PanelModel) => {

      // @ts-ignore
      if (panel.type === PANEL_ID) {
        return;
      }

      if (!_.includes(_.values(DatasourceType), panel.datasource?.type)) {
        return;
      }

      panel.targets?.forEach((target: DataQuery) => {
        const datasource = getDatasourceByUid(target.datasource?.uid);
        if (!datasource) {
          return;
        }
        queries.push({ selected: false, target, panel, datasource });
      });
    });
    setQueries(queries);
  }

  function fetchTasks(): void {
    getTasks()
      .then((tasks) => {
        setTasks(tasks);
        setPanelStatusWithValidate(PanelStatus.OK);
        for (let task of tasks) {
          if (task.progress?.status === ExportStatus.EXPORTING) {
            setTimeout(fetchTasks, 1000);
            return;
          }
        }
      })
      .catch((err) => {
        setPanelStatusWithValidate(PanelStatus.DATASOURCE_ERROR);
        console.error('some error', err);
        setErrorMessage(`${err.name}: ${err.message}`);
      });
  }

  eventBus.subscribe(RefreshEvent, fetchTasks);

  function getDatasourceByUid(uid?: string): DataSourceSettings | undefined {
    if (_.isNil(uid)) {
      console.warn(`uid is required to get datasource`);
      return undefined;
    }
    if (datasources === null) {
      console.warn(`there is no datasources yet`);
      return undefined;
    }
    const datasource = _.find(datasources, (datasource: DataSourceSettings) => datasource.uid === uid);

    if (!datasource) {
      console.warn(`can't find datasource "${uid}"`);
    }

    return datasource;
  }

  async function onAddTaskClick(timeZoneName: string): Promise<void> {
    const selectedQueries = _.filter(queries, (query: DashboardQuery) => query.selected);
    const timerange: [number, number] = [selectedTimeRange.from.unix(), selectedTimeRange.to.unix()];

    const task: ExportTask = {
      // @ts-ignore
      username: contextSrv.user.name,
      timeRange: {
        from: timerange[0] * 1000,
        to: timerange[1] * 1000,
      },
      queries: selectedQueries,
    };
    // TODO: move this function to API Service
    await queryApi('/task', {
      method: 'POST',
      data: {
        task,
        url: window.location.toString(),
        timeZoneName,
      },
    });

    fetchTasks();

    onCloseModal();
    unselectAllQueries();
  }

  function unselectAllQueries(): void {
    if (queries === null) {
      return;
    }
    setQueries(queries.map((query: DashboardQuery) => ({ ...query, selected: false })));
  }

  function openDatasourceModal(dashboard: Dashboard | null, datasources: DataSourceSettings[] | null): void {
    updateQueries(dashboard, datasources);
    setTimeRange(timeRange);
    setModalVisibility(true);
  }

  function onCloseModal(): void {
    setModalVisibility(false);
    unselectAllQueries();
  }

  function getDataFrameForQueriesTable(queries: DashboardQuery[]): DataFrame {
    const dataFrame = toDataFrame({
      name: 'A',
      fields: [
        {
          name: 'Select',
          type: FieldType.string,
          values: _.map(
            queries,
            (query) => `data:image/svg+xml;base64,${query.selected ? SELECT_ICON_BASE_64 : UNSELECT_ICON_BASE_64}`
          ),
          config: {
            custom: {
              filterable: false,
              displayMode: 'image',
            },
            links: [
              {
                targetBlank: false,
                title: 'Select',
                url: '#',
                onClick: (event: DataLinkClickEvent) => onDatasourceSelectClick(event),
              },
            ],
          },
        },
        {
          name: 'Panel',
          type: FieldType.string,
          values: _.map(queries, (query) => query.panel.title),
        },
        {
          name: 'RefId',
          type: FieldType.string,
          values: _.map(queries, (query) => query.target.refId),
        },
        {
          name: 'Datasource',
          type: FieldType.string,
          values: _.map(queries, (query) => query.datasource.name),
        },
      ],
    });

    const dataFrames = applyFieldOverrides({
      data: [dataFrame],
      fieldConfig: {
        overrides: [],
        defaults: {},
      },
      theme: createTheme(),
      replaceVariables: (value: string) => value,
    });
    return dataFrames[0];
  }

  function getDataFrameForTaskTable(tasks: ExportTask[]): DataFrame {
    const sortedTasks = _.orderBy(tasks, (task) => task.progress?.time, 'desc');
    const dataFrame = toDataFrame({
      name: 'A',
      fields: [
        {
          name: 'Time',
          type: FieldType.number,
          values: _.map(sortedTasks, (task) => convertTimestampToDate(task.progress?.time)),
        },
        {
          name: 'From',
          type: FieldType.number,
          values: _.map(sortedTasks, (task) => convertTimestampToDate(task.timeRange.from)),
        },
        {
          name: 'To',
          type: FieldType.number,
          values: _.map(sortedTasks, (task) => convertTimestampToDate(task.timeRange.to)),
        },
        {
          name: 'User',
          type: FieldType.string,
          values: _.map(sortedTasks, (task) => task.username),
        },
        {
          name: 'Datasource',
          type: FieldType.string,
          values: _.map(sortedTasks, (task) => task.queries.map((query) => query.datasource?.name).join(',')),
        },
        {
          name: 'Exported Rows',
          type: FieldType.number,
          values: _.map(sortedTasks, (task) => task.progress?.exportedRowsCount),
        },
        {
          name: 'Progress',
          type: FieldType.string,
          values: _.map(sortedTasks, (task) => `${((task.progress?.progress || 0) * 100).toFixed(0)}%`),
        },
        {
          name: 'Status',
          type: FieldType.string,
          values: _.map(sortedTasks, (task) => task.progress?.status),
        },
        {
          name: 'Error',
          type: FieldType.string,
          values: _.map(sortedTasks, (task) => task.progress?.errorMessage || '-'),
        },
        {
          name: 'Download CSV',
          type: FieldType.string,
          values: _.map(sortedTasks, () => `data:image/png;base64,${DOWNLOAD_ICON_BASE_64}`),
          config: {
            custom: {
              filterable: false,
              displayMode: 'image',
            },
            links: [
              {
                targetBlank: false,
                title: 'Download',
                url: '#',
                onClick: (event: DataLinkClickEvent) => onDownloadClick(event),
              },
            ],
          },
        },
        {
          name: 'Delete task',
          type: FieldType.string,
          values: _.map(tasks, () => `data:image/png;base64,${CLOSE_ICON_BASE_64}`),
          config: {
            custom: {
              filterable: false,
              displayMode: 'image',
            },
            links: [
              {
                targetBlank: false,
                title: 'Delete',
                url: '#',
                onClick: (event: DataLinkClickEvent) => onDeleteClick(event),
              },
            ],
          },
        },
      ],
    });

    const dataFrames = applyFieldOverrides({
      data: [dataFrame],
      fieldConfig: {
        overrides: [],
        defaults: {},
      },
      theme: createTheme(),
      replaceVariables: (value: string) => value,
    });
    return dataFrames[0];
  }

  async function onDeleteClick(e: DataLinkClickEvent): Promise<void> {
    const rowIndex = e.origin.rowIndex;

    const task = _.find(tasks, (task, idx) => idx === rowIndex);
    await deleteTask(task?.id);

    const filteredTasks = _.filter(tasks, (task, idx) => idx !== rowIndex);
    setTasks(filteredTasks);
  }

  function onDownloadClick(e: DataLinkClickEvent): void {
    const rowIndex = e.origin.rowIndex;
    const task = _.find(tasks, (task, idx) => idx === rowIndex);
    getStaticFile(task?.id);
  }

  function onDatasourceSelectClick(e: DataLinkClickEvent): void {
    if (queries === null) {
      return;
    }
    const rowIndex = e.origin.rowIndex;
    const updatedQueries = _.clone(queries);
    updatedQueries[rowIndex].selected = !updatedQueries[rowIndex].selected;
    setQueries(updatedQueries);
  }

  const styles = useStyles2(getStyles);

  const loadingDiv = <LoadingPlaceholder text="Loading..."></LoadingPlaceholder>;
  // TODO: add styles
  const datasourceErrorDiv = (
    <div>
      <p>Datasource is unavailable.</p>
      <div>
        If you have not setup the plugin click
        <a className={styles.customLink} href={`/plugins/${APP_ID}`}>
          here
        </a>
        to configure DataExporter.
      </div>
      <p style={{ marginTop: '16px' }}>{errorMessage}</p>
    </div>
  );
  const permissionErrorDiv = (
    <div>
      <p> Permission Error. </p>
      <div> DataExporter panel available only for Admins. </div>
    </div>
  );
  const mainDiv = (
    <div>
      <Table width={width} height={height - 40} data={tasksDataFrame as DataFrame} />
      <HorizontalGroup justify="flex-end">
        <Button
          variant="primary"
          aria-label="Rich history button"
          icon="plus"
          style={{ marginTop: '8px' }}
          onClick={() => openDatasourceModal(dashboard, datasources)}
        >
          Add Task
        </Button>
        <Modal title="Select Queries" isOpen={isModalOpen} onDismiss={onCloseModal} className={styles.calendarModal}>
          {queriesDataFrame === null ? (
            <div>
              <p>There are no queries to export.</p>
            </div>
          ) : (
            <div>
              <VerticalGroup spacing="xs">
                <HorizontalGroup justify="flex-start" spacing="md">
                  <TimeRangeInput
                    value={selectedTimeRange}
                    onChange={(newTimeRange) => {
                      setTimeRange(newTimeRange);
                    }}
                  />
                </HorizontalGroup>
                <Table width={width / 2 - 20} height={height - 40} data={queriesDataFrame} />
                <HorizontalGroup justify="flex-end" spacing="md">
                  <Button
                    variant="primary"
                    aria-label="Add task button"
                    onClick={() => onAddTaskClick(timeZoneName)}
                    // TODO: move to function
                    disabled={!queries?.filter((query: DashboardQuery) => query.selected)?.length}
                  >
                    Add Task
                  </Button>
                </HorizontalGroup>
              </VerticalGroup>
            </div>
          )}
        </Modal>
      </HorizontalGroup>
    </div>
  );

  function renderSwitch(panelStatus: PanelStatus): JSX.Element {
    switch (panelStatus) {
      case PanelStatus.LOADING:
        return loadingDiv;
      case PanelStatus.DATASOURCE_ERROR:
        return datasourceErrorDiv;
      case PanelStatus.PERMISSION_ERROR:
        return permissionErrorDiv;
      case PanelStatus.OK:
        return mainDiv;
      default:
        return datasourceErrorDiv;
    }
  }
  return <div>{renderSwitch(panelStatus)}</div>;
}

const getStyles = () => ({
  calendarModal: css`
    section {
      position: fixed;
      top: 20%;
      z-index: 1061;
    }
  `,
  customLink: css`
    color: #6e9fff;
    margin: 0 4px;
    &:hover {
      text-decoration: underline;
    }
  `,
});
