import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import {
  BehaviorSubject,
  combineLatest,
  filter,
  firstValueFrom,
  merge,
  Observable,
  of,
  shareReplay,
  Subject,
  switchMap,
  take,
  takeUntil,
  tap,
  withLatestFrom,
} from "rxjs";

import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { CommandDefinition, MessageListener } from "@bitwarden/messaging";
import { UserId } from "@bitwarden/user-core";
import { SubscriberBillingClient } from "@bitwarden/web-vault/app/billing/clients";
import {
  DisplayAccountCreditComponent,
  DisplayBillingAddressComponent,
  DisplayPaymentMethodComponent,
} from "@bitwarden/web-vault/app/billing/payment/components";
import {
  BillingAddress,
  MaskedPaymentMethod,
} from "@bitwarden/web-vault/app/billing/payment/types";
import {
  BitwardenSubscriber,
  mapProviderToSubscriber,
} from "@bitwarden/web-vault/app/billing/types";
import { TaxIdWarningType } from "@bitwarden/web-vault/app/billing/warnings/types";
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
import { SharedModule } from "@bitwarden/web-vault/app/shared";

import { ProviderWarningsService } from "../warnings/services";

type View = {
  activeUserId: UserId;
  provider: BitwardenSubscriber;
  paymentMethod: MaskedPaymentMethod | null;
  billingAddress: BillingAddress | null;
  credit: number | null;
  taxIdWarning: TaxIdWarningType | null;
};

const BANK_ACCOUNT_VERIFIED_COMMAND = new CommandDefinition<{
  providerId: string;
  adminId: string;
}>("providerBankAccountVerified");

// 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: "./provider-payment-details.component.html",
  imports: [
    DisplayAccountCreditComponent,
    DisplayBillingAddressComponent,
    DisplayPaymentMethodComponent,
    HeaderModule,
    SharedModule,
  ],
})
export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy {
  private viewState$ = new BehaviorSubject<View | null>(null);

  private provider$ = combineLatest([
    this.activatedRoute.params,
    this.accountService.activeAccount$.pipe(getUserId),
  ]).pipe(
    switchMap(([{ providerId }, userId]) => this.providerService.get$(providerId, userId)),
    filter((provider) => provider != null),
  );

  private load$: Observable<View> = this.provider$.pipe(
    mapProviderToSubscriber,
    switchMap(async (provider) => {
      const getTaxIdWarning = firstValueFrom(
        this.providerWarningsService.getTaxIdWarning$(provider.data as Provider),
      );
      const getActiveUserId = firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));

      const [activeUserId, paymentMethod, billingAddress, credit, taxIdWarning] = await Promise.all(
        [
          getActiveUserId,
          this.billingClient.getPaymentMethod(provider),
          this.billingClient.getBillingAddress(provider),
          this.billingClient.getCredit(provider),
          getTaxIdWarning,
        ],
      );

      return {
        activeUserId,
        provider,
        paymentMethod,
        billingAddress,
        credit,
        taxIdWarning,
      };
    }),
    shareReplay({ bufferSize: 1, refCount: false }),
  );

  view$: Observable<View> = merge(
    this.load$.pipe(tap((view) => this.viewState$.next(view))),
    this.viewState$.pipe(filter((view): view is View => view !== null)),
  ).pipe(shareReplay({ bufferSize: 1, refCount: true }));

  private destroy$ = new Subject<void>();

  constructor(
    private accountService: AccountService,
    private activatedRoute: ActivatedRoute,
    private billingClient: SubscriberBillingClient,
    private messageListener: MessageListener,
    private providerService: ProviderService,
    private providerWarningsService: ProviderWarningsService,
    private subscriberBillingClient: SubscriberBillingClient,
  ) {}

  async ngOnInit() {
    this.providerWarningsService.taxIdWarningRefreshed$
      .pipe(
        switchMap((warning) =>
          combineLatest([
            of(warning),
            this.provider$.pipe(take(1)).pipe(
              mapProviderToSubscriber,
              switchMap((provider) => this.subscriberBillingClient.getBillingAddress(provider)),
            ),
          ]),
        ),
        takeUntil(this.destroy$),
      )
      .subscribe(([taxIdWarning, billingAddress]) => {
        if (this.viewState$.value) {
          this.viewState$.next({
            ...this.viewState$.value,
            taxIdWarning,
            billingAddress,
          });
        }
      });

    this.messageListener
      .messages$(BANK_ACCOUNT_VERIFIED_COMMAND)
      .pipe(
        withLatestFrom(this.view$),
        filter(
          ([message, view]) =>
            message.providerId === view.provider.data.id && message.adminId === view.activeUserId,
        ),
        switchMap(
          async ([_, view]) =>
            await Promise.all([
              this.subscriberBillingClient.getPaymentMethod(view.provider),
              this.subscriberBillingClient.getBillingAddress(view.provider),
            ]),
        ),
        tap(async ([paymentMethod, billingAddress]) => {
          if (paymentMethod) {
            await this.setPaymentMethod(paymentMethod);
          }
          if (billingAddress) {
            this.setBillingAddress(billingAddress);
          }
        }),
        takeUntil(this.destroy$),
      )
      .subscribe();
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  setBillingAddress = (billingAddress: BillingAddress) => {
    if (this.viewState$.value) {
      if (this.viewState$.value.billingAddress?.taxId !== billingAddress.taxId) {
        this.providerWarningsService.refreshTaxIdWarning();
      }
      this.viewState$.next({
        ...this.viewState$.value,
        billingAddress,
      });
    }
  };

  setPaymentMethod = async (paymentMethod: MaskedPaymentMethod) => {
    if (this.viewState$.value) {
      const billingAddress =
        this.viewState$.value.billingAddress ??
        (await this.subscriberBillingClient.getBillingAddress(this.viewState$.value.provider));

      this.viewState$.next({
        ...this.viewState$.value,
        paymentMethod,
        billingAddress,
      });
    }
  };
}
