import Component from "@ember/component";
import EmberObject, { action, computed } from "@ember/object";
import { alias, and, equal, notEmpty, or } from "@ember/object/computed";
import { next } from "@ember/runloop";
import { isPresent } from "@ember/utils";
import { classNameBindings, classNames } from "@ember-decorators/component";
import { exportEntity } from "discourse/lib/export-csv";
import { outputExportResult } from "discourse/lib/export-result";
import ReportLoader from "discourse/lib/reports-loader";
import { isTesting } from "discourse-common/config/environment";
import { makeArray } from "discourse-common/lib/helpers";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
import Report, { DAILY_LIMIT_DAYS, SCHEMA_VERSION } from "admin/models/report";

const TABLE_OPTIONS = {
  perPage: 8,
  total: true,
  limit: 20,
  formatNumbers: true,
};

const CHART_OPTIONS = {};

@classNameBindings(
  "isHidden:hidden",
  "isHidden::is-visible",
  "isEnabled",
  "isLoading",
  "dasherizedDataSourceName"
)
@classNames("admin-report")
export default class AdminReport extends Component {
  isEnabled = true;
  disabledLabel = I18n.t("admin.dashboard.disabled");
  isLoading = false;
  rateLimitationString = null;
  dataSourceName = null;
  report = null;
  model = null;
  reportOptions = null;
  forcedModes = null;
  showAllReportsLink = false;
  filters = null;
  showTrend = false;
  showHeader = true;
  showTitle = true;
  showFilteringUI = false;

  @alias("model.dates_filtering") showDatesOptions;

  @or("showDatesOptions", "model.available_filters.length") showRefresh;

  @and("showTrend", "model.prev_period") shouldDisplayTrend;

  endDate = null;
  startDate = null;

  @or("showTimeoutError", "showExceptionError", "showNotFoundError") showError;
  @equal("model.error", "not_found") showNotFoundError;
  @equal("model.error", "timeout") showTimeoutError;
  @equal("model.error", "exception") showExceptionError;
  @notEmpty("model.data") hasData;

  _reports = [];

  @computed("siteSettings.dashboard_hidden_reports")
  get isHidden() {
    return (this.siteSettings.dashboard_hidden_reports || "")
      .split("|")
      .filter(Boolean)
      .includes(this.dataSourceName);
  }

  didReceiveAttrs() {
    super.didReceiveAttrs(...arguments);

    let startDate = moment();
    if (this.filters && isPresent(this.filters.startDate)) {
      startDate = moment(this.filters.startDate, "YYYY-MM-DD");
    }
    this.set("startDate", startDate);

    let endDate = moment();
    if (this.filters && isPresent(this.filters.endDate)) {
      endDate = moment(this.filters.endDate, "YYYY-MM-DD");
    }
    this.set("endDate", endDate);

    if (this.filters) {
      this.set("currentMode", this.filters.mode);
    }

    if (this.report) {
      this._renderReport(this.report, this.forcedModes, this.currentMode);
    } else if (this.dataSourceName) {
      this._fetchReport();
    }
  }

  @discourseComputed("dataSourceName", "model.type")
  dasherizedDataSourceName(dataSourceName, type) {
    return (dataSourceName || type || "undefined").replace(/_/g, "-");
  }

  @discourseComputed("dataSourceName", "model.type")
  dataSource(dataSourceName, type) {
    dataSourceName = dataSourceName || type;
    return `/admin/reports/${dataSourceName}`;
  }

  @discourseComputed("displayedModes.length")
  showModes(displayedModesLength) {
    return displayedModesLength > 1;
  }

  @discourseComputed("currentMode")
  isChartMode(currentMode) {
    return currentMode === "chart";
  }

  @action
  changeGrouping(grouping) {
    this.send("refreshReport", {
      chartGrouping: grouping,
    });
  }

