/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: BUSL-1.1
 */

import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { isAfter, isBefore, isSameMonth, format } from 'date-fns';
import getStorage from 'vault/lib/token-storage';
import { parseAPITimestamp } from 'core/utils/date-formatters';
// my sincere apologies to the next dev who has to refactor/debug this (⇀‸↼‶)
export default class Dashboard extends Component {
  @service store;
  @service version;

  chartLegend = [
    { key: 'entity_clients', label: 'entity clients' },
    { key: 'non_entity_clients', label: 'non-entity clients' },
  ];

  // RESPONSE
  @tracked startMonthTimestamp; // when user queries, updates to first month object of response
  @tracked endMonthTimestamp; // when user queries, updates to last month object of response
  @tracked queriedActivityResponse = null;
  // track params sent to /activity request
  @tracked activityQueryParams = {
    start: {}, // updates when user edits billing start month
    end: {}, // updates when user queries end dates via calendar widget
  };

  // SEARCH SELECT FILTERS
  get namespaceArray() {
    return this.getActivityResponse.byNamespace
      ? this.getActivityResponse.byNamespace.map((namespace) => ({
          name: namespace.label,
          id: namespace.label,
        }))
      : [];
  }
  @tracked selectedNamespace = null;
  @tracked selectedAuthMethod = null;
  @tracked authMethodOptions = [];

  // TEMPLATE VIEW
  @tracked noActivityData;
  @tracked showBillingStartModal = false;
  @tracked isLoadingQuery = false;
  @tracked errorObject = null;

  constructor() {
    super(...arguments);
    this.startMonthTimestamp = this.args.model.licenseStartTimestamp;
    this.endMonthTimestamp = this.args.model.currentDate;
    this.activityQueryParams.start.timestamp = this.args.model.licenseStartTimestamp;
    this.activityQueryParams.end.timestamp = this.args.model.currentDate;
    this.noActivityData = this.args.model.activity.id === 'no-data' ? true : false;
  }

  // returns text for empty state message if noActivityData
  get dateRangeMessage() {
    if (!this.startMonthTimestamp && !this.endMonthTimestamp) return null;
    const endMonth = isSameMonth(
      parseAPITimestamp(this.startMonthTimestamp),
      parseAPITimestamp(this.endMonthTimestamp)
    )
      ? ''
      : ` to ${parseAPITimestamp(this.endMonthTimestamp, 'MMMM yyyy')}`;
    // completes the message 'No data received from { dateRangeMessage }'
    return `from ${parseAPITimestamp(this.startMonthTimestamp, 'MMMM yyyy')}` + endMonth;
  }

  get versionText() {
    return this.version.isEnterprise
      ? {
          label: 'Billing start month',
          description:
            'This date comes from your license, and defines when client counting starts. Without this starting point, the data shown is not reliable.',
          title: 'No billing start date found',
          message:
            'In order to get the most from this data, please enter your billing period start month. This will ensure that the resulting data is accurate.',
        }
      : {
          label: 'Client counting start date',
          description:
            'This date is when client counting starts. Without this starting point, the data shown is not reliable.',
          title: 'No start date found',
          message:
            'In order to get the most from this data, please enter a start month above. Vault will calculate new clients starting from that month.',
        };
  }

  get isDateRange() {
    return !isSameMonth(
      parseAPITimestamp(this.getActivityResponse.startTime),
      parseAPITimestamp(this.getActivityResponse.endTime)
    );
  }

  get isCurrentMonth() {
    return (
      isSameMonth(
        parseAPITimestamp(this.getActivityResponse.startTime),
        parseAPITimestamp(this.args.model.currentDate)
      ) &&
      isSameMonth(
        parseAPITimestamp(this.getActivityResponse.endTime),
        parseAPITimestamp(this.args.model.currentDate)
      )
    );
  }

  get startTimeDiscrepancy() {
    // show banner if startTime returned from activity log (response) is after the queried startTime
    const activityStartDateObject = parseAPITimestamp(this.getActivityResponse.startTime);
    const queryStartDateObject = parseAPITimestamp(this.startMonthTimestamp);
    let message = 'You requested data from';
    if (this.startMonthTimestamp === this.args.model.licenseStartTimestamp && this.version.isEnterprise) {
      // on init, date is automatically pulled from license start date and user hasn't queried anything yet
      message = 'Your license start date is';
    }
    if (
      isAfter(activityStartDateObject, queryStartDateObject) &&
      !isSameMonth(activityStartDateObject, queryStartDateObject)
    ) {
      return `${message} ${parseAPITimestamp(this.startMonthTimestamp, 'MMMM yyyy')}.
        We only have data from ${parseAPITimestamp(this.getActivityResponse.startTime, 'MMMM yyyy')},
        and that is what is being shown here.`;
    } else {
      return null;
    }
  }

