/*
 *  This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
 *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
 */

/*
Information (from the database) about authors of shares,
and what shares were authored by a given account.
*/

import { callback2 } from "smc-util/async-utils";
import { cmp, endswith, is_valid_uuid_string, meta_file } from "smc-util/misc";
import { Author } from "smc-webapp/share/types";
import { Database } from "./types";

export class AuthorInfo {
  private database: Database;

  constructor(database: Database) {
    this.database = database;
  }

  public async get_authors(
    project_id: string,
    // path can be a single path or an array of paths;
    // if give a single path, also automatically includes
    // known aux files (this is just for ipynb).
    path: string | string[]
  ): Promise<Author[]> {
    if (!is_valid_uuid_string(project_id)) {
      throw Error(`project_id=${project_id} must be a valid uuid string`);
    }
    // Determine the paths to check in the database:
    const account_ids: string[] = [];
    const known_account_ids: Set<string> = new Set();
    let paths: string[];
    if (typeof path == "string") {
      paths = [path];
      if (endswith(path, ".ipynb")) {
        paths.push(meta_file(path, "jupyter2"));
        paths.push(meta_file(path, "jupyter"));
      }
    } else {
      paths = path;
    }

    const collabs = new Set(
      await callback2(this.database.get_collaborators.bind(this.database), {
        project_id,
      })
    );

    // Get accounts that have edited these paths, if they have edited them using sync.
    for (const path of paths) {
      const id: string = this.database.sha1(project_id, path);
      const result = await callback2(this.database._query, {
        query: `SELECT users FROM syncstrings WHERE string_id='${id}'`,
      });
      if (result == null || result.rowCount < 1) continue;
      for (const account_id of result.rows[0].users) {
        if (
          account_id != project_id &&
          !known_account_ids.has(account_id) &&
          collabs.has(account_id)
        ) {
          account_ids.push(account_id);
          known_account_ids.add(account_id);
        }
      }
    }

    // If no accounts, use the project collaborators as a fallback.
    if (account_ids.length === 0) {
      const result = await callback2(this.database._query, {
        query: `SELECT jsonb_object_keys(users) FROM projects where project_id='${project_id}'`,
      });
      if (result != null && result.rowCount >= 1) {
        for (const v of result.rows) {
          account_ids.push(v.jsonb_object_keys);
        }
      }
    }

    // Get usernames for the accounts
    const authors: Author[] = [];
    const names = await callback2(this.database.get_usernames, {
      account_ids,
      cache_time_s: 60 * 5,
    });
    for (const account_id in names) {
      // todo really need to sort by last name
      const { first_name, last_name } = names[account_id];
      const name = `${first_name} ${last_name}`;
      authors.push({ name, account_id });
    }

    // Sort by last name
    authors.sort((a, b) =>
      cmp(names[a.account_id].last_name, names[b.account_id].last_name)
    );
    return authors;
  }

  public async get_username(account_id: string): Promise<string> {
    const names = await callback2(this.database.get_usernames, {
      account_ids: [account_id],
      cache_time_s: 60 * 5,
    });
    const { first_name, last_name } = names[account_id];
    return `${first_name} ${last_name}`;
  }

  public async get_shares(account_id: string): Promise<string[]> {
    // Returns the id's of all public paths for which account_id
    // is a collaborator on the project that has actively used the project.
    // It would be more useful
    // to additionally filter using the syncstrings table for documents
    // that account_id actually edited, but that's a lot harder.
    // We sort from most recently saved back.
    if (!is_valid_uuid_string(account_id)) {
      throw Error(`account_id=${account_id} must be a valid uuid string`);
    }
    const query = `SELECT public_paths.id FROM public_paths, projects WHERE public_paths.project_id = projects.project_id AND projects.last_active ? '${account_id}' AND projects.users ? '${account_id}' AND (public_paths.unlisted is null OR public_paths.unlisted = false) AND (public_paths.disabled is null OR public_paths.disabled = false) ORDER BY public_paths.last_edited DESC`;
    const result = await callback2(this.database._query, { query });
    const ids: string[] = [];
    if (result == null) return [];
    for (const x of result.rows) {
      ids.push(x.id);
    }
    return ids;
  }
}
