package com.emonster.taroaichat.service;

import com.emonster.taroaichat.domain.Donation;
import com.emonster.taroaichat.domain.UserProfile;
import com.emonster.taroaichat.domain.TarotSession;
import com.emonster.taroaichat.domain.enumeration.PaymentStatus;
import com.emonster.taroaichat.repository.DonationRepository;
import com.emonster.taroaichat.repository.UserProfileRepository;
import com.emonster.taroaichat.repository.TarotSessionRepository;
import com.emonster.taroaichat.service.dto.DonationDTO;
import com.emonster.taroaichat.service.dto.DonationCreatePaymentIntentDTO;
import com.emonster.taroaichat.service.dto.DonationConfirmPaymentDTO;
import com.emonster.taroaichat.service.mapper.DonationMapper;
import com.emonster.taroaichat.service.stripe.StripeService;
import com.emonster.taroaichat.web.rest.errors.BadRequestAlertException;
import com.stripe.model.PaymentIntent;
import com.stripe.model.Refund;

import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service Implementation for managing {@link com.emonster.taroaichat.domain.Donation}.
 */
@Service
@Transactional
public class DonationService {

    private static final Logger LOG = LoggerFactory.getLogger(DonationService.class);

    private final DonationRepository donationRepository;
    private final DonationMapper donationMapper;
    private final StripeService stripeService;
    private final UserProfileRepository userProfileRepository;
    private final TarotSessionRepository tarotSessionRepository;
    private final UserProfileService userProfileService;

    public DonationService(
        DonationRepository donationRepository,
        DonationMapper donationMapper,
        StripeService stripeService,
        UserProfileRepository userProfileRepository,
        TarotSessionRepository tarotSessionRepository,
        UserProfileService userProfileService
    ) {
        this.donationRepository = donationRepository;
        this.donationMapper = donationMapper;
        this.stripeService = stripeService;
        this.userProfileRepository = userProfileRepository;
        this.tarotSessionRepository = tarotSessionRepository;
        this.userProfileService = userProfileService;
    }

    /**
     * Save a donation.
     *
     * @param donationDTO the entity to save.
     * @return the persisted entity.
     */
    public DonationDTO save(DonationDTO donationDTO) {
        LOG.debug("Request to save Donation : {}", donationDTO);
        Donation donation = donationMapper.toEntity(donationDTO);
        donation = donationRepository.save(donation);
        return donationMapper.toDto(donation);
    }

    /**
     * Update a donation.
     *
     * @param donationDTO the entity to save.
     * @return the persisted entity.
     */
    public DonationDTO update(DonationDTO donationDTO) {
        LOG.debug("Request to update Donation : {}", donationDTO);
        Donation donation = donationMapper.toEntity(donationDTO);
        donation.setIsPersisted();
        donation = donationRepository.save(donation);
        return donationMapper.toDto(donation);
    }

    /**
     * Partially update a donation.
     *
     * @param donationDTO the entity to update partially.
     * @return the persisted entity.
     */
    public Optional<DonationDTO> partialUpdate(DonationDTO donationDTO) {
        LOG.debug("Request to partially update Donation : {}", donationDTO);

        return donationRepository
            .findById(donationDTO.getId())
            .map(existingDonation -> {
                donationMapper.partialUpdate(existingDonation, donationDTO);

                return existingDonation;
            })
            .map(donationRepository::save)
            .map(donationMapper::toDto);
    }

    /**
     * Get all the donations with eager load of many-to-many relationships.
     *
     * @return the list of entities.
     */
    public Page<DonationDTO> findAllWithEagerRelationships(Pageable pageable) {
        return donationRepository.findAllWithEagerRelationships(pageable).map(donationMapper::toDto);
    }

    /**
     * Get one donation by id.
     *
     * @param id the id of the entity.
     * @return the entity.
     */
    @Transactional(readOnly = true)
    public Optional<DonationDTO> findOne(Long id) {
        LOG.debug("Request to get Donation : {}", id);
        return donationRepository.findOneWithEagerRelationships(id).map(donationMapper::toDto);
    }

    /**
     * Delete the donation by id.
     *
     * @param id the id of the entity.
     */
    public void delete(Long id) {
        LOG.debug("Request to delete Donation : {}", id);
        donationRepository.deleteById(id);
    }

    // === PAYMENT PROCESSING METHODS ===

    /**
     * Create a donation payment intent.
     *
     * @param userProfileId the user making the donation
     * @param amount the donation amount
     * @param currency the currency code (default USD)
     * @param sessionId optional tarot session ID for context
     * @return the payment intent client secret
     */
    public String createDonationPaymentIntent(Long userProfileId, BigDecimal amount, String currency, Long sessionId) {
        LOG.debug("Creating donation payment intent for user: {} amount: {} {}", userProfileId, amount, currency);

        // Validate amount
        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BadRequestAlertException("Donation amount must be greater than zero", "Donation", "INVALID_AMOUNT");
        }

        // Get user profile
        UserProfile userProfile = userProfileRepository.findById(userProfileId)
            .orElseThrow(() -> new BadRequestAlertException("User profile not found", "Donation", "USER_PROFILE_NOT_FOUND"));

