import {
  Component,
  ChangeDetectionStrategy,
  OnInit,
  OnDestroy,
  inject,
  signal,
} from '@angular/core';
import { Subject, combineLatest } from 'rxjs';
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
import { CustomerSearchNavigation } from '@shared/services/navigation';
import { CustomerSearchStore } from '../store';
import {
  ShippingAddressDTO,
  NotificationChannel,
  ShoppingCartDTO,
  PayerDTO,
  BuyerDTO,
} from '@generated/swagger/checkout-api';
import { DomainCheckoutService } from '@domain/checkout';
import {
  CantAddCustomerToCartData,
  CantAddCustomerToCartModalComponent,
  CantSelectGuestModalComponent,
} from '../../modals';
import { UiModalService } from '@ui/modal';
import { ComponentStore } from '@ngrx/component-store';
import { ApplicationService } from '@core/application';
import {
  CheckoutNavigationService,
  ProductCatalogNavigationService,
} from '@shared/services/navigation';
import { ActivatedRoute, Router } from '@angular/router';
import { log, logAsync } from '@utils/common';
import { CrmCustomerService } from '@domain/crm';
import { MessageModalComponent, MessageModalData } from '@modal/message';
import { GenderSettingsService } from '@shared/services/gender';
import { toSignal } from '@angular/core/rxjs-interop';
import {
  CrmTabMetadataService,
  Customer,
  AssignedPayer,
} from '@isa/crm/data-access';
import {
  CustomerAdapter,
  ShippingAddressAdapter,
} from '@isa/checkout/data-access';
import {
  NavigateAfterRewardSelection,
  RewardSelectionPopUpService,
} from '@isa/checkout/shared/reward-selection-dialog';
import { NavigationStateService } from '@isa/core/navigation';
import { ShippingAddressDTO as CrmShippingAddressDTO } from '@generated/swagger/crm-api';

export interface CustomerDetailsViewMainState {
  isBusy: boolean;
  shoppingCart: ShoppingCartDTO;
  shippingAddress: ShippingAddressDTO;
  payer: PayerDTO;
}

