import {createFormatter} from 'app/common/ValueFormatter';
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
import {ExportData, exportSection, exportTable, Filter} from 'app/server/lib/Export';
import * as bluebird from 'bluebird';
import * as csv from 'csv';
import * as express from 'express';
import * as log from 'app/server/lib/log';
import * as contentDisposition from 'content-disposition';

export interface DownloadCSVOptions {
  filename: string;
  tableId: string;
  viewSectionId: number | undefined;
  filters: Filter[];
  sortOrder: number[];
}

// promisify csv
bluebird.promisifyAll(csv);

/**
 * Converts `activeDoc` to a CSV and sends the converted data through `res`.
 */
export async function downloadCSV(activeDoc: ActiveDoc, req: express.Request,
                                  res: express.Response, options: DownloadCSVOptions) {
  log.info('Generating .csv file...');
  const {filename, tableId, viewSectionId, filters, sortOrder} = options;

  try {
    const data = viewSectionId ?
      await makeCSVFromViewSection(activeDoc, viewSectionId, sortOrder, filters, req) :
      await makeCSVFromTable(activeDoc, tableId, req);
    res.set('Content-Type', 'text/csv');
    res.setHeader('Content-Disposition', contentDisposition(filename + '.csv'));
    res.send(data);
  } catch (err) {
    log.error("Exporting to CSV has failed. Request url: %s", req.url, err);
    const errHtml =
      `<!doctype html>
<html>
  <body>There was an unexpected error while generating a csv file.</body>
</html>
`;
    res.status(400).send(errHtml);
  }
}

/**
 * Returns a csv stream of a view section that can be transformed or parsed.
 *
 * See https://github.com/wdavidw/node-csv for API details.
 *
 * @param {Object} activeDoc - the activeDoc that the table being converted belongs to.
 * @param {Integer} viewSectionId - id of the viewsection to export.
 * @param {Integer[]} activeSortOrder (optional) - overriding sort order.
 * @param {Filter[]} filters (optional) - filters defined from ui.
 * @return {Promise<string>} Promise for the resulting CSV.
 */
export async function makeCSVFromViewSection(
  activeDoc: ActiveDoc,
  viewSectionId: number,
  sortOrder: number[],
  filters: Filter[],
  req: express.Request) {

  const data = await exportSection(activeDoc, viewSectionId, sortOrder, filters, req);
  const file = convertToCsv(data);
  return file;
}

/**
 * Returns a csv stream of a table that can be transformed or parsed.
 *
 * @param {Object} activeDoc - the activeDoc that the table being converted belongs to.
 * @param {Integer} tableId - id of the table to export.
 * @return {Promise<string>} Promise for the resulting CSV.
 */
export async function makeCSVFromTable(
  activeDoc: ActiveDoc,
  tableId: string,
  req: express.Request) {

  if (!activeDoc.docData) {
    throw new Error('No docData in active document');
  }

  // Look up the table to make a CSV from.
  const tables = activeDoc.docData.getTable('_grist_Tables')!;
  const tableRef = tables.findRow('tableId', tableId);

  const data = await exportTable(activeDoc, tableRef, req);
  const file = convertToCsv(data);
  return file;
}

function convertToCsv({
  rowIds,
  access,
  columns: viewColumns,
  docSettings
}: ExportData) {

  // create formatters for columns
  const formatters = viewColumns.map(col => createFormatter(col.type, col.widgetOptions, docSettings));
  // Arrange the data into a row-indexed matrix, starting with column headers.
  const csvMatrix = [viewColumns.map(col => col.label)];
  // populate all the rows with values as strings
  rowIds.forEach(row => {
    csvMatrix.push(access.map((getter, c) => formatters[c].formatAny(getter(row))));
  });
  return csv.stringifyAsync(csvMatrix);
}
