package com.flightticket.controller;

import com.flightticket.client.amadeus.AmadeusFlightSearchRequest;
import com.flightticket.client.amadeus.AmadeusFlightSearchResponse;
import com.flightticket.dto.modification.*;
import com.flightticket.exception.AmadeusServiceException;
import com.flightticket.exception.InsufficientBalanceException;
import com.flightticket.exception.ModificationNotAllowedException;
import com.flightticket.model.entity.ModificationRecord;
import com.flightticket.repository.ModificationRepository;
import com.flightticket.security.annotation.RequiresAgent;
import com.flightticket.service.ModificationService;
import com.flightticket.service.WalletService;
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.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * REST controller for booking modification operations.
 * 
 * This controller provides endpoints for:
 * - Checking modification eligibility
 * - Searching alternative flights
 * - Calculating modification costs
 * - Executing modifications
 * - Retrieving modification details and history
 * 
 * All endpoints require agent authentication via Keycloak.
 * 
 * Requirements: 1.1, 1.3, 1.4, 3.1, 4.1, 5.1, 9.5
 */
@Slf4j
@RestController
@RequestMapping("/api/modifications")
@RequiredArgsConstructor
public class ModificationController {
    
    private final ModificationService modificationService;
    private final ModificationRepository modificationRepository;
    private final WalletService walletService;
    
    /**
     * Check if a booking is eligible for modification.
     * 
     * This endpoint validates:
     * - Booking status (must be CONFIRMED)
     * - Departure time (not departed, not within 24 hours)
     * - Fare rules (fare class allows modifications)
     * 
     * Returns complete eligibility information including allowed modification types,
     * restrictions, estimated fees, and modification deadline.
     * 
     * @param request The eligibility check request containing booking ID
     * @param authentication The authenticated user
     * @return Eligibility result with allowed modifications and restrictions
     * 
     * Requirements: 1.1, 1.2, 1.3, 1.4, 1.5
     * Property 1: Eligibility Determination Consistency
     * Property 2: Eligibility Response Completeness
     * Property 3: Non-Modifiable Booking Handling
     */
    @PostMapping("/check-eligibility")
    @RequiresAgent
    public ResponseEntity<EligibilityResult> checkEligibility(
            @Valid @RequestBody EligibilityRequest request,
            Authentication authentication) {
        
        log.info("Check eligibility request for booking {} by agent {}", 
                request.getBookingId(), authentication.getName());
        
        try {
            EligibilityResult result = modificationService.checkEligibility(request.getBookingId());
            
            if (result.isEligible()) {
                log.info("Booking {} is eligible for modifications: {}", 
                        request.getBookingId(), result.getAllowedModifications());
                return ResponseEntity.ok(result);
            } else {
                log.info("Booking {} is not eligible for modification: {}", 
                        request.getBookingId(), result.getIneligibilityReason());
                return ResponseEntity.ok(result);
            }
            
        } catch (IllegalArgumentException e) {
            log.error("Invalid booking ID: {}", request.getBookingId(), e);
            throw e;
        } catch (Exception e) {
            log.error("Error checking eligibility for booking {}", request.getBookingId(), e);
            throw new RuntimeException("Failed to check modification eligibility", e);
        }
    }
    
    /**
     * Search for alternative flights for modification purposes.
     * 
     * This endpoint queries the Amadeus API to find alternative flight options
     * based on the provided search criteria (origin, destination, date, etc.).
     * 
     * @param searchRequest Flight search criteria
     * @param authentication The authenticated user
     * @return Alternative flight options with pricing
     * 
     * Requirements: 3.1, 4.1
     */
    @PostMapping("/search-alternatives")
    @RequiresAgent
    public ResponseEntity<AmadeusFlightSearchResponse> searchAlternatives(
            @Valid @RequestBody AmadeusFlightSearchRequest searchRequest,
            Authentication authentication) {
        
        log.info("Search alternatives request from {} to {} on {} by agent {}", 
                searchRequest.getOriginLocationCode(),
                searchRequest.getDestinationLocationCode(),
                searchRequest.getDepartureDate(),
                authentication.getName());
        
        try {
            AmadeusFlightSearchResponse response = modificationService.searchAlternatives(searchRequest);
            
            log.info("Found {} alternative flights", 
                    response.getData() != null ? response.getData().size() : 0);
            
            return ResponseEntity.ok(response);
            
        } catch (AmadeusServiceException e) {
            log.error("Amadeus API error while searching alternatives", e);
            
            // Return 502 Bad Gateway for Amadeus API errors
            if (e.getErrorType() != null) {
                switch (e.getErrorType()) {
                    case TIMEOUT:
                    case SERVICE_UNAVAILABLE:
                        throw new AmadeusServiceException("Amadeus API unavailable", 
                                AmadeusServiceException.ErrorType.SERVICE_UNAVAILABLE);
                    default:
                        throw e;
                }
            }
            throw e;
            
        } catch (Exception e) {
            log.error("Error searching alternative flights", e);
            throw new RuntimeException("Failed to search alternative flights", e);
        }
    }
    