@Component({
  selector: 'page-customer-details-main-view',
  templateUrl: 'details-main-view.component.html',
  styleUrls: ['details-main-view.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: false,
})
export class CustomerDetailsViewMainComponent
  extends ComponentStore<CustomerDetailsViewMainState>
  implements OnInit, OnDestroy
{
  private _store = inject(CustomerSearchStore);
  private _navigation = inject(CustomerSearchNavigation);
  private _checkoutService = inject(DomainCheckoutService);
  private _modalService = inject(UiModalService);
  private _application = inject(ApplicationService);
  private _catalogNavigation = inject(ProductCatalogNavigationService);
  private _checkoutNavigation = inject(CheckoutNavigationService);
  private _router = inject(Router);
  private _activatedRoute = inject(ActivatedRoute);
  private _genderSettings = inject(GenderSettingsService);
  private _navigationState = inject(NavigationStateService);
  private _onDestroy$ = new Subject<void>();

  customerService = inject(CrmCustomerService);
  crmTabMetadataService = inject(CrmTabMetadataService);
  private _rewardSelectionPopUpService = inject(RewardSelectionPopUpService);

  // Signal to track if return URL exists (used directly in template)
  readonly hasReturnUrl = signal(false);

  fetching$ = combineLatest([
    this._store.fetchingCustomer$,
    this._store.fetchingCustomerList$,
  ]).pipe(
    map(([fetchingCustomer, fetchingList]) => fetchingCustomer || fetchingList),
  );

  async getReturnUrlFromContext(): Promise<string | null> {
    // Get from preserved context (survives intermediate navigations, auto-scoped to tab)
    const context = await this._navigationState.restoreContext<{
      returnUrl?: string;
    }>('select-customer');

    return context?.returnUrl ?? null;
  }

  async checkHasReturnUrl(): Promise<void> {
    const hasContext =
      await this._navigationState.hasPreservedContext('select-customer');
    this.hasReturnUrl.set(hasContext);
  }

  isBusy$ = this.select((s) => s.isBusy);

  showLoader$ = combineLatest([this.fetching$, this.isBusy$]).pipe(
    map(([fetching, isBusy]) => fetching || isBusy),
  );

  processId$ = this._store.processId$;
  processIdSignal = toSignal(this.processId$);

  customerId$ = this._store.customerId$;

  historyRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
    map(([processId, customerId]) =>
      this._navigation.historyRoute({ processId, customerId }),
    ),
  );

  ordersRoute$ = combineLatest([this.processId$, this.customerId$]).pipe(
    map(([processId, customerId]) =>
      this._navigation.ordersRoute({ processId, customerId }),
    ),
  );

  isB2b$ = this._store.isBusinessKonto$;

  editRoute$ = combineLatest([
    this.processId$,
    this.customerId$,
    this.isB2b$,
  ]).pipe(
    map(([processId, customerId, isB2b]) =>
      this._navigation.editRoute({ processId, customerId, isB2b }),
    ),
  );

  showEditButton$ = combineLatest([
    this._store.isOnlinekonto$,
    this._store.isBestellungOhneKonto$,
  ]).pipe(
    map(
      ([isOnlinekonto, isBestellungOhneKonto]) =>
        !isOnlinekonto && !isBestellungOhneKonto,
    ),
  );

  hasKundenkarte$ = combineLatest([
    this._store.isKundenkarte$,
    this._store.isOnlineKontoMitKundenkarte$,
  ]).pipe(
    map(
      ([isKundenkarte, isOnlineKontoMitKundenkarte]) =>
        isKundenkarte || isOnlineKontoMitKundenkarte,
    ),
  );

  kundenkarteRoute$ = combineLatest([
    this.hasKundenkarte$,
    this.processId$,
    this.customerId$,
  ]).pipe(
    map(([hasKundenkarte, processId, customerId]) =>
      hasKundenkarte
        ? this._navigation.kundenkarteRoute({ processId, customerId })
        : undefined,
    ),
  );

  customerType$ = this._store.select(
    (s) => s.customer?.features?.find((f) => f.enabled)?.description,
  );

  created$ = this._store.select((s) => s.customer?.created);

  customerNumber$ = this._store.select((s) => s.customer?.customerNumber);

  customerNumberDig$ = this._store.select(
    (s) =>
      s.customer?.linkedRecords?.find((r) => r.repository === 'dig')?.number,
  );

  customerNumberBeeline$ = this._store.select(
    (s) =>
      s.customer?.linkedRecords?.find((r) => r.repository === 'beeline')
        ?.number,
  );

  gender$ = this._store.select((s) =>
    this._genderSettings.getGenderByValue(s.customer?.gender),
  );

  title$ = this._store.select((s) => s.customer?.title);

  lastName$ = this._store.select((s) => s.customer?.lastName);

  firstName$ = this._store.select((s) => s.customer?.firstName);

  email$ = this._store.select((s) => s.customer?.communicationDetails?.email);

  street$ = this._store.select((s) => s.customer?.address?.street);

  streetNumber$ = this._store.select((s) => s.customer?.address?.streetNumber);

  zipCode$ = this._store.select((s) => s.customer?.address?.zipCode);

  city$ = this._store.select((s) => s.customer?.address?.city);

  country$ = this._store.select((s) => s.customer?.address?.country);

  info$ = this._store.select((s) => s.customer?.address?.info);

  landline$ = this._store.select(
    (s) => s.customer?.communicationDetails?.phone,
  );

  mobile$ = this._store.select((s) => s.customer?.communicationDetails?.mobile);

  organisationName$ = this._store.select((s) => s.customer?.organisation?.name);

  department$ = this._store.select((s) => s.customer?.organisation?.department);

  vatId$ = this._store.select((s) => s.customer?.organisation?.vatId);

  isBusinessKonto$ = this._store.isBusinessKonto$;

  shoppingCartHasItems$ = this.select((s) => s.shoppingCart?.items?.length > 0);

  shoppingCartHasNoItems$ = this.shoppingCartHasItems$.pipe(
    map((hasItems) => !hasItems),
  );

  dateOfBirth$ = this._store.select((s) => s.customer?.dateOfBirth);

  get isBusy() {
    return this.get((s) => s.isBusy);
  }

  get shoppingCart() {
    return this.get((s) => s.shoppingCart);
  }

  get shoppingCartHasItems() {
    return this.shoppingCart?.items?.length > 0;
  }

  get processId() {
    return this._store.processId;
  }

  get customer() {
    return this._store.customer;
  }

  get customerFeatures() {
    const customerFeatures = this.customer.features;
    const features: { [key: string]: string } = {};

    for (const feature of customerFeatures) {
      features[feature.key] = feature.key;
    }

    return features;
  }

  get shippingAddress() {
    return this.get((s) => s.shippingAddress);
  }

  get buyer() {
    return CustomerAdapter.toBuyer(this.customer as unknown as Customer);
  }

  get payer() {
    return this.get((s) => s.payer);
  }

  get snapshot() {
    return this._activatedRoute.snapshot;
  }

  isOnlineOrCustomerCardUser$ = combineLatest([
    this.customerType$,
    this.hasKundenkarte$,
  ]).pipe(map(([type, hasCard]) => type === 'webshop' || hasCard));

  constructor() {
    super({
      isBusy: false,
      shoppingCart: undefined,
      shippingAddress: undefined,
      payer: undefined,
    });
  }

  setIsBusy(isBusy: boolean) {
    this.patchState({ isBusy });
  }

  @log
  setShippingAddress(address: ShippingAddressDTO) {
    this.patchState({ shippingAddress: address });
  }

  @log
  setPayer(payer: PayerDTO) {
    this.patchState({ payer });
  }

  ngOnInit() {
    // Check if we have a return URL context
    this.checkHasReturnUrl().then(async () => {
      // Check if we should auto-trigger continue() (only from Kundenkarte)
      const context = await this._navigationState.restoreContext<{
        returnUrl?: string;
        autoTriggerContinueFn?: boolean;
      }>('select-customer');

      if (context?.autoTriggerContinueFn) {
        // Clear the autoTriggerContinueFn flag immediately (preserves returnUrl automatically)
        await this._navigationState.patchContext(
          { autoTriggerContinueFn: undefined },
          'select-customer',
        );

        // Auto-trigger continue() ONLY when coming from Kundenkarte
        this.continue();
      }
    });

    this.processId$
      .pipe(
        takeUntil(this._onDestroy$),
        switchMap((pid) =>
          this._checkoutService.getShoppingCart({
            processId: pid,
            latest: true,
          }),
        ),
      )
      .subscribe((shoppingCart) => {
        this.patchState({ shoppingCart });
      });

    // #4564 Fix - Da der Kunde bei einer erneuten Suche auf undefined gesetzt wird, muss man diesen erneut nach erfolgreicher Suche setzen.
    // Dies geschieht bereits in der customer-search.component.ts immer wenn eine Navigation ausgeführt wird (sprich für Fälle in denen ein neuer Kunde gesetzt wird).
    // Falls aber nach exakt dem gleichen Kunden gesucht wird, muss man diesen hier manuell erneut setzen, ansonsten bleibt dieser im Store auf undefined.
    // Da dies nur für die Detailansicht relevant ist, wird hier auch auf hits 1 überprüft, ansonsten befindet man sich sowieso in der Trefferliste.
    this._store.customerListResponse$
      .pipe(takeUntil(this._onDestroy$))
      .subscribe(([result]) => {
        if (result.hits === 1) {
          const customerId = result?.result[0].id;
          const selectedCustomerId = +this.snapshot.params.customerId;

          if (customerId === selectedCustomerId) {
            this._store.selectCustomer({ customerId });
          }
        }
      });
  }

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

  @logAsync
  async continue() {
    if (this.isBusy) return;
    this.setIsBusy(true);

    // Shared validation and setup for all flows
    const canAddCustomer = await this._canAddCustomerAsync();
    if (this.shoppingCartHasItems && !canAddCustomer) {
      this.setIsBusy(false);
      return;
    }

    const canAddShippingAddress = await this._canAddShippingAddressAsync();
    if (this.shippingAddress && !canAddShippingAddress) {
      this.setIsBusy(false);
      return;
    }

    const destinationUpdated = await this._updateDestinationAsync();
    if (!destinationUpdated) {
      this.setIsBusy(false);
      return;
    }

    if (this._isGuestWithOrder()) {
      this._modalService.open({
        content: CantSelectGuestModalComponent,
        data: this.customer,
      });
      return;
    }

    this._patchProcessName();

    const currentBuyer = await this._getCurrentBuyer();

    this._setCustomer();

    this._setBuyer();

    this._setSelectedCustomerIdInTab();

    await this._updateNotifcationChannelsAsync(currentBuyer);

    await this._setPayer();

    await this._setShippingAddress();

    // #5461 Priority fix: Check for regular shopping cart items BEFORE reward return URL
    // This ensures that if a user has items in their regular cart, that takes precedence
    // over any lingering reward flow context
    if (this.shoppingCartHasItems) {
      await this.#rewardSelectionPopUpFlow(this.processId);
      this.setIsBusy(false);
      return;
    }

    // #5262 Check for reward selection flow before navigation
    if (this.hasReturnUrl()) {
      // Restore from preserved context (auto-scoped to current tab) and clean up
      const context = await this._navigationState.restoreAndClearContext<{
        returnUrl?: string;
      }>('select-customer');

      if (context?.returnUrl) {
        await this._router.navigateByUrl(context.returnUrl);
      } else {
        // No returnUrl found, navigate to default reward page
        await this._router.navigate(['/', this.processId, 'reward']);
      }

      this.setIsBusy(false);
      return;
    }

    // Navigation zur Artikelsuche
    const path = this._catalogNavigation.getArticleSearchBasePath(
      this.processId,
    ).path;
    await this._router.navigate(path);

    this.setIsBusy(false);
  }

  @logAsync
  _getCurrentBuyer() {
    return this._checkoutService
      .getBuyer({ processId: this.processId })
      .pipe(first())
      .toPromise();
  }

  @logAsync
  async _canAddCustomerAsync() {
    const res = await this._checkoutService
      .canSetCustomer({
        processId: this.processId,
        customerFeatures: this.customerFeatures,
      })
      .toPromise();

    if (res.ok) {
      return true;
    }

    const required = await this.customerService
      .canUpgrade(this.customer.id)
      .toPromise();

    const upgradeableTo = res.create;
    const data: CantAddCustomerToCartData = {
      message: res.message,
      upgradeableTo,
      customer: this.customer,
      attributes: this.customer.attributes.map((a) => a.data),
      required,
    };

    await this._modalService
      .open({
        content: CantAddCustomerToCartModalComponent,
        data,
      })
      .afterClosed$.toPromise();

    return false;
  }

  @logAsync
  async _canAddShippingAddressAsync() {
    try {
      const res = await this._checkoutService
        .canAddDestination({
          processId: this.processId,
          destinationDTO: {
            target: 2,
            shippingAddress: this.shippingAddress,
          },
        })
        .toPromise();

      if (typeof res === 'string') {
        throw new Error(res);
      }

      return true;
    } catch {
      this._modalService.open({
        content: MessageModalComponent,
        title: 'Warenkorb kann dem Kunden nicht zugewiesen werden',
        data: {
          message: 'Dieser Versand ist nur innerhalb Deutschlands möglich.',
          actions: [
            { label: 'OK' },
            {
              label: 'Lieferadresse hinzufügen',
              primary: true,
              action: () => {
                const nav = this._navigation.addShippingAddressRoute({
                  processId: this.processId,
                  customerId: this.customer.id,
                });
                this._router.navigate(nav.path, {
                  queryParams: nav.queryParams,
                  queryParamsHandling: 'merge',
                });
              },
            },
          ],
        } as MessageModalData,
      });

      return false;
    }
  }

  @logAsync
  async _updateDestinationAsync() {
    const res = await this._checkoutService
      .setDestinationForCustomer({
        processId: this.processId,
        customerFeatures: this.customerFeatures,
      })
      .toPromise();

    if (!res.ok) {
      this._modalService.open({
        content: CantAddCustomerToCartModalComponent,
        data: {
          message: '',
          customer: this.customer,
          required: res.create,
          upgradeableTo: undefined,
          attributes: this.customer.attributes.map((s) => s.data),
        } as CantAddCustomerToCartData,
      });
    }

    return res.ok;
  }

  @log
  _isGuestWithOrder() {
    const isGuest = this.customer.features.some((f) => f.key === 'guest');
    if (!isGuest) return false;

    const hasOrder = this.customer.orderCount > 0;
    return hasOrder;
  }

  @log
  _patchProcessName() {
    let name = `${this.customer.firstName ?? ''} ${this.customer.lastName ?? ''}`;

    // Ticket #4458 Es kann vorkommen, dass B2B Konten keinen Firmennamen hinterlegt haben
    // zusätzlich kanne es bei Mitarbeiter Konten vorkommen, dass die Namen in der Organisation statt im Kundennamen hinterlegt sind
    if (
      (this._store.isBusinessKonto && this.customer.organisation?.name) ||
      (!this.customer.firstName && !this.customer.lastName)
    ) {
      name = `${this.customer.organisation?.name ?? ''}`;
    }

    this._application.patchProcess(this.processId, {
      name,
    });
  }

  @log
  _setCustomer() {
    this._checkoutService.setCustomer({
      processId: this.processId,
      customerDto: this.customer,
    });
    this.crmTabMetadataService.setSelectedCustomerId(
      this.processId,
      this.customer.id,
    );
  }

  @log
  _setBuyer() {
    this._checkoutService.setBuyer({
      processId: this.processId,
      buyer: this.buyer,
    });
  }

  // #5307 Für den Warenkorb und den "Prämien oder Warenkorb" CTA ist es wichtig, dass die customerId im Tab hinterlegt ist
  @log
  _setSelectedCustomerIdInTab() {
    this.crmTabMetadataService.setSelectedCustomerId(
      this.processId,
      this.customer.id,
    );
  }

  @logAsync
  async _updateNotifcationChannelsAsync(currentBuyer: BuyerDTO | undefined) {
    if (currentBuyer?.buyerNumber !== this.customer.customerNumber) {
      const notificationChannels =
        this.customer.notificationChannels === (3 as NotificationChannel)
          ? 1
          : this.customer.notificationChannels;

      this._checkoutService.setNotificationChannels({
        processId: this.processId,
        notificationChannels,
      });
    }
  }

  @logAsync
  async _setPayer() {
    // Check if there's a selected payer in metadata (from previous address selection)
    const selectedPayerId = this.crmTabMetadataService.selectedPayerId(
      this.processId,
    );

    if (selectedPayerId) {
      // Load the selected payer from metadata
      try {
        const payerResponse = await this.customerService
          .getPayer(selectedPayerId)
          .toPromise();

        if (payerResponse?.result) {
          // Create AssignedPayer structure expected by adapter
          // Type cast needed due to incompatible enum types between CRM and Checkout APIs
          const assignedPayer = {
            payer: {
              id: selectedPayerId,
              data: payerResponse.result,
            },
          } as AssignedPayer;

          const payer = CustomerAdapter.toPayerFromAssignedPayer(assignedPayer);

          if (payer) {
            this._checkoutService.setPayer({
              processId: this.processId,
              payer,
            });
            return;
          }
        }
      } catch (error) {
        console.error('Failed to load selected payer from metadata', error);
      }
    }

    // Fallback to current payer from component state
    if (this.payer) {
      this._checkoutService.setPayer({
        processId: this.processId,
        payer: this.payer,
      });
    }
  }

  @logAsync
  async _setShippingAddress() {
    // Check if there's a selected shipping address in metadata (from previous address selection)
    const selectedShippingAddressId =
      this.crmTabMetadataService.selectedShippingAddressId(this.processId);

    if (selectedShippingAddressId) {
      // Load the selected shipping address from metadata
      try {
        const addressResponse = await this.customerService
          .getShippingAddress(selectedShippingAddressId)
          .toPromise();

        if (addressResponse?.result) {
          const shippingAddress = ShippingAddressAdapter.fromCrmShippingAddress(
            addressResponse.result as CrmShippingAddressDTO,
          );

          if (shippingAddress) {
            this._checkoutService.setShippingAddress({
              processId: this.processId,
              shippingAddress,
            });
            return;
          }
        }
      } catch (error) {
        console.error(
          'Failed to load selected shipping address from metadata',
          error,
        );
      }
    }

    // Fallback to current shipping address from component state
    if (this.shippingAddress) {
      this._checkoutService.setShippingAddress({
        processId: this.processId,
        shippingAddress: this.shippingAddress,
      });
    }
  }

  async #rewardSelectionPopUpFlow(tabId: number) {
    await this.#reloadShoppingCart(tabId);
    const navigate: NavigateAfterRewardSelection =
      await this._rewardSelectionPopUpService.popUp();
    await this.#reloadShoppingCart(tabId);

    switch (navigate) {
      case NavigateAfterRewardSelection.CART:
        await this.#navigateToCart(tabId);
        break;
      case NavigateAfterRewardSelection.REWARD:
        await this.#navigateToReward(tabId);
        break;
      case NavigateAfterRewardSelection.CATALOG:
        await this.#navigateToResultList(tabId);
        break;
      default:
        break;
    }
  }

  async #reloadShoppingCart(tabId: number) {
    await this._checkoutService.reloadShoppingCart({ processId: tabId });
  }

  async #navigateToReward(tabId: number) {
    await this._router.navigate([`/${tabId}`, 'reward', 'cart']);
  }

  async #navigateToCart(tabId: number) {
    const path = this._checkoutNavigation.getCheckoutReviewPath(tabId).path;
    await this._router.navigate(path);
  }

  async #navigateToResultList(tabId: number) {
    const path =
      this._catalogNavigation.getArticleSearchResultsPath(tabId).path;
    await this._router.navigate(path);
  }
}
