import { CommonModule } from "@angular/common";
import { booleanAttribute, Component, input, Input } from "@angular/core";
import { Router, RouterModule } from "@angular/router";
import { BehaviorSubject, combineLatest, firstValueFrom, map, Observable, switchMap } from "rxjs";
import { filter } from "rxjs/operators";

import { CollectionService } from "@bitwarden/admin-console/common";
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums";
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service";
import {
  CipherViewLike,
  CipherViewLikeUtils,
} from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import {
  DialogService,
  IconButtonModule,
  ItemModule,
  MenuModule,
  ToastService,
} from "@bitwarden/components";
import { PasswordRepromptService } from "@bitwarden/vault";

import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service";
import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service";
import { AddEditQueryParams } from "../add-edit/add-edit.component";
import {
  AutofillConfirmationDialogComponent,
  AutofillConfirmationDialogResult,
} from "../autofill-confirmation-dialog/autofill-confirmation-dialog.component";

// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
  selector: "app-item-more-options",
  templateUrl: "./item-more-options.component.html",
  imports: [
    ItemModule,
    IconButtonModule,
    MenuModule,
    CommonModule,
    JslibModule,
    RouterModule,
    PremiumBadgeComponent,
  ],
  providers: [
    { provide: PremiumUpgradePromptService, useClass: BrowserPremiumUpgradePromptService },
  ],
})
export class ItemMoreOptionsComponent {
  private _cipher$ = new BehaviorSubject<CipherViewLike>({} as CipherViewLike);

  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @Input({
    required: true,
  })
  set cipher(c: CipherViewLike) {
    this._cipher$.next(c);
  }

  get cipher() {
    return this._cipher$.value;
  }

  /**
   * Flag to show the autofill menu option.
   * When true, the "Autofill" option appears in the menu.
   */
  readonly showAutofill = input(false, { transform: booleanAttribute });

  /**
   * Flag to show the view menu option.
   * When true, the "View" option appears in the menu.
   * Used when the primary action is autofill (so users can view without autofilling).
   */
  readonly showViewOption = input(false, { transform: booleanAttribute });

  protected autofillAllowed$ = this.vaultPopupAutofillService.autofillAllowed$;

  protected uriMatchStrategy$ = this.domainSettingsService.resolvedDefaultUriMatchStrategy$;

  /**
   * Observable that emits a boolean value indicating if the user is authorized to clone the cipher.
   * @protected
   */
  protected canClone$ = combineLatest([
    this._cipher$,
    this.restrictedItemTypesService.restricted$,
  ]).pipe(
    filter(([c]) => c != null),
    switchMap(([c, restrictedTypes]) => {
      // This will check for restrictions from org policies before allowing cloning.
      const isItemRestricted = restrictedTypes.some(
        (restrictType) => restrictType.cipherType === CipherViewLikeUtils.getType(c),
      );
      if (!isItemRestricted) {
        return this.cipherAuthorizationService.canCloneCipher$(c);
      }
      return new BehaviorSubject(false);
    }),
  );

  /** Observable Boolean dependent on the current user having access to an organization and editable collections */
  protected canAssignCollections$ = this.accountService.activeAccount$.pipe(
    getUserId,
    switchMap((userId) => {
      return combineLatest([
        this.organizationService.hasOrganizations(userId),
        this.collectionService.decryptedCollections$(userId),
      ]).pipe(
        map(([hasOrgs, collections]) => {
          const canEditCollections = collections.some((c) => !c.readOnly);
          return hasOrgs && canEditCollections;
        }),
      );
    }),
  );

  protected showArchive$: Observable<boolean> = this.cipherArchiveService.hasArchiveFlagEnabled$;

