// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { firstValueFrom } from "rxjs";

import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { AuthType } from "@bitwarden/common/tools/send/types/auth-type";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";

import { Response } from "../../../models/response";
import { CliUtils } from "../../../utils";
import { SendResponse } from "../models/send.response";

import { SendGetCommand } from "./get.command";

export class SendEditCommand {
  constructor(
    private sendService: SendService,
    private getCommand: SendGetCommand,
    private sendApiService: SendApiService,
    private accountProfileService: BillingAccountProfileStateService,
    private accountService: AccountService,
  ) {}

  async run(requestJson: string, cmdOptions: Record<string, any>): Promise<Response> {
    if (process.env.BW_SERVE !== "true" && (requestJson == null || requestJson === "")) {
      requestJson = await CliUtils.readStdin();
    }

    if (requestJson == null || requestJson === "") {
      return Response.badRequest("`requestJson` was not provided.");
    }

    let req: SendResponse = null;
    if (typeof requestJson !== "string") {
      req = requestJson;
      req.deletionDate = req.deletionDate == null ? null : new Date(req.deletionDate);
      req.expirationDate = req.expirationDate == null ? null : new Date(req.expirationDate);
    } else {
      try {
        const reqJson = Buffer.from(requestJson, "base64").toString();
        req = SendResponse.fromJson(reqJson);
        // FIXME: Remove when updating file. Eslint update
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
      } catch (e) {
        return Response.badRequest("Error parsing the encoded request data.");
      }
    }

    const normalizedOptions = new Options(cmdOptions);
    req.id = normalizedOptions.itemId || req.id;
    if (normalizedOptions.emails) {
      req.emails = normalizedOptions.emails;
    }
    if (normalizedOptions.password) {
      req.password = normalizedOptions.password;
    }
    if (req.password && (typeof req.password !== "string" || req.password === "")) {
      req.password = undefined;
    }

    // Infer authType based on emails/password (mutually exclusive)
    const hasEmails = req.emails != null && req.emails.length > 0;
    const hasPassword = req.password != null && req.password.trim() !== "";

    if (hasEmails && hasPassword) {
      return Response.badRequest("--password and --emails are mutually exclusive.");
    }

    if (hasEmails) {
      req.authType = AuthType.Email;
    } else if (hasPassword) {
      req.authType = AuthType.Password;
    } else {
      req.authType = AuthType.None;
    }

    if (!req.id) {
      return Response.error("`itemid` was not provided.");
    }

    req.id = req.id.toLowerCase();
    const send = await this.sendService.getFromState(req.id);

    if (send == null) {
      return Response.notFound();
    }

    if (send.type !== req.type) {
      return Response.badRequest("Cannot change a Send's type");
    }

    const account = await firstValueFrom(this.accountService.activeAccount$);
    const canAccessPremium = await firstValueFrom(
      this.accountProfileService.hasPremiumFromAnySource$(account.id),
    );
    if (send.type === SendType.File && !canAccessPremium) {
      return Response.error("Premium status is required to use this feature.");
    }

    const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
    let sendView = await send.decrypt(activeUserId);
    sendView = SendResponse.toView(req, sendView);

    try {
      const [encSend, encFileData] = await this.sendService.encrypt(sendView, null, req.password);
      // Forward the plaintext  password (null when preserving an existing password) so the SDK path can
      // derive the send password over the key it generates; the legacy path ignores it.
      await this.sendApiService.save([encSend, encFileData], req.password);
    } catch (e) {
      return Response.error(e);
    }

    return await this.getCommand.run(send.id, {});
  }
}

class Options {
  itemId: string;
  password: string;
  emails: string[];

  constructor(passedOptions: Record<string, any>) {
    this.itemId = passedOptions?.itemId || passedOptions?.itemid;
    this.password = passedOptions.password;
    this.emails = passedOptions.emails;
  }
}
