import { Injectable, signal, computed, effect, Injector } from '@angular/core';
import { DeliveryOption, CartItem } from '../models';
import { getDiscountedPrice } from '../utils/item.utils';
import { normalizeDeliveryOption, normalizeOptionalNumber } from '../utils/normalization.utils';
import { LocalStorageService } from '../core/storage/local-storage.service';
import type { } from '../types/telegram.types';

type CartVariant = { colour?: string; size?: string; price?: number; currency?: string };

@Injectable({
  providedIn: 'root'
})
export class CartService {
  private readonly STORAGE_KEY = 'marketplace_cart';
  private cartItems = signal<CartItem[]>([]);
  private isTelegram = typeof window !== 'undefined' && !!window.Telegram?.WebApp;
  private addingItems = new Map<number, Promise<void>>();
  private initialized = false;
  
  items = this.cartItems.asReadonly();
  itemCount = computed(() => {
    const items = this.cartItems();
    if (!Array.isArray(items)) return 0;
    return items.reduce((total, item) => total + item.quantity, 0);
  });
  totalPrice = computed(() => {
    const items = this.cartItems();
    if (!Array.isArray(items)) return 0;
    return items.reduce((total, item) => {
      return total + (getDiscountedPrice(item) * item.quantity);
    }, 0);
  });
  totalDeliveryPrice = computed(() => {
    const items = this.cartItems();
    if (!Array.isArray(items)) return 0;
    return items.reduce((total, item) => {
      return total + ((item.selectedDelivery?.deliveryPrice ?? 0) * item.quantity);
    }, 0);
  });
  totalWithDelivery = computed(() => this.totalPrice() + this.totalDeliveryPrice());
  allRequiredDeliveriesSelected = computed(() => {
    const items = this.cartItems();
    if (!Array.isArray(items)) return true;
    return items.every(item => !this.itemRequiresDeliverySelection(item) || item.selectedDelivery != null);
  });
  hasDeliveryPrice = computed(() => {
    const items = this.cartItems();
    if (!Array.isArray(items)) return false;
    return this.allRequiredDeliveriesSelected()
      && items.some(item => (item.deliveryOptions?.length ?? 0) > 0 || item.selectedDelivery != null);
  });

  constructor(private injector: Injector, private readonly storage: LocalStorageService) {
    this.loadCart();
    
    // Auto-save whenever cart changes (skip the initial empty state)
    effect(() => {
      const items = this.cartItems();
      if (this.initialized) {
        this.saveToStorage(items);
      }
    });
  }

  private sameDeliveryOption(left: DeliveryOption, right: DeliveryOption): boolean {
    return left.deliveryPrice === right.deliveryPrice
      && left.deliveryPlace === right.deliveryPlace
      && left.deliveryTime === right.deliveryTime;
  }

  private itemRequiresDeliverySelection(item: CartItem): boolean {
    return item.deliveryMode !== 'digital'
      && (item.deliveryOptions?.length ?? 0) > 0
      && item.deliverySelectionRequired !== false;
  }

  private normalizeDeliveryState(item: CartItem): {
    deliveryMode?: CartItem['deliveryMode'];
    deliveryOptions: DeliveryOption[];
    deliverySelectionRequired: boolean;
    selectedDelivery: DeliveryOption | null;
  } {
    const normalizedOptions = Array.isArray(item.deliveryOptions)
      ? item.deliveryOptions
        .map(option => normalizeDeliveryOption(option))
        .filter((option): option is DeliveryOption => option !== null)
      : [];
    const legacyDeliveryPrice = normalizeOptionalNumber(item.deliveryPrice);
    const deliveryOptions = normalizedOptions.length > 0
      ? normalizedOptions
      : legacyDeliveryPrice !== undefined
        ? [{ deliveryPrice: legacyDeliveryPrice, deliveryPlace: '', deliveryTime: '' }]
        : [];
    const deliveryMode = item.deliveryMode === 'digital'
      ? 'digital'
      : deliveryOptions.length > 0
        ? 'selectable'
        : undefined;
    const deliverySelectionRequired = deliveryOptions.length > 0
      ? item.deliverySelectionRequired !== false && normalizedOptions.length > 0
      : false;
    const selectedDelivery = normalizeDeliveryOption(item.selectedDelivery)
      ?? (deliveryOptions.length === 1 && !deliverySelectionRequired ? deliveryOptions[0] : null);
    const matchedSelection = selectedDelivery && deliveryOptions.length > 0
      ? deliveryOptions.find(option => this.sameDeliveryOption(option, selectedDelivery)) ?? selectedDelivery
      : selectedDelivery;

    return {
      deliveryMode,
      deliveryOptions,
      deliverySelectionRequired,
      selectedDelivery: matchedSelection,
    };
  }

  private normalizeCartItem(item: CartItem): CartItem {
    const { deliveryPrice, ...rest } = item;
    const deliveryState = this.normalizeDeliveryState(item);
    const hasDeliveryState = !!deliveryState.deliveryMode
      || deliveryState.deliveryOptions.length > 0
      || deliveryState.selectedDelivery != null;

    return {
      ...rest,
      quantity: item.quantity || 1,
      ...(hasDeliveryState ? { deliverySelectionRequired: deliveryState.deliverySelectionRequired } : {}),
      ...(deliveryState.deliveryMode ? { deliveryMode: deliveryState.deliveryMode } : {}),
      ...(deliveryState.deliveryOptions.length > 0 ? { deliveryOptions: deliveryState.deliveryOptions } : {}),
      ...(deliveryState.selectedDelivery ? { selectedDelivery: deliveryState.selectedDelivery } : {}),
    };
  }