  protected canArchive$: Observable<boolean> = this.accountService.activeAccount$.pipe(
    getUserId,
    switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId)),
  );

  protected canDelete$ = this._cipher$.pipe(
    switchMap((cipher) => this.cipherAuthorizationService.canDeleteCipher$(cipher)),
  );

  constructor(
    private cipherService: CipherService,
    private passwordRepromptService: PasswordRepromptService,
    private toastService: ToastService,
    private dialogService: DialogService,
    private router: Router,
    private i18nService: I18nService,
    private vaultPopupAutofillService: VaultPopupAutofillService,
    private accountService: AccountService,
    private organizationService: OrganizationService,
    private cipherAuthorizationService: CipherAuthorizationService,
    private collectionService: CollectionService,
    private restrictedItemTypesService: RestrictedItemTypesService,
    private cipherArchiveService: CipherArchiveService,
    private domainSettingsService: DomainSettingsService,
  ) {}

  get canEdit() {
    return this.cipher.edit;
  }

  get canViewPassword() {
    return this.cipher.viewPassword;
  }

  get decryptionFailure() {
    return CipherViewLikeUtils.decryptionFailure(this.cipher);
  }

  /**
   * Determines if the cipher can be autofilled.
   */
  get canAutofill() {
    return ([CipherType.Login, CipherType.Card, CipherType.Identity] as CipherType[]).includes(
      CipherViewLikeUtils.getType(this.cipher),
    );
  }

  get isLogin() {
    return CipherViewLikeUtils.getType(this.cipher) === CipherType.Login;
  }

  get favoriteText() {
    return this.cipher.favorite ? "unfavorite" : "favorite";
  }

  async doAutofillAndSave() {
    const cipher = await this.cipherService.getFullCipherView(this.cipher);
    await this.vaultPopupAutofillService.doAutofillAndSave(cipher);
  }

  async doAutofill() {
    const cipher = await this.cipherService.getFullCipherView(this.cipher);

    if (!(await this.passwordRepromptService.passwordRepromptCheck(this.cipher))) {
      return;
    }

    const uris = cipher.login?.uris ?? [];
    const uriMatchStrategy = await firstValueFrom(this.uriMatchStrategy$);

    const showExactMatchDialog =
      uris.length === 0
        ? uriMatchStrategy === UriMatchStrategy.Exact
        : // all saved URIs are exact match
          uris.every((u) => (u.match ?? uriMatchStrategy) === UriMatchStrategy.Exact);

    if (showExactMatchDialog) {
      await this.dialogService.openSimpleDialog({
        title: { key: "cannotAutofill" },
        content: { key: "cannotAutofillExactMatch" },
        type: "info",
        acceptButtonText: { key: "okay" },
        cancelButtonText: null,
      });
      return;
    }

    const currentTab = await firstValueFrom(this.vaultPopupAutofillService.currentAutofillTab$);

    if (!currentTab?.url) {
      await this.dialogService.openSimpleDialog({
        title: { key: "error" },
        content: { key: "errorGettingAutoFillData" },
        type: "danger",
      });
      return;
    }

    const ref = AutofillConfirmationDialogComponent.open(this.dialogService, {
      data: {
        currentUrl: currentTab?.url || "",
        savedUrls: cipher.login?.uris?.filter((u) => u.uri).map((u) => u.uri!) ?? [],
        viewOnly: !this.cipher.edit,
      },
    });

    const result = await firstValueFrom(ref.closed);

    switch (result) {
      case AutofillConfirmationDialogResult.Canceled:
        return;
      case AutofillConfirmationDialogResult.AutofilledOnly:
        await this.vaultPopupAutofillService.doAutofill(cipher, true, true);
        return;
      case AutofillConfirmationDialogResult.AutofillAndUrlAdded:
        await this.vaultPopupAutofillService.doAutofillAndSave(cipher, false, true);
        return;
    }
  }

  async onView() {
    const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(this.cipher);
    if (!repromptPassed) {
      return;
    }
    await this.router.navigate(["/view-cipher"], {
      queryParams: { cipherId: this.cipher.id, type: CipherViewLikeUtils.getType(this.cipher) },
    });
  }

  /**
   * Toggles the favorite status of the cipher and updates it on the server.
   */
  async toggleFavorite() {
    const cipher = await this.cipherService.getFullCipherView(this.cipher);

    cipher.favorite = !cipher.favorite;
    const activeUserId = (await firstValueFrom(
      this.accountService.activeAccount$.pipe(map((a) => a?.id)),
    )) as UserId;

    await this.cipherService.updateWithServer(cipher, activeUserId);
    this.toastService.showToast({
      variant: "success",
      message: this.i18nService.t(
        cipher.favorite ? "itemAddedToFavorites" : "itemRemovedFromFavorites",
      ),
    });
  }

  /**
   * Navigate to the clone cipher page with the current cipher as the source.
   * A password reprompt is attempted if the cipher requires it.
   * A confirmation dialog is shown if the cipher has FIDO2 credentials.
   */
  async clone() {
    if (
      this.cipher.reprompt === CipherRepromptType.Password &&
      !(await this.passwordRepromptService.showPasswordPrompt())
    ) {
      return;
    }

    if (CipherViewLikeUtils.hasFido2Credentials(this.cipher)) {
      const confirmed = await this.dialogService.openSimpleDialog({
        title: { key: "passkeyNotCopied" },
        content: { key: "passkeyNotCopiedAlert" },
        type: "info",
      });

      if (!confirmed) {
        return;
      }
    }

    await this.router.navigate(["/clone-cipher"], {
      queryParams: {
        clone: true.toString(),
        cipherId: this.cipher.id,
        type: CipherViewLikeUtils.getType(this.cipher).toString(),
      } as AddEditQueryParams,
    });
  }

  /** Prompts for password when necessary then navigates to the assign collections route */
  async conditionallyNavigateToAssignCollections() {
    if (this.cipher.reprompt && !(await this.passwordRepromptService.showPasswordPrompt())) {
      return;
    }

    await this.router.navigate(["/assign-collections"], {
      queryParams: { cipherId: this.cipher.id },
    });
  }

  protected async edit() {
    if (this.cipher.reprompt && !(await this.passwordRepromptService.showPasswordPrompt())) {
      return;
    }

    await this.router.navigate(["/edit-cipher"], {
      queryParams: { cipherId: this.cipher.id, type: CipherViewLikeUtils.getType(this.cipher) },
    });
  }

  protected async delete() {
    const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(this.cipher);
    if (!repromptPassed) {
      return;
    }

    const confirmed = await this.dialogService.openSimpleDialog({
      title: { key: "deleteItem" },
      content: { key: "deleteItemConfirmation" },
      type: "warning",
    });

    if (!confirmed) {
      return;
    }

    const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));

    await this.cipherService.softDeleteWithServer(this.cipher.id as CipherId, activeUserId);

    this.toastService.showToast({
      variant: "success",
      message: this.i18nService.t("deletedItem"),
    });
  }

  async archive() {
    const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(this.cipher);
    if (!repromptPassed) {
      return;
    }

    const confirmed = await this.dialogService.openSimpleDialog({
      title: { key: "archiveItem" },
      content: { key: "archiveItemDialogContent" },
      acceptButtonText: { key: "archiveVerb" },
      type: "info",
    });

    if (!confirmed) {
      return;
    }

    const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
    await this.cipherArchiveService.archiveWithServer(this.cipher.id as CipherId, activeUserId);
    this.toastService.showToast({
      variant: "success",
      message: this.i18nService.t("itemWasSentToArchive"),
    });
  }
}