  @discourseComputed("currentMode", "model.modes", "forcedModes")
  displayedModes(currentMode, reportModes, forcedModes) {
    const modes = forcedModes ? forcedModes.split(",") : reportModes;

    return makeArray(modes).map((mode) => {
      const base = `btn-default mode-btn ${mode}`;
      const cssClass = currentMode === mode ? `${base} btn-primary` : base;

      return {
        mode,
        cssClass,
        icon: mode === "table" ? "table" : "signal",
      };
    });
  }

  @discourseComputed("currentMode")
  modeComponent(currentMode) {
    return `admin-report-${currentMode.replace(/_/g, "-")}`;
  }

  @discourseComputed(
    "dataSourceName",
    "startDate",
    "endDate",
    "filters.customFilters"
  )
  reportKey(dataSourceName, startDate, endDate, customFilters) {
    if (!dataSourceName || !startDate || !endDate) {
      return null;
    }

    startDate = startDate.toISOString(true).split("T")[0];
    endDate = endDate.toISOString(true).split("T")[0];

    let reportKey = "reports:";
    reportKey += [
      dataSourceName,
      isTesting() ? "start" : startDate.replace(/-/g, ""),
      isTesting() ? "end" : endDate.replace(/-/g, ""),
      "[:prev_period]",
      this.get("reportOptions.table.limit"),
      // Convert all filter values to strings to ensure unique serialization
      customFilters
        ? JSON.stringify(customFilters, (k, v) => (k ? `${v}` : v))
        : null,
      SCHEMA_VERSION,
    ]
      .filter((x) => x)
      .map((x) => x.toString())
      .join(":");

    return reportKey;
  }

  @discourseComputed("options.chartGrouping", "model.chartData.length")
  chartGroupings(grouping, count) {
    const options = ["daily", "weekly", "monthly"];

    return options.map((id) => {
      return {
        id,
        disabled: id === "daily" && count >= DAILY_LIMIT_DAYS,
        label: `admin.dashboard.reports.${id}`,
        class: `chart-grouping ${grouping === id ? "active" : "inactive"}`,
      };
    });
  }

  @action
  onChangeDateRange(range) {
    this.setProperties({
      startDate: range.from,
      endDate: range.to,
    });
  }

  @action
  applyFilter(id, value) {
    let customFilters = this.get("filters.customFilters") || {};

    if (typeof value === "undefined") {
      delete customFilters[id];
    } else {
      customFilters[id] = value;
    }

    this.send("refreshReport", {
      filters: customFilters,
    });
  }

  @action
  refreshReport(options = {}) {
    if (!this.onRefresh) {
      return;
    }

    this.onRefresh({
      type: this.get("model.type"),
      mode: this.currentMode,
      chartGrouping: options.chartGrouping,
      startDate:
        typeof options.startDate === "undefined"
          ? this.startDate
          : options.startDate,
      endDate:
        typeof options.endDate === "undefined" ? this.endDate : options.endDate,
      filters:
        typeof options.filters === "undefined"
          ? this.get("filters.customFilters")
          : options.filters,
    });
  }

  @action
  exportCsv() {
    const args = {
      name: this.get("model.type"),
      start_date: this.startDate.toISOString(true).split("T")[0],
      end_date: this.endDate.toISOString(true).split("T")[0],
    };

    const customFilters = this.get("filters.customFilters");
    if (customFilters) {
      Object.assign(args, customFilters);
    }

    exportEntity("report", args).then(outputExportResult);
  }

  @action
  onChangeMode(mode) {
    this.set("currentMode", mode);

    this.send("refreshReport", {
      chartGrouping: null,
    });
  }