        // Validate session if provided
        TarotSession session = null;
        if (sessionId != null) {
            session = tarotSessionRepository.findById(sessionId)
                .orElseThrow(() -> new BadRequestAlertException("Tarot session not found", "Donation", "SESSION_NOT_FOUND"));

            // Ensure session belongs to user
            if (!session.getUserProfile().getId().equals(userProfileId)) {
                throw new BadRequestAlertException("Session does not belong to user", "Donation", "UNAUTHORIZED_SESSION_ACCESS");
            }
        }

        // Create payment intent via Stripe
        String clientSecret = stripeService.createDonationPaymentIntent(userProfile, amount, currency, sessionId);

        LOG.debug("Donation payment intent created successfully for user: {}", userProfileId);
        return clientSecret;
    }

    /**
     * Confirm and save a completed donation.
     *
     * @param userProfileId the user making the donation
     * @param paymentIntentId the Stripe payment intent ID
     * @param sessionId optional tarot session ID for context
     * @return the saved donation DTO
     */
    public DonationDTO confirmDonation(Long userProfileId, String paymentIntentId, Long sessionId) {
        LOG.debug("Confirming donation for user: {} paymentIntent: {}", userProfileId, paymentIntentId);

        // Get user profile
        UserProfile userProfile = userProfileRepository.findById(userProfileId)
            .orElseThrow(() -> new BadRequestAlertException("User profile not found", "Donation", "USER_PROFILE_NOT_FOUND"));

        // Get payment intent from Stripe and validate
        PaymentIntent paymentIntent = stripeService.getPaymentIntent(paymentIntentId, userProfileId);

        // Validate payment was successful
        if (!"succeeded".equals(paymentIntent.getStatus())) {
            throw new BadRequestAlertException(
                "Payment intent status is not succeeded: " + paymentIntent.getStatus(),
                "Donation",
                "PAYMENT_NOT_COMPLETED"
            );
        }

        // Check if donation already exists for this payment intent
        Optional<Donation> existingDonation = donationRepository.findByStripePaymentIntentId(paymentIntentId);
        if (existingDonation.isPresent()) {
            Donation donation = existingDonation.get();
            
            // If already completed, just return it
            if (donation.getStatus() == PaymentStatus.COMPLETED) {
                LOG.warn("Donation already completed for payment intent: {}", paymentIntentId);
                return donationMapper.toDto(donation);
            }
            
            // If pending, update to completed
            if (donation.getStatus() == PaymentStatus.PENDING) {
                donation.setStatus(PaymentStatus.COMPLETED);
                donation.setAmount(new BigDecimal(paymentIntent.getAmount()).divide(new BigDecimal("100"))); // Update with actual amount from Stripe
                donation.setCurrency(paymentIntent.getCurrency().toUpperCase());
                donation = donationRepository.save(donation);
                
                LOG.debug("Updated existing PENDING donation to COMPLETED: {} for payment intent: {}", donation.getId(), paymentIntentId);
                return donationMapper.toDto(donation);
            }
        }

        // If no existing donation found, create a new one (fallback case)
        // Get session if provided
        TarotSession session = null;
        if (sessionId != null) {
            session = tarotSessionRepository.findById(sessionId)
                .orElseThrow(() -> new BadRequestAlertException("Tarot session not found", "Donation", "SESSION_NOT_FOUND"));
        }

        // Create donation entity
        Donation donation = new Donation();
        donation.setAmount(new BigDecimal(paymentIntent.getAmount()).divide(new BigDecimal("100"))); // Convert from cents
        donation.setCurrency(paymentIntent.getCurrency().toUpperCase());
        donation.setStripePaymentIntentId(paymentIntentId);
        donation.setStatus(PaymentStatus.COMPLETED);
        donation.setUserProfile(userProfile);
        donation.setSession(session);

        // Save donation
        donation = donationRepository.save(donation);

        LOG.debug("Donation confirmed and saved: {} for user: {}", donation.getId(), userProfileId);
        return donationMapper.toDto(donation);
    }

    /**
     * Get donations for a specific user.
     *
     * @param userProfileId the user profile ID
     * @param pageable the pagination information
     * @return the page of donations
     */
    @Transactional(readOnly = true)
    public Page<DonationDTO> findByUserProfile(String userProfileId, Pageable pageable) {
        LOG.debug("Getting donations for user: {}", userProfileId);
        return donationRepository.findByUserProfileId(userProfileId, pageable).map(donationMapper::toDto);
    }

    /**
     * Get donations for a specific session.
     *
     * @param sessionId the session ID
     * @param pageable the pagination information
     * @return the page of donations
     */
    @Transactional(readOnly = true)
    public Page<DonationDTO> findBySession(Long sessionId, Pageable pageable) {
        LOG.debug("Getting donations for session: {}", sessionId);
        return donationRepository.findBySessionId(sessionId, pageable).map(donationMapper::toDto);
    }

    /**
     * Process a refund for a donation.
     *
     * @param donationId the donation ID
     * @param amount optional partial refund amount
     * @param reason the refund reason
     * @return the updated donation DTO
     */
    public DonationDTO refundDonation(Long donationId, BigDecimal amount, String reason) {
        LOG.debug("Processing refund for donation: {} amount: {}", donationId, amount);

        Donation donation = donationRepository.findById(donationId)
            .orElseThrow(() -> new BadRequestAlertException("Donation not found", "Donation", "DONATION_NOT_FOUND"));

        // Validate donation is in completed status
        if (donation.getStatus() != PaymentStatus.COMPLETED) {
            throw new BadRequestAlertException(
                "Can only refund completed donations. Current status: " + donation.getStatus(),
                "Donation",
                "INVALID_REFUND_STATUS"
            );
        }

        // Process refund via Stripe
        Refund refund = stripeService.refundDonation(donation.getStripePaymentIntentId(), amount, reason);

        // Update donation status
        donation.setStatus(PaymentStatus.REFUNDED);
        donation = donationRepository.save(donation);

        LOG.debug("Donation refund processed: {} refundId: {}", donationId, refund.getId());
        return donationMapper.toDto(donation);
    }

    // === CURRENT USER METHODS ===

    /**
     * Create a donation payment intent for the current user.
     *
     * @param request the payment intent request
     * @return the payment intent client secret
     */
    public String createDonationPaymentIntentForCurrentUser(DonationCreatePaymentIntentDTO request) {
        LOG.debug("Creating donation payment intent for current user: {}", request);

        Long currentUserProfileId = userProfileService.getCurrentUserProfileId()
            .orElseThrow(() -> new BadRequestAlertException("Current user profile not found", "Donation", "USER_PROFILE_NOT_FOUND"));

        return createDonationPaymentIntent(currentUserProfileId, request.getAmount(), "USD", request.getSessionId());
    }

    /**
     * Create a payment intent and pending donation record for the current user.
     *
     * @param request the donation processing request
     * @return response with client secret and donation info
     */
    public Map<String, Object> createDonationPaymentIntentWithRecordForCurrentUser(DonationCreatePaymentIntentDTO request) {
        LOG.debug("Creating donation payment intent with record for current user: {}", request);

        Long currentUserProfileId = userProfileService.getCurrentUserProfileId()
            .orElseThrow(() -> new BadRequestAlertException("Current user profile not found", "Donation", "USER_PROFILE_NOT_FOUND"));

        // Create payment intent through Stripe
        String clientSecret = createDonationPaymentIntent(currentUserProfileId, request.getAmount(), "USD", request.getSessionId());
        
        // Extract payment intent ID from client secret (format: pi_xxx_secret_yyy)
        String paymentIntentId = clientSecret.split("_secret_")[0];
        
        // Create donation record with PENDING status
        UserProfile userProfile = userProfileRepository.findById(currentUserProfileId)
            .orElseThrow(() -> new BadRequestAlertException("User profile not found", "Donation", "USER_PROFILE_NOT_FOUND"));

        TarotSession session = null;
        if (request.getSessionId() != null) {
            session = tarotSessionRepository.findById(request.getSessionId())
                .orElseThrow(() -> new BadRequestAlertException("Tarot session not found", "Donation", "SESSION_NOT_FOUND"));
        }

        // Create donation with PENDING status - will be updated to COMPLETED when payment succeeds
        Donation donation = new Donation();
        donation.setAmount(request.getAmount());
        donation.setCurrency("USD");
        donation.setStripePaymentIntentId(paymentIntentId);
        donation.setStatus(PaymentStatus.PENDING); // PENDING until Stripe confirms
        donation.setUserProfile(userProfile);
        donation.setSession(session);

        donation = donationRepository.save(donation);
        
        LOG.debug("Donation created with PENDING status: {} for user: {}", donation.getId(), currentUserProfileId);
        
        return Map.of(
            "clientSecret", clientSecret,
            "donation", donationMapper.toDto(donation)
        );
    }

    /**
     * Confirm a donation payment for the current user.
     *
     * @param request the payment confirmation request
     * @return the saved donation DTO
     */
    public DonationDTO confirmDonationForCurrentUser(DonationConfirmPaymentDTO request) {
        LOG.debug("Confirming donation for current user: {}", request);

        Long currentUserProfileId = userProfileService.getCurrentUserProfileId()
            .orElseThrow(() -> new BadRequestAlertException("Current user profile not found", "Donation", "USER_PROFILE_NOT_FOUND"));

        return confirmDonation(currentUserProfileId, request.getPaymentIntentId(), request.getSessionId());
    }

    /**
     * Get donations for the current user.
     *
     * @param pageable the pagination information
     * @return the page of donations
     */
    @Transactional(readOnly = true)
    public Page<DonationDTO> findByCurrentUser(Pageable pageable) {
        LOG.debug("Getting donations for current user");

        Long currentUserProfileId = userProfileService.getCurrentUserProfileId()
            .orElseThrow(() -> new BadRequestAlertException("Current user profile not found", "Donation", "USER_PROFILE_NOT_FOUND"));

        return findByUserProfile(currentUserProfileId.toString(), pageable);
    }
}
