import {
  Body,
  Controller,
  Get,
  NotFoundException,
  Param,
  Post,
  Request,
  Sse,
  UseGuards,
  UnauthorizedException,
  ConflictException,
  type MessageEvent,
} from "@nestjs/common";
import { RateLimitGuard, RateLimit } from "@bff/core/rate-limiting/index.js";
import { OrderOrchestrator } from "./services/order-orchestrator.service.js";
import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
import { Logger } from "nestjs-pino";
import { createZodDto, ZodResponse } from "nestjs-zod";
import {
  checkoutSessionCreateOrderRequestSchema,
  createOrderRequestSchema,
  orderCreateResponseSchema,
  sfOrderIdParamSchema,
  orderDetailsSchema,
  orderListResponseSchema,
  type CreateOrderRequest,
} from "@customer-portal/domain/orders";
import { Observable } from "rxjs";
import { OrderEventsService } from "./services/order-events.service.js";
import { SalesforceReadThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-read-throttle.guard.js";
import { SalesforceWriteThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-write-throttle.guard.js";
import { CheckoutSessionService } from "./services/checkout-session.service.js";
import { OrderIdempotencyService } from "./services/order-idempotency.service.js";

class CreateOrderRequestDto extends createZodDto(createOrderRequestSchema) {}
class CheckoutSessionCreateOrderDto extends createZodDto(checkoutSessionCreateOrderRequestSchema) {}
class SfOrderIdParamDto extends createZodDto(sfOrderIdParamSchema) {}
class CreateOrderResponseDto extends createZodDto(orderCreateResponseSchema) {}
class OrderDetailsDto extends createZodDto(orderDetailsSchema) {}
class OrderListResponseDto extends createZodDto(orderListResponseSchema) {}

@Controller("orders")
@UseGuards(RateLimitGuard)
export class OrdersController {
  constructor(
    private orderOrchestrator: OrderOrchestrator,
    private readonly checkoutSessions: CheckoutSessionService,
    private readonly orderIdempotency: OrderIdempotencyService,
    private readonly orderEvents: OrderEventsService,
    private readonly logger: Logger
  ) {}

  @Post()
  @UseGuards(SalesforceWriteThrottleGuard)
  @RateLimit({ limit: 5, ttl: 60 }) // 5 order creations per minute
  @ZodResponse({ status: 201, description: "Create order", type: CreateOrderResponseDto })
  async create(@Request() req: RequestWithUser, @Body() body: CreateOrderRequestDto) {
    return this.orderOrchestrator.createOrder(req.user.id, body);
  }

  @Post("from-checkout-session")
  @UseGuards(SalesforceWriteThrottleGuard)
  @RateLimit({ limit: 5, ttl: 60 }) // 5 order creations per minute
  @ZodResponse({
    status: 201,
    description: "Create order from checkout session",
    type: CreateOrderResponseDto,
  })
  async createFromCheckoutSession(
    @Request() req: RequestWithUser,
    @Body() body: CheckoutSessionCreateOrderDto
  ) {
    this.logger.log(
      {
        userId: req.user?.id,
        checkoutSessionId: body.checkoutSessionId,
      },
      "Order creation from checkout session request received"
    );

    // Check for existing result (idempotent replay for retries/double-clicks)
    const existingResult = await this.orderIdempotency.getExistingResult(body.checkoutSessionId);
    if (existingResult) {
      return existingResult;
    }

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

    try {
      const session = await this.checkoutSessions.getSession(body.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(req.user.id, orderBody);

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

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

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

  @Get("user")
  @UseGuards(SalesforceReadThrottleGuard)
  @ZodResponse({ description: "Get user orders", type: OrderListResponseDto })
  async getUserOrders(@Request() req: RequestWithUser) {
    return this.orderOrchestrator.getOrdersForUser(req.user.id);
  }

  @Get(":sfOrderId")
  @UseGuards(SalesforceReadThrottleGuard)
  @ZodResponse({ description: "Get order details", type: OrderDetailsDto })
  async get(@Request() req: RequestWithUser, @Param() params: SfOrderIdParamDto) {
    if (!req.user?.id) {
      throw new UnauthorizedException("Authentication required");
    }
    return this.orderOrchestrator.getOrderForUser(params.sfOrderId, req.user.id);
  }

  @Sse(":sfOrderId/events")
  @UseGuards(SalesforceReadThrottleGuard)
  async streamOrderUpdates(
    @Request() req: RequestWithUser,
    @Param() params: SfOrderIdParamDto
  ): Promise<Observable<MessageEvent>> {
    // Ensure caller is allowed to access this order stream (avoid leaking existence)
    try {
      await this.orderOrchestrator.getOrderForUser(params.sfOrderId, req.user.id);
    } catch {
      throw new NotFoundException("Order not found");
    }
    return this.orderEvents.subscribe(params.sfOrderId);
  }

  // Note: Order provisioning has been moved to SalesforceProvisioningController
  // This controller now focuses only on customer-facing order operations
}
