import {
   isCancel as $isCancel,
   log as $log,
   password as $password,
   text as $text,
   select as $select,
} from "@clack/prompts";
import type { App } from "App";
import type { PasswordStrategy } from "auth/authenticate/strategies";
import { makeAppFromEnv } from "cli/commands/run";
import type { CliCommand } from "cli/types";
import { Argument } from "commander";
import { $console, isBun } from "bknd/utils";
import c from "picocolors";
import { withConfigOptions, type WithConfigOptions } from "cli/utils/options";

export const user: CliCommand = (program) => {
   withConfigOptions(program.command("user"))
      .description("create/update users, or generate a token (auth)")
      .addArgument(
         new Argument("<action>", "action to perform").choices(["create", "update", "token"]),
      )
      .action(action);
};

async function action(action: "create" | "update" | "token", options: WithConfigOptions) {
   const app = await makeAppFromEnv({
      config: options.config,
      dbUrl: options.dbUrl,
      server: "node",
   });

   if (!app.module.auth.enabled) {
      $log.error("Auth is not enabled");
      process.exit(1);
   }

   switch (action) {
      case "create":
         await create(app, options);
         break;
      case "update":
         await update(app, options);
         break;
      case "token":
         await token(app, options);
         break;
   }
}

async function create(app: App, options: any) {
   const auth = app.module.auth;
   let role: string | null = null;
   const roles = Object.keys(auth.config.roles ?? {});

   const strategy = auth.authenticator.strategy("password") as PasswordStrategy;
   if (roles.length > 0) {
      role = (await $select({
         message: "Select role",
         options: [
            {
               value: null,
               label: "<none>",
               hint: "No role will be assigned to the user",
            },
            ...roles.map((role) => ({
               value: role,
               label: role,
            })),
         ],
      })) as any;
      if ($isCancel(role)) process.exit(1);
   }

   if (!strategy) {
      $log.error("Password strategy not configured");
      process.exit(1);
   }

   const email = await $text({
      message: "Enter email",
      validate: (v) => {
         if (!v.includes("@")) {
            return "Invalid email";
         }
         return;
      },
   });
   if ($isCancel(email)) process.exit(1);

   const password = await $password({
      message: "Enter password",
      validate: (v) => {
         if (v.length < 3) {
            return "Invalid password";
         }
         return;
      },
   });
   if ($isCancel(password)) process.exit(1);

   try {
      const created = await app.createUser({
         email,
         password: await strategy.hash(password as string),
         role,
      });
      $log.success(`Created user: ${c.cyan(created.email)}`);
      process.exit(0);
   } catch (e) {
      $log.error("Error creating user");
      $console.error(e);
      process.exit(1);
   }
}

async function update(app: App, options: any) {
   const config = app.module.auth.toJSON(true);

   const email = (await $text({
      message: "Which user? Enter email",
      validate: (v) => {
         if (!v.includes("@")) {
            return "Invalid email";
         }
         return;
      },
   })) as string;
   if ($isCancel(email)) process.exit(1);

   const { data: user } = await app.modules
      .ctx()
      .em.repository(config.entity_name as "users")
      .findOne({ email });
   if (!user) {
      $log.error("User not found");
      process.exit(1);
   }
   $log.info(`User found: ${c.cyan(user.email)}`);

   const password = await $password({
      message: "New Password?",
      validate: (v) => {
         if (v.length < 3) {
            return "Invalid password";
         }
         return;
      },
   });
   if ($isCancel(password)) process.exit(1);

   if (await app.module.auth.changePassword(user.id, password)) {
      $log.success(`Updated user: ${c.cyan(user.email)}`);
      process.exit(0);
   } else {
      $log.error("Error updating user");
      process.exit(1);
   }
}

async function token(app: App, options: any) {
   if (isBun()) {
      $log.error("Please use node to generate tokens");
      process.exit(1);
   }

   const config = app.module.auth.toJSON(true);
   const users_entity = config.entity_name as "users";
   const em = app.modules.ctx().em;

   const email = (await $text({
      message: "Which user? Enter email",
      validate: (v) => {
         if (!v.includes("@")) {
            return "Invalid email";
         }
         return;
      },
   })) as string;
   if ($isCancel(email)) process.exit(1);

   const { data: user } = await em.repository(users_entity).findOne({ email });
   if (!user) {
      $log.error("User not found");
      process.exit(1);
   }
   $log.info(`User found: ${c.cyan(user.email)}`);

   // biome-ignore lint/suspicious/noConsoleLog:
   console.log(
      `\n${c.dim("Token:")}\n${c.yellow(await app.module.auth.authenticator.jwt(user))}\n`,
   );
   process.exit(0);
}
