import { flags } from "@oclif/command";
import { ux } from "cli-ux";
import { capitalize } from "lodash";
import { FindConditions } from "typeorm";

import ManageCommand from "../ManageCommand";

import { Input } from "@oclif/parser/lib/flags";
import { User } from "@server/entities";

type UserFlags = (typeof UserCommand)["flags"] extends Input<infer F>
  ? F
  : never;

type Action = "show" | "create" | "update" | "delete";

// tslint:disable:no-shadowed-variable

export default class UserCommand extends ManageCommand {
  static description = "Manage users";

  static flags = {
    show: flags.boolean({
      char: "s",
      exclusive: ["create", "update", "delete"],
      description: "Show user(s)",
    }),
    create: flags.boolean({
      char: "c",
      exclusive: ["update", "delete", "id"],
      dependsOn: ["username"],
      description: "Create a new user"
    }),
    update: flags.boolean({
      char: "u",
      exclusive: ["create", "delete"],
      description: "Update an existing user (by --id or --username)"
    }),
    delete: flags.boolean({
      char: "d",
      exclusive: ["create", "update"],
      description: "Delete a user (by --id or --username)"
    }),
    id: flags.integer({
      description: "The id of the user to update or delete",
    }),
    username: flags.string({
      description: "The username of the user to create or update"
    }),
    name: flags.string({
      description: "The name of the user, when creating or updating"
    }),
    passwordPrompt: flags.boolean({
      char: "p",
      description:
        "Prompts for the password of the user when creating or updating"
    })
  };

  getAction(flags: UserFlags): Action {
    if (flags.show) return "show";
    else if (flags.create) return "create";
    else if (flags.update) return "update";
    else if (flags.delete) return "delete";
    else {
      this.error("Must specify an action (--show, --create, --update, --delete)", {
        exit: false
      });
      return this._help();
    }
  }

  getFindConditions(flags: UserFlags, action: Action): FindConditions<User> {
    if (flags.id != null) {
      return { id: flags.id };
    } else if (flags.username) {
      return { username: flags.username };
    } else {
      this.error(`Must specify either --id or --username to ${action}`, {
        exit: false
      });
      return this._help();
    }
  }

  async getOrDeleteUser(flags: UserFlags, action: Action): Promise<User | never> {
    if (action === "create") {
      return this.database.users.create();
    } else {
      const findConditions = this.getFindConditions(flags, action);
      if (action === "delete") {
        const result = await this.database.users.delete(findConditions);
        this.log(`Deleted user: `, result);
        return this.exit();
      } else {
        const user = await this.database.users.findOneUser(findConditions);
        if (!user) {
          return this.error(`The specified user does not exist`);
        }
        return user;
      }
    }
  }

  async run() {
    const parseResult = this.parse(UserCommand);

    const flags = parseResult.flags;
    const action = this.getAction(flags);

    await this.connect();

    if (flags.show) {
      let whereClause: FindConditions<User> = {};
      if (flags.id) {
        whereClause.id = flags.id;
      }
      if (flags.username) {
        whereClause.username = flags.username;
      }
      const users = await this.database.users.find({ where: whereClause });
      if (users.length == 0) {
        this.error("No users found");
      }
      this.log("Users: ")
      for (const user of users) {
        this.log(JSON.stringify(user.toJSON()));
      }
      return;
    }

    const user = await this.getOrDeleteUser(flags, action);

    if (flags.username && (flags.create || flags.id)) {
      user.username = flags.username;
    }
    if (flags.name) {
      user.name = flags.name;
    }
    if (flags.passwordPrompt || flags.create) {
      const password = await ux.prompt("Enter a password to assign the user", {
        type: "hide"
      });
      await user.setPassword(password);
    }

    try {
      await this.database.users.save(user);
      this.log(`${capitalize(action)}d user id ${user.id} (${user.username})`);
    } catch (e) {
      throw e;
    }
  }
}