import { Inject, Injectable, NotFoundException, ConflictException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { randomUUID } from "crypto";
import { CacheService } from "@bff/infra/cache/cache.service.js";
import type {
  CheckoutBuildCartRequest,
  CheckoutCart,
  CheckoutSessionResponse,
  CreateOrderRequest,
  OrderCreateResponse,
} from "@customer-portal/domain/orders";
import { OrderIdempotencyService } from "./order-idempotency.service.js";
import { OrderOrchestrator } from "./order-orchestrator.service.js";

type CheckoutSessionRecord = {
  request: CheckoutBuildCartRequest;
  cart: CheckoutCart;
  createdAt: string;
  expiresAt: string;
};

@Injectable()
export class CheckoutSessionService {
  private readonly ttlSeconds = 2 * 60 * 60; // 2 hours
  private readonly keyPrefix = "checkout-session";

  constructor(
    private readonly cache: CacheService,
    private readonly orderIdempotency: OrderIdempotencyService,
    private readonly orderOrchestrator: OrderOrchestrator,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async createSession(request: CheckoutBuildCartRequest, cart: CheckoutCart) {
    const sessionId = randomUUID();
    const createdAt = new Date();
    const expiresAt = new Date(createdAt.getTime() + this.ttlSeconds * 1000);

    const record: CheckoutSessionRecord = {
      request,
      cart,
      createdAt: createdAt.toISOString(),
      expiresAt: expiresAt.toISOString(),
    };

    const key = this.buildKey(sessionId);
    await this.cache.set(key, record, this.ttlSeconds);

    this.logger.debug("Checkout session created", { sessionId, expiresAt: record.expiresAt });

    return {
      sessionId,
      expiresAt: record.expiresAt,
    };
  }

  async getSession(sessionId: string): Promise<CheckoutSessionRecord> {
    const key = this.buildKey(sessionId);
    const record = await this.cache.get<CheckoutSessionRecord>(key);
    if (!record) {
      throw new NotFoundException("Checkout session not found");
    }
    return record;
  }

  /**
   * Create a session and return the full response with cart summary.
   */
  async createSessionWithResponse(
    request: CheckoutBuildCartRequest,
    cart: CheckoutCart
  ): Promise<CheckoutSessionResponse> {
    const session = await this.createSession(request, cart);

    return {
      sessionId: session.sessionId,
      expiresAt: session.expiresAt,
      orderType: request.orderType,
      cart: {
        items: cart.items,
        totals: cart.totals,
      },
    };
  }

  /**
   * Get a session and return the full response with cart summary.
   */
  async getSessionResponse(sessionId: string): Promise<CheckoutSessionResponse> {
    const session = await this.getSession(sessionId);

    return {
      sessionId,
      expiresAt: session.expiresAt,
      orderType: session.request.orderType,
      cart: {
        items: session.cart.items,
        totals: session.cart.totals,
      },
    };
  }

  async deleteSession(sessionId: string): Promise<void> {
    const key = this.buildKey(sessionId);
    await this.cache.del(key);
  }

  /**
   * Create an order from a checkout session with idempotency guarantees.
   *
   * Handles the full flow: idempotency check, lock acquisition, session
   * retrieval, SKU extraction, order creation, result caching, and session cleanup.
   */
  async createOrderFromSession(
    userId: string,
    checkoutSessionId: string
  ): Promise<OrderCreateResponse> {
    // Check for existing result (idempotent replay for retries/double-clicks)
    const existingResult = await this.orderIdempotency.getExistingResult(checkoutSessionId);
    if (existingResult) {
      return existingResult;
    }

    // Acquire lock to prevent concurrent processing
    const lockAcquired = await this.orderIdempotency.acquireLock(checkoutSessionId);
    if (!lockAcquired) {
      throw new ConflictException("Order creation already in progress for this session");
    }

    try {
      const session = await this.getSession(checkoutSessionId);

      // Use stored cart - prices were locked at session creation time
      // This prevents catalog/price changes mid-checkout from affecting the order
      const cart = session.cart;

      const uniqueSkus = [
        ...new Set(
          cart.items
            .map(item => item.sku)
            .filter((sku): sku is string => typeof sku === "string" && sku.trim().length > 0)
        ),
      ];

      if (uniqueSkus.length === 0) {
        throw new NotFoundException("Checkout session contains no items");
      }

      const orderBody: CreateOrderRequest = {
        orderType: session.request.orderType,
        skus: uniqueSkus,
        ...(Object.keys(cart.configuration ?? {}).length > 0
          ? { configurations: cart.configuration }
          : {}),
      };

      const result = await this.orderOrchestrator.createOrder(userId, orderBody);

      // Store result BEFORE deleting session (ensures idempotency on network failure)
      await this.orderIdempotency.storeResult(checkoutSessionId, result);

      // Safe to delete session now - result is cached for retries
      await this.deleteSession(checkoutSessionId);

      return result;
    } catch (error) {
      // Release lock on failure to allow immediate retry
      await this.orderIdempotency.releaseLock(checkoutSessionId);
      throw error;
    }
  }

  private buildKey(sessionId: string): string {
    return `${this.keyPrefix}:${sessionId}`;
  }
}
