import {
  AfterViewInit,
  ChangeDetectorRef,
  Component,
  Inject,
  signal,
  Signal,
  TemplateRef,
  viewChild,
} from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { Router } from "@angular/router";
import {
  combineLatest,
  firstValueFrom,
  map,
  Observable,
  of,
  shareReplay,
  startWith,
  switchMap,
  tap,
} from "rxjs";

import { AutomaticUserConfirmationService } from "@bitwarden/auto-confirm";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { getById } from "@bitwarden/common/platform/misc";
import {
  DIALOG_DATA,
  DialogConfig,
  DialogRef,
  DialogService,
  ToastService,
} from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";

import { SharedModule } from "../../../../shared";
import { AutoConfirmPolicyEditComponent } from "../policy-edit-definitions/auto-confirm-policy.component";
import {
  PolicyEditDialogComponent,
  PolicyEditDialogData,
  PolicyEditDialogResult,
} from "../policy-edit-dialog.component";

import { MultiStepSubmit } from "./models";

export type AutoConfirmPolicyDialogData = PolicyEditDialogData & {
  firstTimeDialog?: boolean;
};

/**
 * Custom policy dialog component for Auto-Confirm policy.
 * Satisfies the PolicyDialogComponent interface structurally
 * via its static open() function.
 */
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
  templateUrl: "auto-confirm-edit-policy-dialog.component.html",
  imports: [SharedModule],
})
export class AutoConfirmPolicyDialogComponent
  extends PolicyEditDialogComponent
  implements AfterViewInit
{
  policyType = PolicyType;

  protected readonly firstTimeDialog = signal(false);
  protected readonly currentStep = signal(0);
  protected multiStepSubmit: Observable<MultiStepSubmit[]> = of([]);
  protected autoConfirmEnabled$: Observable<boolean> = this.accountService.activeAccount$.pipe(
    getUserId,
    switchMap((userId) => this.policyService.policies$(userId)),
    map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm)?.enabled ?? false),
  );
  // Users with manage policies custom permission should not see the dialog's second step since
  // they do not have permission to configure the setting. This will only allow them to configure
  // the policy.
  protected managePoliciesOnly$: Observable<boolean> = this.accountService.activeAccount$.pipe(
    getUserId,
    switchMap((userId) => this.organizationService.organizations$(userId)),
    getById(this.data.organizationId),
    map((organization) => (!organization?.isAdmin && organization?.canManagePolicies) ?? false),
  );

  private readonly submitPolicy: Signal<TemplateRef<unknown> | undefined> = viewChild("step0");
  private readonly openExtension: Signal<TemplateRef<unknown> | undefined> = viewChild("step1");

  private readonly submitPolicyTitle: Signal<TemplateRef<unknown> | undefined> =
    viewChild("step0Title");
  private readonly openExtensionTitle: Signal<TemplateRef<unknown> | undefined> =
    viewChild("step1Title");

  override policyComponent: AutoConfirmPolicyEditComponent | undefined;

  constructor(
    @Inject(DIALOG_DATA) protected data: AutoConfirmPolicyDialogData,
    accountService: AccountService,
    policyApiService: PolicyApiServiceAbstraction,
    i18nService: I18nService,
    cdr: ChangeDetectorRef,
    formBuilder: FormBuilder,
    dialogRef: DialogRef<PolicyEditDialogResult>,
    toastService: ToastService,
    keyService: KeyService,
    private organizationService: OrganizationService,
    private policyService: PolicyService,
    private router: Router,
    private autoConfirmService: AutomaticUserConfirmationService,
  ) {
    super(
      data,
      accountService,
      policyApiService,
      i18nService,
      cdr,
      formBuilder,
      dialogRef,
      toastService,
      keyService,
    );

    this.firstTimeDialog.set(data.firstTimeDialog ?? false);
  }

  /**
   * Instantiates the child policy component and inserts it into the view.
   */
  async ngAfterViewInit() {
    await super.ngAfterViewInit();

    if (this.policyComponent) {
      this.saveDisabled$ = combineLatest([
        this.autoConfirmEnabled$,
        this.policyComponent.enabled.valueChanges.pipe(
          startWith(this.policyComponent.enabled.value),
        ),
      ]).pipe(map(([policyEnabled, value]) => !policyEnabled && !value));
    }

    this.multiStepSubmit = this.accountService.activeAccount$.pipe(
      getUserId,
      switchMap((userId) => this.policyService.policies$(userId)),
      map((policies) => policies.find((p) => p.type === PolicyType.SingleOrg)?.enabled ?? false),
      tap((singleOrgPolicyEnabled) =>
        this.policyComponent?.setSingleOrgEnabled(singleOrgPolicyEnabled),
      ),
      switchMap((singleOrgPolicyEnabled) => this.buildMultiStepSubmit(singleOrgPolicyEnabled)),
      shareReplay({ bufferSize: 1, refCount: true }),
    );
  }

  private buildMultiStepSubmit(singleOrgPolicyEnabled: boolean): Observable<MultiStepSubmit[]> {
    return this.managePoliciesOnly$.pipe(
      map((managePoliciesOnly) => {
        const submitSteps = [
          {
            sideEffect: () => this.handleSubmit(singleOrgPolicyEnabled ?? false),
            footerContent: this.submitPolicy,
            titleContent: this.submitPolicyTitle,
          },
        ];

        if (!managePoliciesOnly) {
          submitSteps.push({
            sideEffect: () => this.openBrowserExtension(),
            footerContent: this.openExtension,
            titleContent: this.openExtensionTitle,
          });
        }
        return submitSteps;
      }),
    );
  }

  private async handleSubmit(singleOrgEnabled: boolean) {
    const enabledSingleOrgDuringAction = !singleOrgEnabled;

    if (enabledSingleOrgDuringAction) {
      await this.setSingleOrgPolicy(true);
    }

    try {
      await this.submitAutoConfirm();
    } catch (error) {
      // Roll back SingleOrg if we enabled it during this action
      if (enabledSingleOrgDuringAction) {
        await this.setSingleOrgPolicy(false);
      }
      throw error;
    }
  }

  /**
   *  Triggers policy submission for auto confirm.
   *  @returns boolean: true if multi-submit workflow should continue, false otherwise.
   */
  private async submitAutoConfirm() {
    if (!this.policyComponent) {
      throw new Error("PolicyComponent not initialized.");
    }

    const autoConfirmRequest = await this.policyComponent.buildRequest();

    await this.policyApiService.putPolicyVNext(this.data.organizationId, this.data.policy.type, {
      policy: autoConfirmRequest,
    });

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

    const currentAutoConfirmState = await firstValueFrom(
      this.autoConfirmService.configuration$(userId),
    );

    await this.autoConfirmService.upsert(userId, {
      ...currentAutoConfirmState,
      showSetupDialog: false,
    });

    this.toastService.showToast({
      variant: "success",
      message: this.i18nService.t("editedPolicyId", this.i18nService.t(this.data.policy.name)),
    });

    if (!this.policyComponent.enabled.value) {
      this.dialogRef.close("saved");
    }
  }

  private async setSingleOrgPolicy(enabled: boolean): Promise<void> {
    const singleOrgRequest: PolicyRequest = {
      enabled,
      data: null,
    };

    await this.policyApiService.putPolicyVNext(this.data.organizationId, PolicyType.SingleOrg, {
      policy: singleOrgRequest,
    });
  }

  private async openBrowserExtension() {
    await this.router.navigate(["/browser-extension-prompt"], {
      queryParams: { url: "AutoConfirm" },
    });
  }

  submit = async () => {
    if (!this.policyComponent) {
      throw new Error("PolicyComponent not initialized.");
    }

    if ((await this.policyComponent.confirm()) == false) {
      this.dialogRef.close();
      return;
    }

    try {
      const multiStepSubmit = await firstValueFrom(this.multiStepSubmit);
      const sideEffect = multiStepSubmit[this.currentStep()].sideEffect;
      if (sideEffect) {
        await sideEffect();
      }

      if (this.currentStep() === multiStepSubmit.length - 1) {
        this.dialogRef.close("saved");
        return;
      }

      this.currentStep.update((value) => value + 1);
      this.policyComponent.setStep(this.currentStep());
    } catch (error: any) {
      this.toastService.showToast({
        variant: "error",
        message: error.message,
      });
    }
  };

  static open = (
    dialogService: DialogService,
    config: DialogConfig<AutoConfirmPolicyDialogData>,
  ) => {
    return dialogService.openDrawer<PolicyEditDialogResult, AutoConfirmPolicyDialogData>(
      AutoConfirmPolicyDialogComponent,
      config,
    );
  };
}