  private saveToStorage(items: CartItem[]): void {
    const data = JSON.stringify(items);

    // Always save to localStorage
    this.storage.setItem(this.STORAGE_KEY, data);

    // Also save to Telegram CloudStorage if available
    if (this.isTelegram) {
      window.Telegram!.WebApp.CloudStorage.setItem(this.STORAGE_KEY, data, (err) => {
        if (err) {
          console.error('Error saving to Telegram CloudStorage:', err);
        }
      });
    }
  }

  private loadCart(): void {
    if (this.isTelegram) {
      // Load from Telegram CloudStorage first
      window.Telegram!.WebApp.CloudStorage.getItem(this.STORAGE_KEY, (err, value) => {
        if (err) {
          console.error('Error loading from Telegram CloudStorage:', err);
          this.loadFromLocalStorage();
        } else if (value) {
          this.parseAndSetCart(value) || this.loadFromLocalStorage();
        } else {
          // No data in CloudStorage, try localStorage
          this.loadFromLocalStorage();
        }
        this.initialized = true;
      });
    } else {
      this.loadFromLocalStorage();
      this.initialized = true;
    }
  }

  private loadFromLocalStorage(): void {
    const stored = this.storage.getItem(this.STORAGE_KEY);
    if (stored) {
      this.parseAndSetCart(stored);
    }
  }

  /** Parse JSON cart data, migrate legacy items, and set the signal. Returns true on success. */
  private parseAndSetCart(json: string): boolean {
    try {
      const items = JSON.parse(json);
      if (Array.isArray(items)) {
        this.cartItems.set(items.map(item => this.normalizeCartItem(item)));
        return true;
      }
    } catch (err) {
      console.error('Error parsing cart data:', err);
    }
    this.cartItems.set([]);
    return false;
  }

  private normalizeVariantSize(size?: string): string {
    return size && size.toLowerCase() !== 'default' ? size : '';
  }

  private isSameCartLine(item: CartItem, itemID: number, variant?: CartVariant): boolean {
    return item.itemID === itemID
      && (item.colour ?? '') === (variant?.colour ?? '')
      && this.normalizeVariantSize(item.size) === this.normalizeVariantSize(variant?.size);
  }

  private findCartLineIndex(itemID: number, variant?: CartVariant): number {
    return this.cartItems().findIndex(item => this.isSameCartLine(item, itemID, variant));
  }

  /**
   * Resolves once the item is actually present in the cart signal - not
   * merely once the call was fired. New (not-yet-in-cart) items fetch their
   * details from the API first, so callers that navigate straight after
   * (e.g. Buy Now -> /cart) must await this rather than treat it as fired
   * synchronously.
   */
  addItem(itemID: number, quantity: number = 1, variant?: CartVariant): Promise<void> {
    // A concurrent call for the same not-yet-in-cart item awaits the same
    // in-flight promise instead of resolving immediately - otherwise a
    // rapid double-click could see the item "added" before it actually is.
    const pending = this.addingItems.get(itemID);
    if (pending) return pending;

    const currentItems = this.cartItems();
    const existingItem = currentItems.find(i => this.isSameCartLine(i, itemID, variant));

    if (existingItem) {
      // Item exists, increase quantity
      this.updateQuantity(itemID, existingItem.quantity + quantity, variant);
      return Promise.resolve();
    }

    // Get item details from API and add to cart
    const promise = import('./api.service').then(({ ApiService }) =>
      new Promise<void>((resolve, reject) => {
        this.injector.get(ApiService).getItem(itemID).subscribe({
          next: (item) => {
            const cartItem = this.normalizeCartItem({
              ...item,
              quantity,
              ...(variant?.colour != null && { colour: variant.colour }),
              ...(variant?.size != null && { size: variant.size }),
              ...(variant?.price != null && { price: variant.price }),
              ...(variant?.currency != null && { currency: variant.currency }),
            });
            this.cartItems.set([...this.cartItems(), cartItem]);
            this.addingItems.delete(itemID);
            resolve();
          },
          error: (err) => {
            console.error('Error adding to cart:', err);
            this.addingItems.delete(itemID);
            reject(err);
          }
        });
      })
    ).catch((err) => {
      console.error('Error loading API service:', err);
      this.addingItems.delete(itemID);
      throw err;
    });

    this.addingItems.set(itemID, promise);
    return promise;
  }

  updateQuantity(itemID: number, quantity: number, variant?: CartVariant): void {
    if (quantity <= 0) {
      this.removeItem(itemID, variant);
      return;
    }
    
    const currentItems = this.cartItems();
    const updatedItems = currentItems.map(item =>
      this.isSameCartLine(item, itemID, variant) ? { ...item, quantity } : item
    );
    this.cartItems.set(updatedItems);
  }

  setSelectedDelivery(itemID: number, selectedDelivery: DeliveryOption | null): void {
    const normalizedSelection = normalizeDeliveryOption(selectedDelivery);
    const updatedItems = this.cartItems().map(item => {
      if (item.itemID !== itemID) {
        return item;
      }

      return this.normalizeCartItem({
        ...item,
        selectedDelivery: normalizedSelection,
      });
    });

    this.cartItems.set(updatedItems);
  }

  removeItems(itemIDs: number[]): void {
    const currentItems = this.cartItems();
    const updatedItems = currentItems.filter(item => !itemIDs.includes(item.itemID));
    this.cartItems.set(updatedItems);
  }

  removeItem(itemID: number, variant?: CartVariant): void {
    if (!variant) {
      this.removeItems([itemID]);
      return;
    }

    const lineIndex = this.findCartLineIndex(itemID, variant);
    if (lineIndex === -1) {
      return;
    }

    const updatedItems = this.cartItems().filter((_item, index) => index !== lineIndex);
    this.cartItems.set(updatedItems);
  }

  clearCart(): void {
    this.cartItems.set([]);
  }
}
