/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 { HotkeysProvider, Intent } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import React from 'react';
import type { RouteComponentProps } from 'react-router';
import { Redirect } from 'react-router';
import { HashRouter, Route, Switch } from 'react-router-dom';

import type { HeaderActiveTab } from './components';
import { HeaderBar, Loader } from './components';
import type { DruidEngine, QueryWithContext } from './druid-models';
import { Capabilities } from './helpers';
import { AppToaster } from './singletons';
import { localStorageGetJson, LocalStorageKeys, QueryManager } from './utils';
import {
  DatasourcesView,
  HomeView,
  IngestionView,
  LoadDataView,
  LookupsView,
  SegmentsView,
  ServicesView,
  SqlDataLoaderView,
  WorkbenchView,
} from './views';

import './console-application.scss';

export interface ConsoleApplicationProps {
  exampleManifestsUrl?: string;
  defaultQueryContext?: Record<string, any>;
  mandatoryQueryContext?: Record<string, any>;
}

export interface ConsoleApplicationState {
  capabilities: Capabilities;
  capabilitiesLoading: boolean;
}

export class ConsoleApplication extends React.PureComponent<
  ConsoleApplicationProps,
  ConsoleApplicationState
> {
  private readonly capabilitiesQueryManager: QueryManager<null, Capabilities>;

  static shownServiceNotification() {
    AppToaster.show({
      icon: IconNames.ERROR,
      intent: Intent.DANGER,
      timeout: 120000,
      message: (
        <>
          It appears that the service serving this console is not responding. The console will not
          function at the moment.
        </>
      ),
    });
  }

  private supervisorId?: string;
  private taskId?: string;
  private taskGroupId?: string;
  private openDialog?: string;
  private datasource?: string;
  private onlyUnavailable?: boolean;
  private queryWithContext?: QueryWithContext;

  constructor(props: ConsoleApplicationProps, context: any) {
    super(props, context);
    this.state = {
      capabilities: Capabilities.FULL,
      capabilitiesLoading: true,
    };

    this.capabilitiesQueryManager = new QueryManager({
      processQuery: async () => {
        const capabilitiesOverride = localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE);
        const capabilities = capabilitiesOverride
          ? new Capabilities(capabilitiesOverride)
          : await Capabilities.detectCapabilities();

        if (!capabilities) {
          ConsoleApplication.shownServiceNotification();
          return Capabilities.FULL;
        }

        return await Capabilities.detectCapacity(capabilities);
      },
      onStateChange: ({ data, loading }) => {
        this.setState({
          capabilities: data || Capabilities.FULL,
          capabilitiesLoading: loading,
        });
      },
    });
  }

  componentDidMount(): void {
    this.capabilitiesQueryManager.runQuery(null);
  }

  componentWillUnmount(): void {
    this.capabilitiesQueryManager.terminate();
  }

  private readonly handleUnrestrict = (capabilities: Capabilities) => {
    this.setState({ capabilities });
  };

  private resetInitialsWithDelay() {
    setTimeout(() => {
      this.taskId = undefined;
      this.taskGroupId = undefined;
      this.supervisorId = undefined;
      this.openDialog = undefined;
      this.datasource = undefined;
      this.onlyUnavailable = undefined;
      this.queryWithContext = undefined;
    }, 50);
  }

  private readonly goToStreamingDataLoader = (supervisorId?: string) => {
    if (supervisorId) this.supervisorId = supervisorId;
    window.location.hash = 'streaming-data-loader';
    this.resetInitialsWithDelay();
  };

  private readonly goToClassicBatchDataLoader = (taskId?: string) => {
    if (taskId) this.taskId = taskId;
    window.location.hash = 'classic-batch-data-loader';
    this.resetInitialsWithDelay();
  };

  private readonly goToDatasources = (datasource: string) => {
    this.datasource = datasource;
    window.location.hash = 'datasources';
    this.resetInitialsWithDelay();
  };

  private readonly goToSegments = (datasource: string, onlyUnavailable = false) => {
    this.datasource = datasource;
    this.onlyUnavailable = onlyUnavailable;
    window.location.hash = 'segments';
    this.resetInitialsWithDelay();
  };

  private readonly goToIngestionWithTaskId = (taskId?: string) => {
    this.taskId = taskId;
    window.location.hash = 'ingestion';
    this.resetInitialsWithDelay();
  };

  private readonly goToIngestionWithTaskGroupId = (taskGroupId?: string, openDialog?: string) => {
    this.taskGroupId = taskGroupId;
    if (openDialog) this.openDialog = openDialog;
    window.location.hash = 'ingestion';
    this.resetInitialsWithDelay();
  };

  private readonly goToIngestionWithDatasource = (datasource?: string, openDialog?: string) => {
    this.datasource = datasource;
    if (openDialog) this.openDialog = openDialog;
    window.location.hash = 'ingestion';
    this.resetInitialsWithDelay();
  };

  private readonly goToQuery = (queryWithContext: QueryWithContext) => {
    this.queryWithContext = queryWithContext;
    window.location.hash = 'workbench';
    this.resetInitialsWithDelay();
  };

  private readonly wrapInViewContainer = (
    active: HeaderActiveTab,
    el: JSX.Element,
    classType: 'normal' | 'narrow-pad' | 'thin' = 'normal',
  ) => {
    const { capabilities } = this.state;

    return (
      <>
        <HeaderBar
          active={active}
          capabilities={capabilities}
          onUnrestrict={this.handleUnrestrict}
        />
        <div className={classNames('view-container', classType)}>{el}</div>
      </>
    );
  };

  private readonly wrappedHomeView = () => {
    const { capabilities } = this.state;
    return this.wrapInViewContainer(null, <HomeView capabilities={capabilities} />);
  };

  private readonly wrappedDataLoaderView = () => {
    const { exampleManifestsUrl } = this.props;

    return this.wrapInViewContainer(
      'data-loader',
      <LoadDataView
        mode="all"
        initTaskId={this.taskId}
        initSupervisorId={this.supervisorId}
        exampleManifestsUrl={exampleManifestsUrl}
        goToIngestion={this.goToIngestionWithTaskGroupId}
      />,
      'narrow-pad',
    );
  };

  private readonly wrappedStreamingDataLoaderView = () => {
    return this.wrapInViewContainer(
      'streaming-data-loader',
      <LoadDataView
        mode="streaming"
        initSupervisorId={this.supervisorId}
        goToIngestion={this.goToIngestionWithTaskGroupId}
      />,
      'narrow-pad',
    );
  };

  private readonly wrappedClassicBatchDataLoaderView = () => {
    const { exampleManifestsUrl } = this.props;

    return this.wrapInViewContainer(
      'classic-batch-data-loader',
      <LoadDataView
        mode="batch"
        initTaskId={this.taskId}
        exampleManifestsUrl={exampleManifestsUrl}
        goToIngestion={this.goToIngestionWithTaskGroupId}
      />,
      'narrow-pad',
    );
  };

  private readonly wrappedWorkbenchView = (p: RouteComponentProps<any>) => {
    const { defaultQueryContext, mandatoryQueryContext } = this.props;
    const { capabilities } = this.state;

    const queryEngines: DruidEngine[] = ['native'];
    if (capabilities.hasSql()) {
      queryEngines.push('sql-native');
    }
    if (capabilities.hasMultiStageQuery()) {
      queryEngines.push('sql-msq-task');
    }

    return this.wrapInViewContainer(
      'workbench',
      <WorkbenchView
        capabilities={capabilities}
        tabId={p.match.params.tabId}
        onTabChange={newTabId => {
          location.hash = `#workbench/${newTabId}`;
        }}
        initQueryWithContext={this.queryWithContext}
        defaultQueryContext={defaultQueryContext}
        mandatoryQueryContext={mandatoryQueryContext}
        queryEngines={queryEngines}
        allowExplain
        goToIngestion={this.goToIngestionWithTaskId}
      />,
      'thin',
    );
  };

  private readonly wrappedSqlDataLoaderView = () => {
    const { capabilities } = this.state;
    return this.wrapInViewContainer(
      'sql-data-loader',
      <SqlDataLoaderView
        capabilities={capabilities}
        goToQuery={this.goToQuery}
        goToIngestion={this.goToIngestionWithTaskId}
      />,
    );
  };

  private readonly wrappedDatasourcesView = () => {
    const { capabilities } = this.state;
    return this.wrapInViewContainer(
      'datasources',
      <DatasourcesView
        initDatasource={this.datasource}
        goToQuery={this.goToQuery}
        goToTask={this.goToIngestionWithDatasource}
        goToSegments={this.goToSegments}
        capabilities={capabilities}
      />,
    );
  };

  private readonly wrappedSegmentsView = () => {
    const { capabilities } = this.state;
    return this.wrapInViewContainer(
      'segments',
      <SegmentsView
        datasource={this.datasource}
        onlyUnavailable={this.onlyUnavailable}
        goToQuery={this.goToQuery}
        capabilities={capabilities}
      />,
    );
  };

  private readonly wrappedIngestionView = () => {
    const { capabilities } = this.state;
    return this.wrapInViewContainer(
      'ingestion',
      <IngestionView
        taskId={this.taskId}
        taskGroupId={this.taskGroupId}
        datasourceId={this.datasource}
        openDialog={this.openDialog}
        goToDatasource={this.goToDatasources}
        goToQuery={this.goToQuery}
        goToStreamingDataLoader={this.goToStreamingDataLoader}
        goToClassicBatchDataLoader={this.goToClassicBatchDataLoader}
        capabilities={capabilities}
      />,
    );
  };

  private readonly wrappedServicesView = () => {
    const { capabilities } = this.state;
    return this.wrapInViewContainer(
      'services',
      <ServicesView goToQuery={this.goToQuery} capabilities={capabilities} />,
    );
  };

  private readonly wrappedLookupsView = () => {
    return this.wrapInViewContainer('lookups', <LookupsView />);
  };

  render(): JSX.Element {
    const { capabilities, capabilitiesLoading } = this.state;

    if (capabilitiesLoading) {
      return (
        <div className="loading-capabilities">
          <Loader />
        </div>
      );
    }

    return (
      <HotkeysProvider>
        <HashRouter hashType="noslash">
          <div className="console-application">
            <Switch>
              {capabilities.hasCoordinatorAccess() && (
                <Route path="/data-loader" component={this.wrappedDataLoaderView} />
              )}
              {capabilities.hasCoordinatorAccess() && (
                <Route
                  path="/streaming-data-loader"
                  component={this.wrappedStreamingDataLoaderView}
                />
              )}
              {capabilities.hasCoordinatorAccess() && (
                <Route
                  path="/classic-batch-data-loader"
                  component={this.wrappedClassicBatchDataLoaderView}
                />
              )}
              {capabilities.hasCoordinatorAccess() && capabilities.hasMultiStageQuery() && (
                <Route path="/sql-data-loader" component={this.wrappedSqlDataLoaderView} />
              )}

              <Route path="/ingestion" component={this.wrappedIngestionView} />
              <Route path="/datasources" component={this.wrappedDatasourcesView} />
              <Route path="/segments" component={this.wrappedSegmentsView} />
              <Route path="/services" component={this.wrappedServicesView} />

              <Route path="/query">
                <Redirect to="/workbench" />
              </Route>
              <Route
                path={['/workbench/:tabId', '/workbench']}
                component={this.wrappedWorkbenchView}
              />

              {capabilities.hasCoordinatorAccess() && (
                <Route path="/lookups" component={this.wrappedLookupsView} />
              )}
              <Route component={this.wrappedHomeView} />
            </Switch>
          </div>
        </HashRouter>
      </HotkeysProvider>
    );
  }
}
