package com.flightticket.service;

import com.flightticket.client.expedia.ExpediaClient;
import com.flightticket.client.expedia.ExpediaLinkCache;
import com.flightticket.client.expedia.LinkType;
import com.flightticket.dto.expedia.*;
import com.flightticket.dto.wallet.TransactionResult;
import com.flightticket.exception.PriceChangedException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.UUID;

/**
 * Implementation of ExpediaBookingService
 * Orchestrates the three-step booking workflow: Shop → Price Check → Book
 * Integrates with wallet service for payment processing using Hold & Resume pattern
 * 
 * Requirements: 2.1, 3.1, 4.1, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 6.6
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ExpediaBookingServiceImpl implements ExpediaBookingService {
    
    private final ExpediaClient expediaClient;
    private final ExpediaLinkCache linkCache;
    private final WalletService walletService;
    
    // TTL constants (in seconds)
    private static final long PRICE_CHECK_TTL = 900; // 15 minutes
    private static final long BOOK_TTL = 900; // 15 minutes
    private static final long RESUME_TTL = 1800; // 30 minutes
    private static final long CANCEL_TTL = 1800; // 30 minutes
    
    /**
     * Step 1: Search for available accommodations
     * Calls Expedia Shop API and caches price_check links for each room
     * 
     * Requirements: 2.1, 2.2, 2.3, 2.4
     */
    @Override
    public List<ExpediaShopResponse.ExpediaRoom> searchAvailability(ExpediaShopRequest request) {
        log.info("Searching availability for location: {}, check-in: {}, check-out: {}", 
            request.getLocation(), request.getCheckIn(), request.getCheckOut());
        
        // Call Expedia Shop API
        ExpediaShopResponse response = expediaClient.searchAvailability(request);
        
        // Extract and cache price_check links for each room
        if (response.getRooms() != null) {
            for (ExpediaShopResponse.ExpediaRoom room : response.getRooms()) {
                if (room.getLinks() != null) {
                    String priceCheckLink = room.getLinks().getPriceCheckLink();
                    if (priceCheckLink != null) {
                        linkCache.storePriceCheckLink(room.getRoomId(), priceCheckLink, PRICE_CHECK_TTL);
                        log.debug("Cached price_check link for room: {}", room.getRoomId());
                    } else {
                        log.warn("No price_check link found for room: {}", room.getRoomId());
                    }
                }
            }
            log.info("Found {} available rooms", response.getRooms().size());
        } else {
            log.info("No rooms found for the search criteria");
        }
        
        return response.getRooms();
    }
    
    /**
     * Step 2: Validate room availability and lock price
     * Retrieves cached price_check link and calls Expedia Price Check API
     * If price matches, caches the book link for the next step
     * If price has changed, throws PriceChangedException with old and new prices
     * 
     * Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 7.1, 7.2, 7.3, 7.5
     */
    @Override
    public ExpediaPriceCheckResponse validatePrice(String roomId) throws PriceChangedException {
        log.info("Validating price for room: {}", roomId);
        
        // Retrieve cached price_check link
        String priceCheckLink = linkCache.getLink(roomId, LinkType.PRICE_CHECK);
        log.debug("Retrieved price_check link for room: {}", roomId);
        
        // Call Expedia Price Check API
        ExpediaPriceCheckResponse response = expediaClient.priceCheck(priceCheckLink);
        
        // Check if price has changed
        if (response.isPriceChanged()) {
            log.warn("Price changed for room: {}. Original: {} {}, New: {} {}", 
                roomId,
                response.getOriginalPrice().getCurrency(),
                response.getOriginalPrice().getTotal(),
                response.getCurrentPrice().getCurrency(),
                response.getCurrentPrice().getTotal());
            
            throw new PriceChangedException(
                response.getOriginalPrice().getTotal(),
                response.getCurrentPrice().getTotal(),
                response.getCurrentPrice().getCurrency()
            );
        }
        
        // Price matched - cache the book link
        if (response.isPriceMatched() && response.getLinks() != null) {
            String bookLink = response.getLinks().getBookLink();
            if (bookLink != null) {
                linkCache.storeBookLink(roomId, bookLink, BOOK_TTL);
                log.info("Price validated and book link cached for room: {}", roomId);
            } else {
                log.warn("No book link found in price check response for room: {}", roomId);
            }
        }
        
        return response;
    }
    
    /**
     * Step 3: Book room with hold and payment processing
     * Orchestrates the complete booking flow:
     * 1. Retrieves cached book link from price check step
     * 2. Creates hold booking with Expedia (hold=true)
     * 3. Reserves funds in the agency's wallet
     * 4. Debits the wallet for the booking amount
     * 5. If payment succeeds: resumes booking with Expedia to confirm
     * 6. If payment fails: cancels the hold booking
     * 7. Cleans up cached links after completion
     * 
     * Requirements: 4.1, 4.2, 4.3, 4.4, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 6.6
     */
    @Override
    public ExpediaBookingResponse bookRoom(String roomId, ExpediaBookingRequest request, UUID agencyId, UUID userId) {
        log.info("Starting booking process for room: {}, agency: {}, user: {}", roomId, agencyId, userId);
        
        // Step 1: Retrieve cached book link
        String bookLink = linkCache.getLink(roomId, LinkType.BOOK);
        log.debug("Retrieved book link for room: {}", roomId);
        
        // Step 2: Create hold booking
        ExpediaHoldResponse holdResponse = expediaClient.createHoldBooking(bookLink, request);
        String bookingId = holdResponse.getBookingId();
        log.info("Hold booking created with ID: {}", bookingId);
        
        // Step 3: Cache resume and cancel links
        if (holdResponse.getLinks() != null) {
            String resumeLink = holdResponse.getLinks().getResumeLink();
            String cancelLink = holdResponse.getLinks().getCancelLink();
            
            if (resumeLink != null) {
                linkCache.storeResumeLink(bookingId, resumeLink, RESUME_TTL);
                log.debug("Cached resume link for booking: {}", bookingId);
            }
            
            if (cancelLink != null) {
                linkCache.storeCancelLink(bookingId, cancelLink, CANCEL_TTL);
                log.debug("Cached cancel link for booking: {}", bookingId);
            }
        }
        
        try {
            // Step 4: Reserve funds in wallet
            log.info("Reserving funds for booking: {}, amount: {} {}", 
                bookingId, holdResponse.getPrice().getCurrency(), holdResponse.getPrice().getTotal());
            
            TransactionResult reserveResult = walletService.reserveFunds(
                agencyId, 
                holdResponse.getPrice().getTotal(), 
                bookingId
            );
            
            if (!reserveResult.isSuccess()) {
                log.error("Failed to reserve funds for booking: {}. Reason: {}", bookingId, reserveResult.getMessage());
                cancelHoldBooking(bookingId);
                throw new RuntimeException("Failed to reserve funds: " + reserveResult.getMessage());
            }
            
            log.info("Funds reserved successfully for booking: {}", bookingId);
            
            // Step 5: Debit wallet
            log.info("Debiting wallet for booking: {}", bookingId);
            TransactionResult debitResult = walletService.debitWallet(
                agencyId, 
                holdResponse.getPrice().getTotal(), 
                bookingId, 
                userId
            );
            
            if (!debitResult.isSuccess()) {
                log.error("Failed to debit wallet for booking: {}. Reason: {}", bookingId, debitResult.getMessage());
                // Cancel the hold booking
                cancelHoldBooking(bookingId);
                throw new RuntimeException("Payment failed: " + debitResult.getMessage());
            }
            
            log.info("Wallet debited successfully for booking: {}", bookingId);
            
            // Step 6: Resume booking to confirm
            String resumeLink = linkCache.getLink(bookingId, LinkType.RESUME);
            ExpediaBookingResponse bookingResponse = expediaClient.resumeBooking(resumeLink);
            
            log.info("Booking confirmed with confirmation number: {}", bookingResponse.getConfirmationNumber());
            
            // Step 7: Clean up cached links
            linkCache.cleanupLinks(bookingId);
            log.debug("Cleaned up cached links for booking: {}", bookingId);
            
            return bookingResponse;
            
        } catch (Exception e) {
            log.error("Error during booking process for booking: {}. Error: {}", bookingId, e.getMessage(), e);
            
            // Attempt to cancel the hold booking
            try {
                cancelHoldBooking(bookingId);
            } catch (Exception cancelException) {
                log.error("Failed to cancel hold booking: {}. Error: {}", bookingId, cancelException.getMessage());
            }
            
            // Clean up cached links
            try {
                linkCache.cleanupLinks(bookingId);
            } catch (Exception cleanupException) {
                log.error("Failed to cleanup links for booking: {}. Error: {}", bookingId, cleanupException.getMessage());
            }
            
            throw e;
        }
    }
    
    /**
     * Helper method to cancel a hold booking
     * Retrieves the cancel link from cache and calls the Expedia cancel endpoint
     */
    private void cancelHoldBooking(String bookingId) {
        try {
            String cancelLink = linkCache.getLink(bookingId, LinkType.CANCEL);
            expediaClient.cancelHoldBooking(cancelLink);
            log.info("Hold booking cancelled: {}", bookingId);
        } catch (Exception e) {
            log.error("Failed to cancel hold booking: {}. Error: {}", bookingId, e.getMessage());
            // Don't throw - allow the hold to expire naturally
        }
    }
}