  _computeReport() {
    if (!this.element || this.isDestroying || this.isDestroyed) {
      return;
    }

    if (!this._reports || !this._reports.length) {
      return;
    }

    // on a slow network _fetchReport could be called multiple times between
    // T and T+x, and all the ajax responses would occur after T+(x+y)
    // to avoid any inconsistencies we filter by period and make sure
    // the array contains only unique values
    let filteredReports = this._reports.uniqBy("report_key");
    let report;

    const sort = (r) => {
      if (r.length > 1) {
        return r.findBy("type", this.dataSourceName);
      } else {
        return r;
      }
    };

    if (!this.startDate || !this.endDate) {
      report = sort(filteredReports)[0];
    } else {
      report = sort(
        filteredReports.filter((r) => r.report_key.includes(this.reportKey))
      )[0];

      if (!report) {
        return;
      }
    }

    if (report.error === "not_found") {
      this.set("showFilteringUI", false);
    }

    this._renderReport(report, this.forcedModes, this.currentMode);
  }

  _renderReport(report, forcedModes, currentMode) {
    const modes = forcedModes ? forcedModes.split(",") : report.modes;
    currentMode = currentMode || (modes ? modes[0] : null);

    this.setProperties({
      model: report,
      currentMode,
      options: this._buildOptions(currentMode, report),
    });
  }

  _fetchReport() {
    this.setProperties({ isLoading: true, rateLimitationString: null });

    next(() => {
      let payload = this._buildPayload(["prev_period"]);

      const callback = (response) => {
        if (!this.element || this.isDestroying || this.isDestroyed) {
          return;
        }

        this.set("isLoading", false);

        if (response === 429) {
          this.set(
            "rateLimitationString",
            I18n.t("admin.dashboard.too_many_requests")
          );
        } else if (response === 500) {
          this.set("model.error", "exception");
        } else if (response) {
          this._reports.push(this._loadReport(response));
          this._computeReport();
        }
      };

      ReportLoader.enqueue(this.dataSourceName, payload.data, callback);
    });
  }

  _buildPayload(facets) {
    let payload = { data: { facets } };

    if (this.startDate) {
      payload.data.start_date = moment(this.startDate)
        .toISOString(true)
        .split("T")[0];
    }

    if (this.endDate) {
      payload.data.end_date = moment(this.endDate)
        .toISOString(true)
        .split("T")[0];
    }

    if (this.get("reportOptions.table.limit")) {
      payload.data.limit = this.get("reportOptions.table.limit");
    }

    if (this.get("filters.customFilters")) {
      payload.data.filters = this.get("filters.customFilters");
    }

    return payload;
  }

  _buildOptions(mode, report) {
    if (mode === "table") {
      const tableOptions = JSON.parse(JSON.stringify(TABLE_OPTIONS));
      return EmberObject.create(
        Object.assign(tableOptions, this.get("reportOptions.table") || {})
      );
    } else if (mode === "chart") {
      const chartOptions = JSON.parse(JSON.stringify(CHART_OPTIONS));
      return EmberObject.create(
        Object.assign(chartOptions, this.get("reportOptions.chart") || {}, {
          chartGrouping:
            this.get("reportOptions.chartGrouping") ||
            Report.groupingForDatapoints(report.chartData.length),
        })
      );
    } else if (mode === "stacked-chart" || mode === "stacked_chart") {
      return this.get("reportOptions.stackedChart") || {};
    }
  }

  _loadReport(jsonReport) {
    Report.fillMissingDates(jsonReport, { filledField: "chartData" });

    if (jsonReport.chartData && jsonReport.modes[0] === "stacked_chart") {
      jsonReport.chartData = jsonReport.chartData.map((chartData) => {
        if (chartData.length > 40) {
          return {
            data: chartData.data,
            req: chartData.req,
            label: chartData.label,
            color: chartData.color,
          };
        } else {
          return chartData;
        }
      });
    }

    if (jsonReport.prev_data) {
      Report.fillMissingDates(jsonReport, {
        filledField: "prevChartData",
        dataField: "prev_data",
        starDate: jsonReport.prev_startDate,
        endDate: jsonReport.prev_endDate,
      });
    }

    return Report.create(jsonReport);
  }
}