import getURL from "discourse-common/lib/get-url";
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import { makeArray } from "discourse-common/lib/helpers";
import { isEmpty } from "@ember/utils";
import EmberObject from "@ember/object";
import { escapeExpression } from "discourse/lib/utilities";
import { ajax } from "discourse/lib/ajax";
import round from "discourse/lib/round";
import {
  fillMissingDates,
  formatUsername,
  toNumber
} from "discourse/lib/utilities";
import { number, durationTiny } from "discourse/lib/formatter";
import { renderAvatar } from "discourse/helpers/user-avatar";

// Change this line each time report format change
// and you want to ensure cache is reset
export const SCHEMA_VERSION = 4;

const Report = EmberObject.extend({
  average: false,
  percent: false,
  higher_is_better: true,
  description_link: null,
  description: null,

  @discourseComputed("type", "start_date", "end_date")
  reportUrl(type, start_date, end_date) {
    start_date = moment
      .utc(start_date)
      .locale("en")
      .format("YYYY-MM-DD");

    end_date = moment
      .utc(end_date)
      .locale("en")
      .format("YYYY-MM-DD");

    return getURL(
      `/admin/reports/${type}?start_date=${start_date}&end_date=${end_date}`
    );
  },

  valueAt(numDaysAgo) {
    if (this.data) {
      const wantedDate = moment()
        .subtract(numDaysAgo, "days")
        .locale("en")
        .format("YYYY-MM-DD");
      const item = this.data.find(d => d.x === wantedDate);
      if (item) {
        return item.y;
      }
    }
    return 0;
  },

  valueFor(startDaysAgo, endDaysAgo) {
    if (this.data) {
      const earliestDate = moment()
        .subtract(endDaysAgo, "days")
        .startOf("day");
      const latestDate = moment()
        .subtract(startDaysAgo, "days")
        .startOf("day");
      let d,
        sum = 0,
        count = 0;
      this.data.forEach(datum => {
        d = moment(datum.x);
        if (d >= earliestDate && d <= latestDate) {
          sum += datum.y;
          count++;
        }
      });
      if (this.method === "average" && count > 0) {
        sum /= count;
      }
      return round(sum, -2);
    }
  },

  @discourseComputed("data", "average")
  todayCount() {
    return this.valueAt(0);
  },

  @discourseComputed("data", "average")
  yesterdayCount() {
    return this.valueAt(1);
  },

  @discourseComputed("data", "average")
  sevenDaysAgoCount() {
    return this.valueAt(7);
  },

  @discourseComputed("data", "average")
  thirtyDaysAgoCount() {
    return this.valueAt(30);
  },

  @discourseComputed("data", "average")
  lastSevenDaysCount() {
    return this.averageCount(7, this.valueFor(1, 7));
  },

  @discourseComputed("data", "average")
  lastThirtyDaysCount() {
    return this.averageCount(30, this.valueFor(1, 30));
  },

  averageCount(count, value) {
    return this.average ? value / count : value;
  },

  @discourseComputed("yesterdayCount", "higher_is_better")
  yesterdayTrend(yesterdayCount, higherIsBetter) {
    return this._computeTrend(this.valueAt(2), yesterdayCount, higherIsBetter);
  },

  @discourseComputed("lastSevenDaysCount", "higher_is_better")
  sevenDaysTrend(lastSevenDaysCount, higherIsBetter) {
    return this._computeTrend(
      this.valueFor(8, 14),
      lastSevenDaysCount,
      higherIsBetter
    );
  },

  @discourseComputed("data")
  currentTotal(data) {
    return data.reduce((cur, pair) => cur + pair.y, 0);
  },

  @discourseComputed("data", "currentTotal")
  currentAverage(data, total) {
    return makeArray(data).length === 0
      ? 0
      : parseFloat((total / parseFloat(data.length)).toFixed(1));
  },

  @discourseComputed("trend", "higher_is_better")
  trendIcon(trend, higherIsBetter) {
    return this._iconForTrend(trend, higherIsBetter);
  },

  @discourseComputed("sevenDaysTrend", "higher_is_better")
  sevenDaysTrendIcon(sevenDaysTrend, higherIsBetter) {
    return this._iconForTrend(sevenDaysTrend, higherIsBetter);
  },

  @discourseComputed("thirtyDaysTrend", "higher_is_better")
  thirtyDaysTrendIcon(thirtyDaysTrend, higherIsBetter) {
    return this._iconForTrend(thirtyDaysTrend, higherIsBetter);
  },

  @discourseComputed("yesterdayTrend", "higher_is_better")
  yesterdayTrendIcon(yesterdayTrend, higherIsBetter) {
    return this._iconForTrend(yesterdayTrend, higherIsBetter);
  },

  @discourseComputed(
    "prev_period",
    "currentTotal",
    "currentAverage",
    "higher_is_better"
  )
  trend(prev, currentTotal, currentAverage, higherIsBetter) {
    const total = this.average ? currentAverage : currentTotal;
    return this._computeTrend(prev, total, higherIsBetter);
  },

  @discourseComputed("prev30Days", "lastThirtyDaysCount", "higher_is_better")
  thirtyDaysTrend(prev30Days, lastThirtyDaysCount, higherIsBetter) {
    return this._computeTrend(prev30Days, lastThirtyDaysCount, higherIsBetter);
  },

  @discourseComputed("type")
  method(type) {
    if (type === "time_to_first_response") {
      return "average";
    } else {
      return "sum";
    }
  },

  percentChangeString(val1, val2) {
    const change = this._computeChange(val1, val2);

    if (isNaN(change) || !isFinite(change)) {
      return null;
    } else if (change > 0) {
      return "+" + change.toFixed(0) + "%";
    } else {
      return change.toFixed(0) + "%";
    }
  },

  @discourseComputed("prev_period", "currentTotal", "currentAverage")
  trendTitle(prev, currentTotal, currentAverage) {
    let current = this.average ? currentAverage : currentTotal;
    let percent = this.percentChangeString(prev, current);

    if (this.average) {
      prev = prev ? prev.toFixed(1) : "0";
      if (this.percent) {
        current += "%";
        prev += "%";
      }
    } else {
      prev = number(prev);
      current = number(current);
    }

    return I18n.t("admin.dashboard.reports.trend_title", {
      percent,
      prev,
      current
    });
  },

  changeTitle(valAtT1, valAtT2, prevPeriodString) {
    const change = this.percentChangeString(valAtT1, valAtT2);
    let title = "";
    if (change) {
      title += `${change} change. `;
    }
    title += `Was ${number(valAtT1)} ${prevPeriodString}.`;
    return title;
  },

  @discourseComputed("yesterdayCount")
  yesterdayCountTitle(yesterdayCount) {
    return this.changeTitle(this.valueAt(2), yesterdayCount, "two days ago");
  },

  @discourseComputed("lastSevenDaysCount")
  sevenDaysCountTitle(lastSevenDaysCount) {
    return this.changeTitle(
      this.valueFor(8, 14),
      lastSevenDaysCount,
      "two weeks ago"
    );
  },

  @discourseComputed("prev30Days", "lastThirtyDaysCount")
  thirtyDaysCountTitle(prev30Days, lastThirtyDaysCount) {
    return this.changeTitle(
      prev30Days,
      lastThirtyDaysCount,
      "in the previous 30 day period"
    );
  },

  @discourseComputed("data")
  sortedData(data) {
    return this.xAxisIsDate ? data.toArray().reverse() : data.toArray();
  },

  @discourseComputed("data")
  xAxisIsDate() {
    if (!this.data[0]) return false;
    return this.data && this.data[0].x.match(/\d{4}-\d{1,2}-\d{1,2}/);
  },

  @discourseComputed("labels")
  computedLabels(labels) {
    return labels.map(label => {
      const type = label.type || "string";

      let mainProperty;
      if (label.property) mainProperty = label.property;
      else if (type === "user") mainProperty = label.properties["username"];
      else if (type === "topic") mainProperty = label.properties["title"];
      else if (type === "post")
        mainProperty = label.properties["truncated_raw"];
      else mainProperty = label.properties[0];

      return {
        title: label.title,
        sortProperty: label.sort_property || mainProperty,
        mainProperty,
        type,
        compute: (row, opts = {}) => {
          let value = null;

          if (opts.useSortProperty) {
            value = row[label.sort_property || mainProperty];
          } else {
            value = row[mainProperty];
          }

          if (type === "user") return this._userLabel(label.properties, row);
          if (type === "post") return this._postLabel(label.properties, row);
          if (type === "topic") return this._topicLabel(label.properties, row);
          if (type === "seconds") return this._secondsLabel(value);
          if (type === "link") return this._linkLabel(label.properties, row);
          if (type === "percent") return this._percentLabel(value);
          if (type === "bytes") return this._bytesLabel(value);
          if (type === "number") {
            return this._numberLabel(value, opts);
          }
          if (type === "date") {
            const date = moment(value);
            if (date.isValid()) return this._dateLabel(value, date);
          }
          if (type === "precise_date") {
            const date = moment(value);
            if (date.isValid()) return this._dateLabel(value, date, "LLL");
          }
          if (type === "text") return this._textLabel(value);

          return {
            value,
            type,
            property: mainProperty,
            formatedValue: value ? escapeExpression(value) : "—"
          };
        }
      };
    });
  },

  _userLabel(properties, row) {
    const username = row[properties.username];

    const formatedValue = () => {
      const userId = row[properties.id];

      const user = EmberObject.create({
        username,
        name: formatUsername(username),
        avatar_template: row[properties.avatar]
      });

      const href = getURL(`/admin/users/${userId}/${username}`);

      const avatarImg = renderAvatar(user, {
        imageSize: "tiny",
        ignoreTitle: true
      });

      return `<a href='${href}'>${avatarImg}<span class='username'>${user.name}</span></a>`;
    };

    return {
      value: username,
      formatedValue: username ? formatedValue() : "—"
    };
  },

  _topicLabel(properties, row) {
    const topicTitle = row[properties.title];

    const formatedValue = () => {
      const topicId = row[properties.id];
      const href = getURL(`/t/-/${topicId}`);
      return `<a href='${href}'>${escapeExpression(topicTitle)}</a>`;
    };

    return {
      value: topicTitle,
      formatedValue: topicTitle ? formatedValue() : "—"
    };
  },

  _postLabel(properties, row) {
    const postTitle = row[properties.truncated_raw];
    const postNumber = row[properties.number];
    const topicId = row[properties.topic_id];
    const href = getURL(`/t/-/${topicId}/${postNumber}`);

    return {
      property: properties.title,
      value: postTitle,
      formatedValue:
        postTitle && href
          ? `<a href='${href}'>${escapeExpression(postTitle)}</a>`
          : "—"
    };
  },

  _secondsLabel(value) {
    return {
      value: toNumber(value),
      formatedValue: durationTiny(value)
    };
  },

  _percentLabel(value) {
    return {
      value: toNumber(value),
      formatedValue: value ? `${value}%` : "—"
    };
  },

  _numberLabel(value, options = {}) {
    const formatNumbers = isEmpty(options.formatNumbers)
      ? true
      : options.formatNumbers;

    const formatedValue = () => (formatNumbers ? number(value) : value);

    return {
      value: toNumber(value),
      formatedValue: value ? formatedValue() : "—"
    };
  },

  _bytesLabel(value) {
    return {
      value: toNumber(value),
      formatedValue: I18n.toHumanSize(value)
    };
  },

  _dateLabel(value, date, format = "LL") {
    return {
      value,
      formatedValue: value ? date.format(format) : "—"
    };
  },

  _textLabel(value) {
    const escaped = escapeExpression(value);

    return {
      value,
      formatedValue: value ? escaped : "—"
    };
  },

  _linkLabel(properties, row) {
    const property = properties[0];
    const value = getURL(row[property]);
    const formatedValue = (href, anchor) => {
      return `<a href="${escapeExpression(href)}">${escapeExpression(
        anchor
      )}</a>`;
    };

    return {
      value,
      formatedValue: value ? formatedValue(value, row[properties[1]]) : "—"
    };
  },

  _computeChange(valAtT1, valAtT2) {
    return ((valAtT2 - valAtT1) / valAtT1) * 100;
  },

  _computeTrend(valAtT1, valAtT2, higherIsBetter) {
    const change = this._computeChange(valAtT1, valAtT2);

    if (change > 50) {
      return higherIsBetter ? "high-trending-up" : "high-trending-down";
    } else if (change > 2) {
      return higherIsBetter ? "trending-up" : "trending-down";
    } else if (change <= 2 && change >= -2) {
      return "no-change";
    } else if (change < -50) {
      return higherIsBetter ? "high-trending-down" : "high-trending-up";
    } else if (change < -2) {
      return higherIsBetter ? "trending-down" : "trending-up";
    }
  },

  _iconForTrend(trend, higherIsBetter) {
    switch (trend) {
      case "trending-up":
        return higherIsBetter ? "angle-up" : "angle-down";
      case "trending-down":
        return higherIsBetter ? "angle-down" : "angle-up";
      case "high-trending-up":
        return higherIsBetter ? "angle-double-up" : "angle-double-down";
      case "high-trending-down":
        return higherIsBetter ? "angle-double-down" : "angle-double-up";
      default:
        return "minus";
    }
  }
});

