package com.flightticket.controller;

import com.flightticket.dto.error.ErrorResponse;
import com.flightticket.dto.expedia.*;
import com.flightticket.exception.ExpediaServiceException;
import com.flightticket.exception.InsufficientBalanceException;
import com.flightticket.exception.LinkExpiredException;
import com.flightticket.exception.PriceChangedException;
import com.flightticket.security.annotation.RequiresAgent;
import com.flightticket.service.ExpediaBookingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

/**
 * REST controller for Expedia accommodation booking operations
 * Implements three-step booking workflow: Search → Price Check → Book
 * 
 * Requirements: 2.1, 3.1, 4.1, 7.2, 7.3
 */
@Slf4j
@RestController
@RequestMapping("/api/v1/expedia")
@RequiredArgsConstructor
@Tag(name = "Expedia Accommodation Booking", description = "APIs for searching, price checking, and booking accommodations via Expedia Rapid API. Implements HATEOAS-based workflow with dynamic link caching and wallet integration.")
@SecurityRequirement(name = "bearerAuth")
public class ExpediaController {
    
    private final ExpediaBookingService expediaBookingService;
    
    /**
     * Search for available accommodations
     * 
     * Requirements: 2.1
     * 
     * @param request Search parameters (location, dates, occupancy)
     * @return List of available rooms with pricing and details
     */
    @Operation(
            summary = "Search for available accommodations",
            description = """
                    Search for available hotel rooms and accommodations using the Expedia Rapid API.
                    
                    This is the first step in the three-step booking workflow (Search → Price Check → Book).
                    The response includes HATEOAS links for price checking, which are cached for 15 minutes.
                    
                    **Workflow:**
                    1. Submit search with location, dates, and occupancy
                    2. Receive list of available rooms with pricing
                    3. Each room includes a cached price_check link for the next step
                    
                    **Requirements:** 2.1 - Shop Service Availability Search
                    """
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Search successful - returns list of available rooms",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = ExpediaShopResponse.ExpediaRoom.class),
                            examples = @ExampleObject(value = """
                                    [{
                                        "room_id": "room-12345",
                                        "room_type": "Deluxe King Room",
                                        "description": "Spacious room with king bed, city view, and modern amenities",
                                        "max_occupancy": 2,
                                        "bed_count": 1,
                                        "bed_type": "King",
                                        "price": {
                                            "amount": 150.00,
                                            "currency": "USD",
                                            "total_amount": 165.00,
                                            "taxes": 15.00
                                        },
                                        "cancellation_policy": "Free cancellation until 24 hours before check-in",
                                        "refundable": true
                                    }]
                                    """)
                    )
            ),
            @ApiResponse(
                    responseCode = "400",
                    description = "Invalid search parameters (e.g., invalid dates, negative occupancy)",
                    content = @Content(schema = @Schema(implementation = ErrorResponse.class))
            ),
            @ApiResponse(
                    responseCode = "403",
                    description = "Insufficient permissions or agency inactive",
                    content = @Content(schema = @Schema(implementation = ErrorResponse.class))
            ),
            @ApiResponse(
                    responseCode = "503",
                    description = "Expedia API unavailable or circuit breaker open",
                    content = @Content(schema = @Schema(implementation = ErrorResponse.class))
            ),
            @ApiResponse(
                    responseCode = "504",
                    description = "Request timeout - Expedia API did not respond within 15 seconds",
                    content = @Content(schema = @Schema(implementation = ErrorResponse.class))
            )
    })
    @PostMapping("/search")
    @RequiresAgent
    public ResponseEntity<List<ExpediaShopResponse.ExpediaRoom>> searchAvailability(
            @io.swagger.v3.oas.annotations.parameters.RequestBody(
                    description = "Search parameters including location, dates, and occupancy",
                    required = true,
                    content = @Content(
                            schema = @Schema(implementation = ExpediaShopRequest.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "location": "LAX",
                                        "checkIn": "2024-06-15",
                                        "checkOut": "2024-06-18",
                                        "adults": 2,
                                        "children": 0,
                                        "currency": "USD"
                                    }
                                    """)
                    )
            )
            @Valid @RequestBody ExpediaShopRequest request) {
        log.info("Expedia search request for location: {}, check-in: {}, check-out: {}", 
            request.getLocation(), request.getCheckIn(), request.getCheckOut());
        
        try {
            List<ExpediaShopResponse.ExpediaRoom> rooms = expediaBookingService.searchAvailability(request);
            log.info("Found {} available rooms", rooms != null ? rooms.size() : 0);
            return ResponseEntity.ok(rooms);
            
        } catch (ExpediaServiceException e) {
            log.error("Expedia service error during search: {}", e.getMessage(), e);
            throw e; // Let GlobalExceptionHandler handle it
        }
    }
    
    /**
     * Validate room availability and lock price
     * 
     * Requirements: 3.1, 7.2, 7.3
     * 
     * @param roomId Unique room identifier from search results
     * @return Price check result with status and pricing information
     */
    @Operation(
            summary = "Validate room availability and lock price",
            description = """
                    Verify that a selected room is still available and lock the price before booking.
                    
                    This is the second step in the three-step booking workflow (Search → Price Check → Book).
                    Uses the cached price_check link from the search step.
                    
                    **Workflow:**
                    1. Provide room_id from search results
                    2. System retrieves cached price_check link (valid for 15 minutes)
                    3. Validates room availability and price with Expedia
                    4. Returns price confirmation or price change notification
                    
                    **Price Change Handling:**
                    - If price matches: Returns 200 with book link cached for next step
                    - If price changed: Returns 409 Conflict with old and new prices
                    - Client must explicitly confirm new price to proceed
                    
                    **Requirements:** 3.1 - Price Check Service, 7.2 - Price Change Detection
                    """
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Price validated successfully - room available at original price",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = ExpediaPriceCheckResponse.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "status": "matched",
                                        "room_id": "room-12345",
                                        "original_price": {
                                            "amount": 150.00,
                                            "currency": "USD",
                                            "total_amount": 165.00,
                                            "taxes": 15.00
                                        },
                                        "current_price": {
                                            "amount": 150.00,
                                            "currency": "USD",
                                            "total_amount": 165.00,
                                            "taxes": 15.00
                                        },
                                        "message": "Price confirmed. Ready to book."
                                    }
                                    """)
                    )
            ),
            @ApiResponse(
                    responseCode = "409",
                    description = "Price has changed - user confirmation required",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = PriceChangeErrorResponse.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "originalPrice": 150.00,
                                        "newPrice": 175.00,
                                        "currency": "USD",
                                        "message": "Price changed from USD 150.00 to USD 175.00. User confirmation required."
                                    }
                                    """)
                    )
            ),
            @ApiResponse(
                    responseCode = "410",
                    description = "Cached link expired - search session timed out (15 minute limit)",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = ErrorResponse.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "timestamp": "2024-01-15T10:30:00",
                                        "status": 410,
                                        "error": "Gone",
                                        "message": "Booking session expired. Please search again.",
                                        "path": "/api/v1/expedia/price-check/room-12345"
                                    }
                                    """)
                    )
            ),
            @ApiResponse(
                    responseCode = "403",
                    description = "Insufficient permissions or agency inactive",
                    content = @Content(schema = @Schema(implementation = ErrorResponse.class))
            ),
            @ApiResponse(
                    responseCode = "503",
                    description = "Expedia API unavailable or circuit breaker open",
                    content = @Content(schema = @Schema(implementation = ErrorResponse.class))
            )
    })
    @PostMapping("/price-check/{roomId}")
    @RequiresAgent
    public ResponseEntity<?> priceCheck(
            @Parameter(
                    description = "Unique room identifier from search results",
                    required = true,
                    example = "room-12345"
            )
            @PathVariable String roomId) {
        log.info("Price check request for room: {}", roomId);
        
        try {
            ExpediaPriceCheckResponse response = expediaBookingService.validatePrice(roomId);
            log.info("Price validated successfully for room: {}", roomId);
            return ResponseEntity.ok(response);
            
        } catch (PriceChangedException e) {
            log.warn("Price changed for room: {}. Original: {} {}, New: {} {}", 
                roomId, e.getCurrency(), e.getOriginalPrice(), e.getCurrency(), e.getNewPrice());
            
            PriceChangeErrorResponse errorResponse = PriceChangeErrorResponse.builder()
                    .originalPrice(e.getOriginalPrice())
                    .newPrice(e.getNewPrice())
                    .currency(e.getCurrency())
                    .message(e.getMessage())
                    .build();
            
            return ResponseEntity.status(HttpStatus.CONFLICT).body(errorResponse);
            
        } catch (LinkExpiredException e) {
            log.warn("Link expired for room: {}. Message: {}", roomId, e.getMessage());
            
            ErrorResponse errorResponse = ErrorResponse.builder()
                    .timestamp(LocalDateTime.now())
                    .status(HttpStatus.GONE.value())
                    .error("Gone")
                    .message("Booking session expired. Please search again.")
                    .path("/api/v1/expedia/price-check/" + roomId)
                    .traceId(generateTraceId())
                    .build();
            
            return ResponseEntity.status(HttpStatus.GONE).body(errorResponse);
            
        } catch (ExpediaServiceException e) {
            log.error("Expedia service error during price check: {}", e.getMessage(), e);
            throw e; // Let GlobalExceptionHandler handle it
        }
    }
    
    /**
     * Book room with hold and payment processing
     * Orchestrates the complete booking flow with wallet integration
     * 
     * Requirements: 4.1, 7.2, 7.3
     * 
     * @param roomId Unique room identifier from search results
     * @param request Booking details (travelers, payment info, contact details)
     * @param agencyId Agency ID for wallet operations
     * @param authentication Spring Security authentication context
     * @return Final booking response with confirmation number and details
     */
    @Operation(
            summary = "Book room with hold and payment processing",
            description = """
                    Complete the booking process with hold-and-resume pattern and wallet integration.
                    
                    This is the third and final step in the booking workflow (Search → Price Check → Book).
                    Uses the cached book link from the price check step.
                    
                    **Workflow:**
                    1. Provide room_id and booking details (travelers, payment, contact)
                    2. System retrieves cached book link (valid for 15 minutes)
                    3. Creates hold booking with Expedia (no immediate charge)
                    4. Reserves funds in agency wallet
                    5. Debits agency wallet for booking amount
                    6. If payment succeeds: Finalizes booking with Expedia (resume)
                    7. If payment fails: Cancels hold booking
                    8. Returns confirmation number and booking details
                    
                    **Hold & Resume Pattern:**
                    - Room is secured without immediate Expedia charge
                    - Wallet is charged first to ensure payment
                    - Only after successful payment is Expedia booking finalized
                    - Prevents double-charging and lost reservations
                    
                    **Requirements:** 4.1 - Booking Service with Hold & Resume, 5.1-5.7 - Payment Processing
                    """
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Booking completed successfully with confirmation",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = ExpediaBookingResponse.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "booking_id": "booking-67890",
                                        "confirmation_number": "EXP-ABC123456",
                                        "status": "confirmed",
                                        "created_at": "2024-01-15T10:30:00",
                                        "message": "Booking confirmed successfully",
                                        "details": {
                                            "property": {
                                                "property_id": "prop-123",
                                                "name": "Grand Hotel Downtown",
                                                "address": "123 Main Street",
                                                "city": "Los Angeles",
                                                "country": "USA",
                                                "phone": "+1-555-0100"
                                            },
                                            "room": {
                                                "room_id": "room-12345",
                                                "room_type": "Deluxe King Room",
                                                "bed_type": "King",
                                                "max_occupancy": 2
                                            },
                                            "check_in": "2024-06-15",
                                            "check_out": "2024-06-18",
                                            "nights": 3,
                                            "price": {
                                                "amount": 150.00,
                                                "currency": "USD",
                                                "total_amount": 495.00,
                                                "taxes": 45.00
                                            }
                                        }
                                    }
                                    """)
                    )
            ),
            @ApiResponse(
                    responseCode = "402",
                    description = "Insufficient wallet balance - payment required",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = ErrorResponse.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "timestamp": "2024-01-15T10:30:00",
                                        "status": 402,
                                        "error": "Payment Required",
                                        "message": "Insufficient wallet balance. Please recharge your wallet.",
                                        "path": "/api/v1/expedia/book/room-12345"
                                    }
                                    """)
                    )
            ),
            @ApiResponse(
                    responseCode = "410",
                    description = "Cached link expired - booking session timed out (15 minute limit)",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = ErrorResponse.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "timestamp": "2024-01-15T10:30:00",
                                        "status": 410,
                                        "error": "Gone",
                                        "message": "Booking session expired. Please search again.",
                                        "path": "/api/v1/expedia/book/room-12345"
                                    }
                                    """)
                    )
            ),
            @ApiResponse(
                    responseCode = "403",
                    description = "Insufficient permissions or agency inactive",
                    content = @Content(schema = @Schema(implementation = ErrorResponse.class))
            ),
            @ApiResponse(
                    responseCode = "503",
                    description = "Expedia API unavailable or circuit breaker open",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = ErrorResponse.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "timestamp": "2024-01-15T10:30:00",
                                        "status": 503,
                                        "error": "Service Unavailable",
                                        "message": "Accommodation booking service temporarily unavailable. Please try again later.",
                                        "path": "/api/v1/expedia/book/room-12345"
                                    }
                                    """)
                    )
            ),
            @ApiResponse(
                    responseCode = "504",
                    description = "Request timeout - Expedia API did not respond within 15 seconds",
                    content = @Content(schema = @Schema(implementation = ErrorResponse.class))
            )
    })
    @PostMapping("/book/{roomId}")
    @RequiresAgent
    public ResponseEntity<?> bookRoom(
            @Parameter(
                    description = "Unique room identifier from search results",
                    required = true,
                    example = "room-12345"
            )
            @PathVariable String roomId,
            @io.swagger.v3.oas.annotations.parameters.RequestBody(
                    description = "Booking details including travelers, payment information, and contact details",
                    required = true,
                    content = @Content(
                            schema = @Schema(implementation = ExpediaBookingRequest.class),
                            examples = @ExampleObject(value = """
                                    {
                                        "travelers": [
                                            {
                                                "givenName": "John",
                                                "surname": "Doe",
                                                "birthDate": "1985-03-15",
                                                "gender": "M"
                                            }
                                        ],
                                        "payment": {
                                            "cardHolderName": "John Doe",
                                            "cardNumber": "4111111111111111",
                                            "expiryMonth": "12",
                                            "expiryYear": "2025",
                                            "cvv": "123",
                                            "billingAddress": "123 Main St",
                                            "billingCity": "Los Angeles",
                                            "billingCountry": "USA",
                                            "billingPostalCode": "90001"
                                        },
                                        "email": "john.doe@example.com",
                                        "phone": "+1-555-0100"
                                    }
                                    """)
                    )
            )
            @Valid @RequestBody ExpediaBookingRequest request,
            @Parameter(
                    description = "Agency ID for wallet operations and billing",
                    required = true,
                    example = "550e8400-e29b-41d4-a716-446655440000"
            )
            @RequestParam UUID agencyId,
            Authentication authentication) {
        
        UUID userId = UUID.fromString(authentication.getName());
        log.info("Booking request for room: {}, agency: {}, user: {}", roomId, agencyId, userId);
        
        try {
            ExpediaBookingResponse response = expediaBookingService.bookRoom(roomId, request, agencyId, userId);
            log.info("Booking completed successfully. Confirmation: {}", response.getConfirmationNumber());
            return ResponseEntity.ok(response);
            
        } catch (InsufficientBalanceException e) {
            log.warn("Insufficient balance for booking room: {}. Agency: {}", roomId, agencyId);
            
            ErrorResponse errorResponse = ErrorResponse.builder()
                    .timestamp(LocalDateTime.now())
                    .status(HttpStatus.PAYMENT_REQUIRED.value())
                    .error("Payment Required")
                    .message("Insufficient wallet balance. Please recharge your wallet.")
                    .path("/api/v1/expedia/book/" + roomId)
                    .traceId(generateTraceId())
                    .build();
            
            return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED).body(errorResponse);
            
        } catch (LinkExpiredException e) {
            log.warn("Link expired for room: {}. Message: {}", roomId, e.getMessage());
            
            ErrorResponse errorResponse = ErrorResponse.builder()
                    .timestamp(LocalDateTime.now())
                    .status(HttpStatus.GONE.value())
                    .error("Gone")
                    .message("Booking session expired. Please search again.")
                    .path("/api/v1/expedia/book/" + roomId)
                    .traceId(generateTraceId())
                    .build();
            
            return ResponseEntity.status(HttpStatus.GONE).body(errorResponse);
            
        } catch (ExpediaServiceException e) {
            log.error("Expedia service error during booking: {}", e.getMessage(), e);
            
            HttpStatus status = switch (e.getErrorType()) {
                case TIMEOUT -> HttpStatus.GATEWAY_TIMEOUT;
                case CIRCUIT_BREAKER_OPEN, SERVICE_UNAVAILABLE -> HttpStatus.SERVICE_UNAVAILABLE;
                default -> HttpStatus.BAD_GATEWAY;
            };
            
            ErrorResponse errorResponse = ErrorResponse.builder()
                    .timestamp(LocalDateTime.now())
                    .status(status.value())
                    .error(status.getReasonPhrase())
                    .message("Accommodation booking service temporarily unavailable. Please try again later.")
                    .path("/api/v1/expedia/book/" + roomId)
                    .traceId(generateTraceId())
                    .build();
            
            return ResponseEntity.status(status).body(errorResponse);
        }
    }
    
    /**
     * Generate unique trace ID for error tracking
     */
    private String generateTraceId() {
        return UUID.randomUUID().toString().substring(0, 8) + "-" +
               UUID.randomUUID().toString().substring(0, 6) + "-" +
               UUID.randomUUID().toString().substring(0, 6);
    }
}
