/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: MPL-2.0
 */

/* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { inject as controller } from '@ember/controller';
import { inject as service } from '@ember/service';
import { scheduleOnce } from '@ember/runloop';
import { task } from 'ember-concurrency';
import intersection from 'lodash.intersection';
import {
  serialize,
  deserializedQueryParam as selection,
} from 'nomad-ui/utils/qp-serialize';

import EmberObject, { computed } from '@ember/object';
import { alias } from '@ember/object/computed';
import Searchable from 'nomad-ui/mixins/searchable';
import classic from 'ember-classic-decorator';

export default class OptimizeController extends Controller {
  @controller('optimize/summary') summaryController;
  @service router;
  @service system;

  queryParams = [
    {
      searchTerm: 'search',
    },
    {
      qpNamespace: 'namespacefilter',
    },
    {
      qpType: 'type',
    },
    {
      qpStatus: 'status',
    },
    {
      qpDatacenter: 'dc',
    },
    {
      qpPrefix: 'prefix',
    },
  ];

  constructor() {
    super(...arguments);

    this.summarySearch = RecommendationSummarySearch.create({
      dataSource: this,
    });
  }

  get namespaces() {
    return this.model.namespaces;
  }

  get summaries() {
    return this.model.summaries;
  }

  @tracked searchTerm = '';

  @tracked qpType = '';
  @tracked qpStatus = '';
  @tracked qpDatacenter = '';
  @tracked qpPrefix = '';
  @tracked qpNamespace = '*';

  @selection('qpType') selectionType;
  @selection('qpStatus') selectionStatus;
  @selection('qpDatacenter') selectionDatacenter;
  @selection('qpPrefix') selectionPrefix;

  get optionsNamespaces() {
    const availableNamespaces = this.namespaces.map((namespace) => ({
      key: namespace.name,
      label: namespace.name,
    }));

    availableNamespaces.unshift({
      key: '*',
      label: 'All (*)',
    });

    // Unset the namespace selection if it was server-side deleted
    if (!availableNamespaces.mapBy('key').includes(this.qpNamespace)) {
      scheduleOnce('actions', () => {
        // eslint-disable-next-line ember/no-side-effects
        this.qpNamespace = '*';
      });
    }

    return availableNamespaces;
  }

  optionsType = [
    { key: 'service', label: 'Service' },
    { key: 'system', label: 'System' },
  ];

  optionsStatus = [
    { key: 'pending', label: 'Pending' },
    { key: 'running', label: 'Running' },
    { key: 'dead', label: 'Dead' },
  ];

  get optionsDatacenter() {
    const flatten = (acc, val) => acc.concat(val);
    const allDatacenters = new Set(
      this.summaries.mapBy('job.datacenters').reduce(flatten, [])
    );

    // Remove any invalid datacenters from the query param/selection
    const availableDatacenters = Array.from(allDatacenters).compact();
    scheduleOnce('actions', () => {
      // eslint-disable-next-line ember/no-side-effects
      this.qpDatacenter = serialize(
        intersection(availableDatacenters, this.selectionDatacenter)
      );
    });

    return availableDatacenters.sort().map((dc) => ({ key: dc, label: dc }));
  }

  get optionsPrefix() {
    // A prefix is defined as the start of a job name up to the first - or .
    // ex: mktg-analytics -> mktg, ds.supermodel.classifier -> ds
    const hasPrefix = /.[-._]/;

    // Collect and count all the prefixes
    const allNames = this.summaries.mapBy('job.name');
    const nameHistogram = allNames.reduce((hist, name) => {
      if (hasPrefix.test(name)) {
        const prefix = name.match(/(.+?)[-._]/)[1];
        hist[prefix] = hist[prefix] ? hist[prefix] + 1 : 1;
      }
      return hist;
    }, {});

    // Convert to an array
    const nameTable = Object.keys(nameHistogram).map((key) => ({
      prefix: key,
      count: nameHistogram[key],
    }));

    // Only consider prefixes that match more than one name
    const prefixes = nameTable.filter((name) => name.count > 1);

    // Remove any invalid prefixes from the query param/selection
    const availablePrefixes = prefixes.mapBy('prefix');
    scheduleOnce('actions', () => {
      // eslint-disable-next-line ember/no-side-effects
      this.qpPrefix = serialize(
        intersection(availablePrefixes, this.selectionPrefix)
      );
    });

    // Sort, format, and include the count in the label
    return prefixes.sortBy('prefix').map((name) => ({
      key: name.prefix,
      label: `${name.prefix} (${name.count})`,
    }));
  }

  get filteredSummaries() {
    const {
      selectionType: types,
      selectionStatus: statuses,
      selectionDatacenter: datacenters,
      selectionPrefix: prefixes,
    } = this;

    // A summary’s job must match ALL filter facets, but it can match ANY selection within a facet
    // Always return early to prevent unnecessary facet predicates.
    return this.summarySearch.listSearched.filter((summary) => {
      const job = summary.get('job');

      if (job.isDestroying) {
        return false;
      }

      if (
        this.qpNamespace !== '*' &&
        job.get('namespace.name') !== this.qpNamespace
      ) {
        return false;
      }

      if (types.length && !types.includes(job.get('displayType.type'))) {
        return false;
      }

      if (statuses.length && !statuses.includes(job.get('status'))) {
        return false;
      }

      if (
        datacenters.length &&
        !job.get('datacenters').find((dc) => datacenters.includes(dc))
      ) {
        return false;
      }

      const name = job.get('name');
      if (
        prefixes.length &&
        !prefixes.find((prefix) => name.startsWith(prefix))
      ) {
        return false;
      }

      return true;
    });
  }

  get activeRecommendationSummary() {
    if (this.router.currentRouteName === 'optimize.summary') {
      return this.summaryController.model;
    } else {
      return undefined;
    }
  }

  // This is a task because the accordion uses timeouts for animation
  // eslint-disable-next-line require-yield
  @(task(function* () {
    const currentSummaryIndex = this.filteredSummaries.indexOf(
      this.activeRecommendationSummary
    );
    const nextSummary = this.filteredSummaries.objectAt(
      currentSummaryIndex + 1
    );

    if (nextSummary) {
      this.transitionToSummary(nextSummary);
    } else {
      this.send('reachedEnd');
    }
  }).drop())
  proceed;

  @action
  transitionToSummary(summary) {
    this.transitionToRoute('optimize.summary', summary.slug, {
      queryParams: { jobNamespace: summary.jobNamespace },
    });
  }

  @action
  setFacetQueryParam(queryParam, selection) {
    this[queryParam] = serialize(selection);
    this.syncActiveSummary();
  }

  @action
  syncActiveSummary() {
    scheduleOnce('actions', () => {
      if (
        !this.activeRecommendationSummary ||
        !this.filteredSummaries.includes(this.activeRecommendationSummary)
      ) {
        const firstFilteredSummary = this.filteredSummaries.objectAt(0);

        if (firstFilteredSummary) {
          this.transitionToSummary(firstFilteredSummary);
        } else {
          this.transitionToRoute('optimize');
        }
      }
    });
  }
}

@classic
class RecommendationSummarySearch extends EmberObject.extend(Searchable) {
  @computed
  get fuzzySearchProps() {
    return ['slug'];
  }

  @alias('dataSource.summaries') listToSearch;
  @alias('dataSource.searchTerm') searchTerm;

  exactMatchEnabled = false;
  fuzzySearchEnabled = true;
  includeFuzzySearchMatches = true;
}