  get upgradeDuringActivity() {
    const versionHistory = this.args.model.versionHistory;
    if (!versionHistory || versionHistory.length === 0) {
      return null;
    }

    // filter for upgrade data of noteworthy upgrades (1.9 and/or 1.10)
    const upgradeVersionHistory = versionHistory.filter(
      ({ version }) => version.match('1.9') || version.match('1.10')
    );
    if (!upgradeVersionHistory || upgradeVersionHistory.length === 0) {
      return null;
    }

    const activityStart = parseAPITimestamp(this.getActivityResponse.startTime);
    const activityEnd = parseAPITimestamp(this.getActivityResponse.endTime);
    // filter and return all upgrades that happened within date range of queried activity
    const upgradesWithinData = upgradeVersionHistory.filter(({ timestampInstalled }) => {
      const upgradeDate = parseAPITimestamp(timestampInstalled);
      return isAfter(upgradeDate, activityStart) && isBefore(upgradeDate, activityEnd);
    });
    return upgradesWithinData.length === 0 ? null : upgradesWithinData;
  }

  get upgradeVersionAndDate() {
    if (!this.upgradeDuringActivity) return null;

    if (this.upgradeDuringActivity.length === 2) {
      const [firstUpgrade, secondUpgrade] = this.upgradeDuringActivity;
      const firstDate = parseAPITimestamp(firstUpgrade.timestampInstalled, 'MMM d, yyyy');
      const secondDate = parseAPITimestamp(secondUpgrade.timestampInstalled, 'MMM d, yyyy');
      return `Vault was upgraded to ${firstUpgrade.version} (${firstDate}) and ${secondUpgrade.version} (${secondDate}) during this time range.`;
    } else {
      const [upgrade] = this.upgradeDuringActivity;
      return `Vault was upgraded to ${upgrade.version} on ${parseAPITimestamp(
        upgrade.timestampInstalled,
        'MMM d, yyyy'
      )}.`;
    }
  }

  get upgradeExplanation() {
    if (!this.upgradeDuringActivity) return null;
    if (this.upgradeDuringActivity.length === 1) {
      const version = this.upgradeDuringActivity[0].version;
      if (version.match('1.9')) {
        return ' How we count clients changed in 1.9, so keep that in mind when looking at the data.';
      }
      if (version.match('1.10')) {
        return ' We added monthly breakdowns and mount level attribution starting in 1.10, so keep that in mind when looking at the data.';
      }
    }
    // return combined explanation if spans multiple upgrades
    return ' How we count clients changed in 1.9 and we added monthly breakdowns and mount level attribution starting in 1.10. Keep this in mind when looking at the data.';
  }

  get formattedStartDate() {
    if (!this.startMonthTimestamp) return null;
    return parseAPITimestamp(this.startMonthTimestamp, 'MMMM yyyy');
  }

  // GETTERS FOR RESPONSE & DATA

  // on init API response uses license start_date, getter updates when user queries dates
  get getActivityResponse() {
    return this.queriedActivityResponse || this.args.model.activity;
  }

  get byMonthActivityData() {
    if (this.selectedNamespace) {
      return this.filteredActivityByMonth;
    } else {
      return this.getActivityResponse?.byMonth;
    }
  }

  get hasAttributionData() {
    if (this.selectedAuthMethod) return false;
    if (this.selectedNamespace) {
      return this.authMethodOptions.length > 0;
    }
    return !!this.totalClientAttribution && this.totalUsageCounts && this.totalUsageCounts.clients !== 0;
  }

  // (object) top level TOTAL client counts for given date range
  get totalUsageCounts() {
    return this.selectedNamespace ? this.filteredActivityByNamespace : this.getActivityResponse.total;
  }

  // (object) single month new client data with total counts + array of namespace breakdown
  get newClientCounts() {
    return this.isDateRange ? null : this.byMonthActivityData[0]?.new_clients;
  }

  // total client data for horizontal bar chart in attribution component
  get totalClientAttribution() {
    if (this.selectedNamespace) {
      return this.filteredActivityByNamespace?.mounts || null;
    } else {
      return this.getActivityResponse?.byNamespace || null;
    }
  }

  // new client data for horizontal bar chart
  get newClientAttribution() {
    // new client attribution only available in a single, historical month (not a date range or current month)
    if (this.isDateRange || this.isCurrentMonth) return null;

    if (this.selectedNamespace) {
      return this.newClientCounts?.mounts || null;
    } else {
      return this.newClientCounts?.namespaces || null;
    }
  }