Report.reopenClass({
  fillMissingDates(report, options = {}) {
    const dataField = options.dataField || "data";
    const filledField = options.filledField || "data";
    const startDate = options.startDate || "start_date";
    const endDate = options.endDate || "end_date";

    if (_.isArray(report[dataField])) {
      const startDateFormatted = moment
        .utc(report[startDate])
        .locale("en")
        .format("YYYY-MM-DD");
      const endDateFormatted = moment
        .utc(report[endDate])
        .locale("en")
        .format("YYYY-MM-DD");

      if (report.modes[0] === "stacked_chart") {
        report[filledField] = report[dataField].map(rep => {
          return {
            req: rep.req,
            label: rep.label,
            color: rep.color,
            data: fillMissingDates(
              JSON.parse(JSON.stringify(rep.data)),
              startDateFormatted,
              endDateFormatted
            )
          };
        });
      } else {
        report[filledField] = fillMissingDates(
          JSON.parse(JSON.stringify(report[dataField])),
          startDateFormatted,
          endDateFormatted
        );
      }
    }
  },

  find(type, startDate, endDate, categoryId, groupId) {
    return ajax("/admin/reports/" + type, {
      data: {
        start_date: startDate,
        end_date: endDate,
        category_id: categoryId,
        group_id: groupId
      }
    }).then(json => {
      // don’t fill for large multi column tables
      // which are not date based
      const modes = json.report.modes;
      if (modes.length !== 1 && modes[0] !== "table") {
        Report.fillMissingDates(json.report);
      }

      const model = Report.create({ type: type });
      model.setProperties(json.report);

      if (json.report.related_report) {
        // TODO: fillMissingDates if xaxis is date
        const related = Report.create({
          type: json.report.related_report.type
        });
        related.setProperties(json.report.related_report);
        model.set("relatedReport", related);
      }

      return model;
    });
  }
});

export default Report;