    /**
     * Calculate the cost of a modification.
     * 
     * This endpoint calculates:
     * - Modification fee (from fare rules)
     * - Fare difference (new fare - original fare)
     * - Total cost (fee + fare difference)
     * - Wallet impact (DEBIT or CREDIT, new balance)
     * 
     * @param request Cost calculation request
     * @param authentication The authenticated user
     * @return Complete modification cost details including wallet impact
     * 
     * Requirements: 6.1, 6.2, 6.3
     * Property 9: Modification Cost Calculation
     * Property 10: Credit Calculation for Fare Decreases
     * Property 11: Fare Difference Calculation
     */
    @PostMapping("/calculate-cost")
    @RequiresAgent
    public ResponseEntity<ModificationCost> calculateCost(
            @Valid @RequestBody CostCalculationRequest request,
            Authentication authentication) {
        
        log.info("Calculate cost request for booking {} with new fare {} by agent {}", 
                request.getBookingId(), request.getNewFare(), authentication.getName());
        
        try {
            ModificationCost cost = modificationService.calculateCost(
                    request.getBookingId(),
                    request.getNewFare(),
                    request.getModificationType()
            );
            
            log.info("Calculated modification cost for booking {}: total={}, type={}", 
                    request.getBookingId(), cost.getTotalCost(), cost.getTransactionType());
            
            return ResponseEntity.ok(cost);
            
        } catch (IllegalArgumentException e) {
            log.error("Invalid cost calculation request: {}", e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            log.error("Error calculating modification cost for booking {}", 
                    request.getBookingId(), e);
            throw new RuntimeException("Failed to calculate modification cost", e);
        }
    }
    
    /**
     * Execute a booking modification.
     * 
     * This endpoint orchestrates the complete modification workflow:
     * 1. Validate eligibility
     * 2. Calculate cost
     * 3. Check wallet balance
     * 4. Submit to Amadeus API
     * 5. Process wallet transaction
     * 6. Create modification record
     * 7. Log success
     * 
     * The entire operation is transactional. If any step fails:
     * - Wallet transactions are rolled back
     * - Booking state is preserved
     * - Failure is logged
     * 
     * @param request Modification execution request
     * @param authentication The authenticated user
     * @return Modification response with updated booking details
     * 
     * Requirements: 1.1, 2.1, 3.1, 4.1, 5.1, 6.3, 6.4, 7.4
     * Property 12: Wallet Balance Validation Before Processing
     * Property 13: Insufficient Balance Handling
     * Property 14: Transaction Record Creation
     * Property 17: API Rejection Preserves Original State
     * Property 20: Local State Synchronization
     * Property 21: Transaction Rollback on API Failure
     */
    @PostMapping("/execute")
    @RequiresAgent
    public ResponseEntity<ModificationResponse> executeModification(
            @Valid @RequestBody ModificationExecutionRequest request,
            Authentication authentication) {
        
        UUID agentId = UUID.fromString(authentication.getName());
        
        log.info("Execute modification request for booking {} by agent {}", 
                request.getBookingId(), agentId);
        
        try {
            // Execute the modification
            ModificationRecord record = modificationService.executeModification(
                    request.getBookingId(),
                    request.getModificationRequest(),
                    request.getNewFare(),
                    request.getModificationType(),
                    agentId
            );
            
            // Get updated wallet balance
            UUID agencyId = record.getAgencyId();
            var wallet = walletService.getWalletByAgency(agencyId);
            
            // Build response
            ModificationResponse response = ModificationResponse.builder()
                    .modificationId(record.getModificationId())
                    .bookingId(record.getBookingId())
                    .status(record.getStatus())
                    .walletTransactionId(record.getWalletTransactionId())
                    .newWalletBalance(wallet.getBalance())
                    .amadeusConfirmationCode(record.getAmadeusConfirmationCode())
                    .updatedPnr(record.getBooking() != null ? record.getBooking().getPnr() : null)
                    .message("Modification completed successfully")
                    .build();
            
            log.info("Modification {} completed successfully for booking {}", 
                    record.getModificationId(), request.getBookingId());
            
            return ResponseEntity.ok(response);
            
        } catch (InsufficientBalanceException e) {
            log.warn("Insufficient balance for modification of booking {}: {}", 
                    request.getBookingId(), e.getMessage());
            
            // Return 402 Payment Required for insufficient balance
            return ResponseEntity.status(HttpStatus.PAYMENT_REQUIRED).build();
            
        } catch (ModificationNotAllowedException e) {
            log.warn("Modification not allowed for booking {}: {}", 
                    request.getBookingId(), e.getMessage());
            
            // Return 422 Unprocessable Entity for business rule violations
            return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
            
        } catch (AmadeusServiceException e) {
            log.error("Amadeus API error during modification of booking {}", 
                    request.getBookingId(), e);
            
            // Return 502 Bad Gateway or 503 Service Unavailable based on error type
            if (e.getErrorType() != null) {
                switch (e.getErrorType()) {
                    case TIMEOUT:
                    case SERVICE_UNAVAILABLE:
                        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
                    default:
                        return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build();
                }
            }
            return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build();
            
        } catch (Exception e) {
            log.error("Unexpected error during modification of booking {}", 
                    request.getBookingId(), e);
            
            // Return 500 Internal Server Error for unexpected errors
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
    
    /**
     * Get modification details by modification ID.
     * 
     * @param modificationId The modification record ID
     * @param authentication The authenticated user
     * @return Modification details
     * 
     * Requirements: 8.2, 8.5
     */
    @GetMapping("/{modificationId}")
    @RequiresAgent
    public ResponseEntity<ModificationHistoryResponse> getModificationDetails(
            @PathVariable String modificationId,
            Authentication authentication) {
        
        log.info("Get modification details request for {} by agent {}", 
                modificationId, authentication.getName());
        
        try {
            ModificationRecord record = modificationRepository.findByModificationId(modificationId)
                    .orElseThrow(() -> new IllegalArgumentException("Modification not found: " + modificationId));
            
            ModificationHistoryResponse response = mapToHistoryResponse(record);
            
            return ResponseEntity.ok(response);
            
        } catch (IllegalArgumentException e) {
            log.error("Modification not found: {}", modificationId);
            return ResponseEntity.notFound().build();
        } catch (Exception e) {
            log.error("Error retrieving modification details for {}", modificationId, e);
            throw new RuntimeException("Failed to retrieve modification details", e);
        }
    }
    
    /**
     * Get modification history for a booking.
     * 
     * Returns all modification attempts for a booking, ordered by timestamp
     * in descending order (most recent first).
     * 
     * @param bookingId The booking ID
     * @param authentication The authenticated user
     * @return List of modification history records
     * 
     * Requirements: 8.1, 8.2, 8.4, 8.5
     * Property 22: Modification Attempt Logging
     * Property 23: State Change Recording
     * Property 24: Failure Reason Logging
     * Property 25: Audit Log Ordering
     */
    @GetMapping("/booking/{bookingId}/history")
    @RequiresAgent
    public ResponseEntity<List<ModificationHistoryResponse>> getModificationHistory(
            @PathVariable UUID bookingId,
            Authentication authentication) {
        
        log.info("Get modification history request for booking {} by agent {}", 
                bookingId, authentication.getName());
        
        try {
            List<ModificationRecord> records = modificationRepository
                    .findByBookingIdOrderByRequestedAtDesc(bookingId);
            
            List<ModificationHistoryResponse> history = records.stream()
                    .map(this::mapToHistoryResponse)
                    .collect(Collectors.toList());
            
            log.info("Retrieved {} modification records for booking {}", 
                    history.size(), bookingId);
            
            return ResponseEntity.ok(history);
            
        } catch (Exception e) {
            log.error("Error retrieving modification history for booking {}", bookingId, e);
            throw new RuntimeException("Failed to retrieve modification history", e);
        }
    }
    
    /**
     * Helper method to map ModificationRecord entity to ModificationHistoryResponse DTO.
     */
    private ModificationHistoryResponse mapToHistoryResponse(ModificationRecord record) {
        return ModificationHistoryResponse.builder()
                .modificationId(record.getModificationId())
                .bookingId(record.getBookingId())
                .agentId(record.getAgentId())
                .type(record.getType())
                .status(record.getStatus())
                .modificationCost(record.getModificationCost())
                .currency(record.getCurrency())
                .requestedAt(record.getRequestedAt())
                .completedAt(record.getCompletedAt())
                .failureReason(record.getFailureReason())
                .beforeState(record.getBeforeState())
                .afterState(record.getAfterState())
                .build();
    }
}