  get responseTimestamp() {
    return this.getActivityResponse.responseTimestamp;
  }

  // FILTERS
  get filteredActivityByNamespace() {
    const namespace = this.selectedNamespace;
    const auth = this.selectedAuthMethod;
    if (!namespace && !auth) {
      return this.getActivityResponse;
    }
    if (!auth) {
      return this.getActivityResponse.byNamespace.find((ns) => ns.label === namespace);
    }
    return this.getActivityResponse.byNamespace
      .find((ns) => ns.label === namespace)
      .mounts?.find((mount) => mount.label === auth);
  }

  get filteredActivityByMonth() {
    const namespace = this.selectedNamespace;
    const auth = this.selectedAuthMethod;
    if (!namespace && !auth) {
      return this.getActivityResponse?.byMonth;
    }
    const namespaceData = this.getActivityResponse?.byMonth
      .map((m) => m.namespaces_by_key[namespace])
      .filter((d) => d !== undefined);
    if (!auth) {
      return namespaceData.length === 0 ? null : namespaceData;
    }
    const mountData = namespaceData
      .map((namespace) => namespace.mounts_by_key[auth])
      .filter((d) => d !== undefined);
    return mountData.length === 0 ? null : mountData;
  }

  @action
  async handleClientActivityQuery({ dateType, monthIdx, year }) {
    this.showBillingStartModal = false;
    switch (dateType) {
      case 'cancel':
        return;
      case 'reset': // clicked 'Current billing period' in calendar widget -> reset to initial start/end dates
        this.activityQueryParams.start.timestamp = this.args.model.licenseStartTimestamp;
        this.activityQueryParams.end.timestamp = this.args.model.currentDate;
        break;
      case 'currentMonth': // clicked 'Current month' from calendar widget
        this.activityQueryParams.start.timestamp = this.args.model.currentDate;
        this.activityQueryParams.end.timestamp = this.args.model.currentDate;
        break;
      case 'startDate': // from "Edit billing start" modal
        this.activityQueryParams.start = { monthIdx, year };
        this.activityQueryParams.end.timestamp = this.args.model.currentDate;
        break;
      case 'endDate': // selected month and year from calendar widget
        this.activityQueryParams.end = { monthIdx, year };
        break;
      default:
        break;
    }
    try {
      this.isLoadingQuery = true;
      const response = await this.store.queryRecord('clients/activity', {
        start_time: this.activityQueryParams.start,
        end_time: this.activityQueryParams.end,
      });
      // preference for byMonth timestamps because those correspond to a user's query
      const { byMonth } = response;
      this.startMonthTimestamp = byMonth[0]?.timestamp || response.startTime;
      this.endMonthTimestamp = byMonth[byMonth.length - 1]?.timestamp || response.endTime;
      if (response.id === 'no-data') {
        this.noActivityData = true;
      } else {
        this.noActivityData = false;
        getStorage().setItem('vault:ui-inputted-start-date', this.startMonthTimestamp);
      }
      this.queriedActivityResponse = response;

      // reset search-select filters
      this.selectedNamespace = null;
      this.selectedAuthMethod = null;
      this.authMethodOptions = [];
    } catch (e) {
      this.errorObject = e;
      return e;
    } finally {
      this.isLoadingQuery = false;
    }
  }

  get hasMultipleMonthsData() {
    return this.byMonthActivityData && this.byMonthActivityData.length > 1;
  }

  @action
  selectNamespace([value]) {
    this.selectedNamespace = value;
    if (!value) {
      this.authMethodOptions = [];
      // on clear, also make sure auth method is cleared
      this.selectedAuthMethod = null;
    } else {
      // Side effect: set auth namespaces
      const mounts = this.filteredActivityByNamespace.mounts?.map((mount) => ({
        id: mount.label,
        name: mount.label,
      }));
      this.authMethodOptions = mounts;
    }
  }

  @action
  setAuthMethod([authMount]) {
    this.selectedAuthMethod = authMount;
  }

  // validation function sent to <DateDropdown> selecting 'endDate'
  @action
  isEndBeforeStart(selection) {
    let { start } = this.activityQueryParams;
    start = start?.timestamp ? parseAPITimestamp(start.timestamp) : new Date(start.year, start.monthIdx);
    return isBefore(selection, start) && !isSameMonth(start, selection)
      ? `End date must be after ${format(start, 'MMMM yyyy')}`
      : false;
  }
}
