import { CardContent, Chip, IconButton, Tooltip } from "@material-ui/core";
import React, { useState, useEffect } from 'react';
import WarningIcon from '@material-ui/icons/Warning';
import { QueryStatus, runCypherQuery } from "./ReportQueryRunner";
import debounce from 'lodash/debounce';
import { useCallback } from 'react';
import { Typography, Fab } from '@material-ui/core';
import CircularProgress from "@material-ui/core/CircularProgress";
import NeoCodeViewerComponent from "../component/editor/CodeViewerComponent";
import { DEFAULT_ROW_LIMIT, HARD_ROW_LIMITING, REPORT_TYPES, RUN_QUERY_DELAY_MS, SELECTION_TYPES } from "../config/ReportConfig";
import { MoreVert } from "@material-ui/icons";
import { Neo4jContext, Neo4jContextState } from "use-neo4j/dist/neo4j.context";
import { useContext } from "react";
import NeoTableChart from "../chart/TableChart";

export const NeoReport = ({
    database = "neo4j", // The Neo4j database to run queries onto.
    query = "", // The Cypher query used to populate the report.
    parameters = {}, // A dictionary of parameters to pass into the query.
    disabled = false, // Whether to disable query execution.
    selection = {}, // A selection of return fields to send to the report.
    fields = [], // A list of the return data fields that the query produces.
    settings = {}, // An optional dictionary of customization settings to pass to the report.  
    setFields = (f) => { fields = f }, // The callback to update the set of query fields after query execution. 
    setGlobalParameter = () => { }, // callback to update global (dashboard) parameters.
    getGlobalParameter = (key) => {return ""}, // function to get global (cypher) parameters.
    refreshRate = 0, // Optionally refresh the report every X seconds.
    dimensions = { width: 300, height: 300 }, // Size of the report in pixels.
    rowLimit = DEFAULT_ROW_LIMIT, // The maximum number of records to render.
    queryTimeLimit = 20, // Time limit for queries before automatically aborted.
    type = "table", // The type of report as a string.
    expanded = false, // whether the report is visualized in a fullscreen view.
    ChartType = NeoTableChart, // The report component to render with the query results.
}) => {
    const [records, setRecords] = useState(null);
    const [timer, setTimer] = useState(null);
    const [status, setStatus] = useState(QueryStatus.NO_QUERY);
    const { driver } = useContext<Neo4jContextState>(Neo4jContext);
    if (!driver) throw new Error('`driver` not defined. Have you added it into your app as <Neo4jContext.Provider value={{driver}}> ?')

    const debouncedRunCypherQuery = useCallback(
        debounce(runCypherQuery, RUN_QUERY_DELAY_MS),
        [],
    );

    const populateReport = (debounced = true) => {
        // If this is a 'text-only' report, no queries are ran, instead we pass the input directly to the report.
        if (REPORT_TYPES[type].textOnly) {
            setStatus(QueryStatus.COMPLETE);
            setRecords([{ input: query, parameters: parameters }]);
            return;
        }

        // Reset the report records before we run the query.
        setRecords([]);

        // Determine the set of fields from the configurations.
        var numericFields = (REPORT_TYPES[type].selection && fields) ? Object.keys(REPORT_TYPES[type].selection).filter(field => REPORT_TYPES[type].selection[field].type == SELECTION_TYPES.NUMBER && !REPORT_TYPES[type].selection[field].multiple) : [];
        var numericOrDatetimeFields = (REPORT_TYPES[type].selection && fields) ? Object.keys(REPORT_TYPES[type].selection).filter(field => REPORT_TYPES[type].selection[field].type == SELECTION_TYPES.NUMBER_OR_DATETIME && !REPORT_TYPES[type].selection[field].multiple) : [];
        var textFields = (REPORT_TYPES[type].selection && fields) ? Object.keys(REPORT_TYPES[type].selection).filter(field => REPORT_TYPES[type].selection[field].type == SELECTION_TYPES.TEXT && !REPORT_TYPES[type].selection[field].multiple) : [];
        var optionalFields = (REPORT_TYPES[type].selection && fields) ? Object.keys(REPORT_TYPES[type].selection).filter(field => REPORT_TYPES[type].selection[field].optional == true) : [];

        // Take care of multi select fields, they need to be added to the numeric fields too.
        if (REPORT_TYPES[type].selection) {
            Object.keys(REPORT_TYPES[type].selection).forEach((field, i) => {
                if (REPORT_TYPES[type].selection[field].multiple && selection[field]) {
                    selection[field].forEach((f, i) => numericFields.push(field + "(" + f + ")"))
                }
            });
        }

        const defaultKeyField = (REPORT_TYPES[type].selection) ? Object.keys(REPORT_TYPES[type].selection).find(field => REPORT_TYPES[type].selection[field].key == true) : undefined;
        const useRecordMapper = REPORT_TYPES[type].useRecordMapper == true;
        const useNodePropsAsFields = REPORT_TYPES[type].useNodePropsAsFields == true;

        if (debounced) {
            setStatus(QueryStatus.RUNNING)
            debouncedRunCypherQuery(driver, database, query, parameters, selection, fields,
                rowLimit, setStatus, setRecords, setFields, HARD_ROW_LIMITING, useRecordMapper, useNodePropsAsFields,
                numericFields, numericOrDatetimeFields, textFields, optionalFields, defaultKeyField, queryTimeLimit);
        } else {
            runCypherQuery(driver, database, query, parameters, selection, fields,
                rowLimit, setStatus, setRecords, setFields, HARD_ROW_LIMITING, useRecordMapper, useNodePropsAsFields,
                numericFields, numericOrDatetimeFields, textFields, optionalFields, defaultKeyField, queryTimeLimit);
        }
    };

    // When report parameters are changed, re-run the report.
    useEffect(() => {

        if (timer) {
            // @ts-ignore
            clearInterval(timer);
        }
        if (!disabled) {
            if (query.trim() == "") {
                setStatus(QueryStatus.NO_QUERY);
            }
            populateReport();
            // If a refresh rate was specified, set up an interval for re-running the report. (max 24 hrs)
            if (refreshRate && refreshRate > 0) {
                // @ts-ignore
                setTimer(setInterval(function () {
                    populateReport(false);
                }, Math.min(refreshRate, 86400) * 1000.0));
            }
        }
    }, REPORT_TYPES[type].useRecordMapper == true ?
        [disabled, query, JSON.stringify(parameters), fields ? fields : [], JSON.stringify(selection)] :
        [disabled, query, JSON.stringify(parameters), null, null])

    // Define query callback to allow reports to get extra data on interactions.
    const queryCallback = useCallback(
        (query, parameters, setRecords) => {
            runCypherQuery(driver, database, query, parameters, selection, fields, rowLimit,
                (status) => { status == QueryStatus.NO_DATA ? setRecords([]) : null },
                (result => setRecords(result)),
                () => { return }, HARD_ROW_LIMITING,
                REPORT_TYPES[type].useRecordMapper == true, false,
                [], [], [], [], null, queryTimeLimit);
        },
        [],
    );

    // Draw the report based on the query status.
    if (disabled) {
        return <div></div>;
    } else if (status == QueryStatus.NO_QUERY) {
        return (<div style={{ padding: 15 }}>No query specified. <br /> Use the <Chip style={{backgroundColor: "#efefef"}}size="small" icon={<MoreVert />} label="Report Settings" /> button to get started. </div>);
    } else if (status == QueryStatus.RUNNING) {
        return (<Typography variant="h2" color="textSecondary" style={{ paddingTop: "100px", textAlign: "center" }}>
            <CircularProgress color="inherit" />
        </Typography>);
    } else if (status == QueryStatus.NO_DATA) {
        return <NeoCodeViewerComponent value={"Query returned no data."} />
    } else if (status == QueryStatus.NO_DRAWABLE_DATA) {
        return <NeoCodeViewerComponent value={"Data was returned, but it can't be visualized.\n\n" +
            "This could have the following causes:\n" +
            "- a numeric value field was selected, but no numeric values were returned. \n" +
            "- a numeric value field was selected, but only zero's were returned.\n" +
            "- Your visualization expects nodes/relationships, but none were returned."
        } />
    } else if (status == QueryStatus.COMPLETE) {
        if (records == null || records.length == 0) {
            return <div>Loading...</div>
        }
        {/* @ts-ignore */ }
        return (<div style={{ height: "100%", marginTop: "0px", overflow: REPORT_TYPES[type].allowScrolling ? "auto" : "hidden" }}>
            <ChartType records={records}
                selection={selection}
                settings={settings}
                fullscreen={expanded}
                dimensions={dimensions}
                parameters={parameters}
                queryCallback={queryCallback}
                setGlobalParameter={setGlobalParameter}
                getGlobalParameter={getGlobalParameter} />
        </div>);
    } else if (status == QueryStatus.COMPLETE_TRUNCATED) {
        if (records == null || records.length == 0) {
            return <div>Loading...</div>
        }
        {/* Results have been truncated */ }
        return (<div style={{ height: "100%", marginTop: "0px", overflow: REPORT_TYPES[type].allowScrolling ? "auto" : "hidden" }}>
            <div style={{ marginBottom: "-31px" }}>
                <div style={{ display: "flex" }} >
                    <Tooltip title={"Over " + rowLimit + " row(s) were returned, results have been truncated."} placement="left" aria-label="host">
                        <WarningIcon style={{ zIndex: 999, marginTop: "2px", marginRight: "20px", marginLeft: "auto", color: "orange" }} />
                    </Tooltip>
                </div>
            </div>
            <ChartType
                records={records}
                selection={selection}
                settings={settings}
                fullscreen={expanded}
                dimensions={dimensions}
                parameters={parameters}
                queryCallback={queryCallback}
                setGlobalParameter={setGlobalParameter}
                getGlobalParameter={getGlobalParameter} />
        </div>);
    } else if (status == QueryStatus.TIMED_OUT) {
        return <NeoCodeViewerComponent value={"Query was aborted - it took longer than " + queryTimeLimit + "s to run. \n"
            + "Consider limiting your returned query rows,\nor increase the maximum query time."} />
    }
    {/* @ts-ignore */ }

    return <NeoCodeViewerComponent value={records && records[0] && records[0].error && records[0].error}
        placeholder={"Unknown query error, check the browser console."} />

}

export default NeoReport;