package com.rit.canteen.sales.service;

import com.razorpay.Order;
import com.razorpay.Payment;
import com.rit.canteen.sales.model.OrderItem;
import com.rit.canteen.sales.model.PaymentSession;
import com.rit.canteen.sales.model.User;
import com.rit.canteen.sales.repository.PaymentSessionRepository;
import com.rit.canteen.sales.repository.TokenTransactionRepository;
import com.rit.canteen.sales.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;

@Service
public class PaymentService {

    private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
    private static final BigDecimal MIN_TOPUP = new BigDecimal("50");
    private static final BigDecimal MAX_TOPUP = new BigDecimal("5000");

    @Autowired
    private RazorpayService razorpayService;

    @Autowired
    private PaymentSessionRepository paymentSessionRepository;

    @Autowired
    private OrderPlacementService orderPlacementService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private TokenTransactionRepository tokenTransactionRepository;

    @Autowired
    private UserRepository userRepository;

    public Map<String, Object> createWalletTopupSession(Long userId, BigDecimal amountInr) throws Exception {
        ensureConfigured();
        if (amountInr == null || amountInr.compareTo(MIN_TOPUP) < 0) {
            throw new IllegalArgumentException("Minimum top up amount is ₹50");
        }
        if (amountInr.compareTo(MAX_TOPUP) > 0) {
            throw new IllegalArgumentException("Maximum top up per transaction is ₹5,000");
        }
        // Normalize to 2 decimals
        amountInr = amountInr.setScale(2, RoundingMode.HALF_UP);
        if (amountInr.stripTrailingZeros().scale() > 0) {
            // Ritz tokens are whole units
            throw new IllegalArgumentException("Top up amount must be a whole number of rupees");
        }

        User user = userRepository.findById(userId)
            .orElseThrow(() -> new IllegalArgumentException("User not found"));

        String receipt = "TOP-" + userId + "-" + System.currentTimeMillis();
        Map<String, String> notes = Map.of(
            "purpose", "WALLET_TOPUP",
            "userId", String.valueOf(userId)
        );

        Order rzOrder = razorpayService.createOrder(amountInr, receipt, notes);

        PaymentSession session = new PaymentSession();
        session.setUserId(userId);
        session.setPurpose(PaymentSession.Purpose.WALLET_TOPUP);
        session.setStatus(PaymentSession.Status.CREATED);
        session.setAmountInr(amountInr);
        session.setCurrency("INR");
        session.setRazorpayOrderId(rzOrder.get("id"));
        session.setCreatedAt(LocalDateTime.now());
        session.setUpdatedAt(LocalDateTime.now());
        paymentSessionRepository.save(session);

        return checkoutPayload(session, user);
    }

    @SuppressWarnings("unchecked")
    public Map<String, Object> createOrderPaymentSession(Long userId, Map<String, Object> orderRequest) throws Exception {
        ensureConfigured();
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new IllegalArgumentException("User not found"));

        // Build temporary order for validation
        String payloadJson = orderPlacementService.serializeOrderPayload(orderRequest);
        com.rit.canteen.sales.model.Order draft =
            orderPlacementService.buildOrderFromPayload(payloadJson, userId, "RAZORPAY");

        BigDecimal serverTotal = orderPlacementService.calculateServerTotal(draft.getItems());
        if (serverTotal.compareTo(BigDecimal.ONE) < 0) {
            throw new IllegalArgumentException("Order total must be at least ₹1");
        }

        List<Map<String, Object>> stockConflicts = orderPlacementService.checkStock(draft.getItems());
        if (!stockConflicts.isEmpty()) {
            Map<String, Object> err = new LinkedHashMap<>();
            err.put("success", false);
            err.put("errorType", "STOCK_ERROR");
            err.put("message", "Some items are no longer available in the requested quantity.");
            err.put("conflicts", stockConflicts);
            return err;
        }

        // Persist validated total + items into payload
        orderRequest.put("totalAmount", serverTotal);
        List<Map<String, Object>> normalizedItems = new ArrayList<>();
        for (OrderItem item : draft.getItems()) {
            Map<String, Object> m = new LinkedHashMap<>();
            m.put("productId", item.getProductId());
            m.put("productName", item.getProductName());
            m.put("price", item.getPrice());
            m.put("quantity", item.getQuantity());
            m.put("stallId", item.getStallId());
            m.put("stallName", item.getStallName());
            normalizedItems.add(m);
        }
        orderRequest.put("items", normalizedItems);
        orderRequest.put("orderType", orderRequest.getOrDefault("orderType", "MY_ORDER"));
        payloadJson = orderPlacementService.serializeOrderPayload(orderRequest);

        String receipt = "ORD-" + userId + "-" + System.currentTimeMillis();
        Map<String, String> notes = Map.of(
            "purpose", "ORDER",
            "userId", String.valueOf(userId)
        );

        Order rzOrder = razorpayService.createOrder(serverTotal, receipt, notes);

        PaymentSession session = new PaymentSession();
        session.setUserId(userId);
        session.setPurpose(PaymentSession.Purpose.ORDER);
        session.setStatus(PaymentSession.Status.CREATED);
        session.setAmountInr(serverTotal);
        session.setCurrency("INR");
        session.setRazorpayOrderId(rzOrder.get("id"));
        session.setOrderPayload(payloadJson);
        session.setCreatedAt(LocalDateTime.now());
        session.setUpdatedAt(LocalDateTime.now());
        paymentSessionRepository.save(session);

        return checkoutPayload(session, user);
    }

    /**
     * Verifies Razorpay checkout response and fulfills the payment session.
     * Idempotent: replaying the same payment returns the previous fulfillment result.
     */
    @Transactional
    public Map<String, Object> verifyAndFulfill(Long userId, String razorpayOrderId,
                                                 String razorpayPaymentId, String razorpaySignature) throws Exception {
        ensureConfigured();

        if (razorpayOrderId == null || razorpayPaymentId == null || razorpaySignature == null) {
            throw new IllegalArgumentException("Missing payment verification fields");
        }

        PaymentSession session = paymentSessionRepository.findByRazorpayOrderId(razorpayOrderId)
            .orElseThrow(() -> new IllegalArgumentException("Unknown payment session"));

        if (!session.getUserId().equals(userId)) {
            throw new SecurityException("Payment session does not belong to this user");
        }

        // Idempotent success
        if (session.getStatus() == PaymentSession.Status.FULFILLED
            || session.getStatus() == PaymentSession.Status.FULFILLED_AS_CREDIT) {
            return buildFulfillmentResponse(session);
        }

        // Another path may have already stored this payment id
        Optional<PaymentSession> byPayment = paymentSessionRepository.findByRazorpayPaymentId(razorpayPaymentId);
        if (byPayment.isPresent() && !byPayment.get().getId().equals(session.getId())) {
            throw new IllegalStateException("Payment already linked to another session");
        }

        if (!razorpayService.verifyPaymentSignature(razorpayOrderId, razorpayPaymentId, razorpaySignature)) {
            session.setStatus(PaymentSession.Status.FAILED);
            session.setFailureReason("Invalid payment signature");
            paymentSessionRepository.save(session);
            throw new SecurityException("Payment signature verification failed");
        }

        // Server-side confirmation with Razorpay API
        Payment payment = razorpayService.fetchPayment(razorpayPaymentId);
        if (!razorpayService.isPaymentCaptured(payment)) {
            session.setStatus(PaymentSession.Status.FAILED);
            session.setFailureReason("Payment not captured: " + payment.get("status"));
            paymentSessionRepository.save(session);
            throw new IllegalStateException("Payment has not been captured yet. Status: " + payment.get("status"));
        }

        // Ensure payment is for this order
        String paymentOrderId = payment.get("order_id");
        if (paymentOrderId != null && !razorpayOrderId.equals(paymentOrderId)) {
            session.setStatus(PaymentSession.Status.FAILED);
            session.setFailureReason("Payment order mismatch");
            paymentSessionRepository.save(session);
            throw new SecurityException("Payment does not match Razorpay order");
        }

        BigDecimal paidInr = razorpayService.paymentAmountInr(payment);
        if (paidInr.compareTo(session.getAmountInr()) != 0) {
            session.setStatus(PaymentSession.Status.FAILED);
            session.setFailureReason("Amount mismatch: paid=" + paidInr + " expected=" + session.getAmountInr());
            paymentSessionRepository.save(session);
            throw new SecurityException("Paid amount does not match expected amount");
        }

        session.setRazorpayPaymentId(razorpayPaymentId);
        session.setRazorpaySignature(razorpaySignature);
        session.setStatus(PaymentSession.Status.PAID);
        paymentSessionRepository.save(session);

        return fulfillSession(session);
    }

    /**
     * Webhook-driven fulfillment when client disconnects after paying.
     */
    @Transactional
    public void handlePaymentCapturedWebhook(String razorpayOrderId, String razorpayPaymentId) {
        try {
            PaymentSession session = paymentSessionRepository.findByRazorpayOrderId(razorpayOrderId).orElse(null);
            if (session == null) {
                log.warn("Webhook for unknown order {}", razorpayOrderId);
                return;
            }
            if (session.getStatus() == PaymentSession.Status.FULFILLED
                || session.getStatus() == PaymentSession.Status.FULFILLED_AS_CREDIT) {
                return;
            }

            // Verify via API
            Payment payment = razorpayService.fetchPayment(razorpayPaymentId);
            if (!razorpayService.isPaymentCaptured(payment)) {
                log.warn("Webhook payment {} not captured", razorpayPaymentId);
                return;
            }

            BigDecimal paidInr = razorpayService.paymentAmountInr(payment);
            if (paidInr.compareTo(session.getAmountInr()) != 0) {
                log.error("Webhook amount mismatch for order {}", razorpayOrderId);
                session.setStatus(PaymentSession.Status.FAILED);
                session.setFailureReason("Webhook amount mismatch");
                paymentSessionRepository.save(session);
                return;
            }

            session.setRazorpayPaymentId(razorpayPaymentId);
            session.setStatus(PaymentSession.Status.PAID);
            paymentSessionRepository.save(session);
            fulfillSession(session);
        } catch (Exception e) {
            log.error("Webhook fulfillment failed for order {}: {}", razorpayOrderId, e.getMessage(), e);
        }
    }

    private Map<String, Object> fulfillSession(PaymentSession session) throws Exception {
        if (session.getPurpose() == PaymentSession.Purpose.WALLET_TOPUP) {
            return fulfillWalletTopup(session);
        }
        return fulfillOrder(session);
    }

    private Map<String, Object> fulfillWalletTopup(PaymentSession session) {
        String ref = "RZP-" + session.getRazorpayPaymentId();
        if (tokenTransactionRepository.existsByReferenceId(ref)) {
            session.setStatus(PaymentSession.Status.FULFILLED);
            session.setFulfilledAt(LocalDateTime.now());
            paymentSessionRepository.save(session);
            User user = userRepository.findById(session.getUserId()).orElse(null);
            Map<String, Object> resp = buildFulfillmentResponse(session);
            if (user != null) resp.put("newBalance", user.getRitzTokenBalance());
            return resp;
        }

        User updated = tokenService.topUp(session.getUserId(), session.getAmountInr(), ref);
        session.setStatus(PaymentSession.Status.FULFILLED);
        session.setFulfilledAt(LocalDateTime.now());
        paymentSessionRepository.save(session);

        Map<String, Object> resp = buildFulfillmentResponse(session);
        resp.put("newBalance", updated.getRitzTokenBalance());
        resp.put("message", "Successfully added " + session.getAmountInr() + " Ritz Tokens");
        return resp;
    }

    private Map<String, Object> fulfillOrder(PaymentSession session) {
        try {
            com.rit.canteen.sales.model.Order order = orderPlacementService.buildOrderFromPayload(
                session.getOrderPayload(), session.getUserId(), "RAZORPAY");
            order.setTotalAmount(session.getAmountInr());

            OrderPlacementService.PlacementResult result =
                orderPlacementService.placeOrder(order, false);

            if (!result.success) {
                // Should not normally reach here for stock (throws), but handle validation failures
                return creditAsFallback(session, result.message);
            }

            session.setStatus(PaymentSession.Status.FULFILLED);
            session.setFulfillmentOrderNumber(result.orderNumber);
            session.setFulfillmentDisplayId(result.displayOrderId);
            session.setFulfilledAt(LocalDateTime.now());
            paymentSessionRepository.save(session);
            return buildFulfillmentResponse(session);
        } catch (OrderPlacementService.StockConflictException e) {
            log.warn("Stock conflict after payment {}; crediting wallet as fallback", session.getRazorpayPaymentId());
            return creditAsFallback(session,
                "Payment received, but some items went out of stock. ₹"
                    + session.getAmountInr() + " has been credited to your Ritz wallet.");
        }
    }

    private Map<String, Object> creditAsFallback(PaymentSession session, String reason) {
        String ref = "RZP-FALLBACK-" + session.getRazorpayPaymentId();
        if (!tokenTransactionRepository.existsByReferenceId(ref)) {
            tokenService.topUp(session.getUserId(), session.getAmountInr(), ref);
        }
        session.setStatus(PaymentSession.Status.FULFILLED_AS_CREDIT);
        session.setFailureReason(reason);
        session.setFulfilledAt(LocalDateTime.now());
        paymentSessionRepository.save(session);

        User user = userRepository.findById(session.getUserId()).orElse(null);
        Map<String, Object> resp = new LinkedHashMap<>();
        resp.put("success", true);
        resp.put("type", "WALLET_CREDIT_FALLBACK");
        resp.put("purpose", session.getPurpose().name());
        resp.put("status", session.getStatus().name());
        resp.put("message", reason);
        resp.put("creditedAmount", session.getAmountInr());
        if (user != null) resp.put("newBalance", user.getRitzTokenBalance());
        resp.put("razorpayPaymentId", session.getRazorpayPaymentId());
        return resp;
    }

    private Map<String, Object> buildFulfillmentResponse(PaymentSession session) {
        Map<String, Object> resp = new LinkedHashMap<>();
        resp.put("success", true);
        resp.put("purpose", session.getPurpose().name());
        resp.put("status", session.getStatus().name());
        resp.put("razorpayOrderId", session.getRazorpayOrderId());
        resp.put("razorpayPaymentId", session.getRazorpayPaymentId());
        resp.put("amount", session.getAmountInr());

        if (session.getPurpose() == PaymentSession.Purpose.ORDER
            && session.getStatus() == PaymentSession.Status.FULFILLED) {
            resp.put("type", "ORDER");
            resp.put("orderNumber", session.getFulfillmentOrderNumber());
            resp.put("displayOrderId", session.getFulfillmentDisplayId());
            resp.put("message", "Order placed successfully");
        } else if (session.getPurpose() == PaymentSession.Purpose.WALLET_TOPUP) {
            resp.put("type", "WALLET_TOPUP");
            User user = userRepository.findById(session.getUserId()).orElse(null);
            if (user != null) resp.put("newBalance", user.getRitzTokenBalance());
            resp.put("message", "Wallet top-up successful");
        } else if (session.getStatus() == PaymentSession.Status.FULFILLED_AS_CREDIT) {
            resp.put("type", "WALLET_CREDIT_FALLBACK");
            resp.put("message", session.getFailureReason());
            User user = userRepository.findById(session.getUserId()).orElse(null);
            if (user != null) resp.put("newBalance", user.getRitzTokenBalance());
        }
        return resp;
    }

    private Map<String, Object> checkoutPayload(PaymentSession session, User user) {
        Map<String, Object> resp = new LinkedHashMap<>();
        resp.put("success", true);
        resp.put("paymentSessionId", session.getId());
        resp.put("keyId", razorpayService.getKeyId());
        resp.put("razorpayOrderId", session.getRazorpayOrderId());
        resp.put("amount", RazorpayService.toPaise(session.getAmountInr())); // paise for Checkout.js
        resp.put("amountInr", session.getAmountInr());
        resp.put("currency", session.getCurrency());
        resp.put("purpose", session.getPurpose().name());
        resp.put("name", "Tillo Canteen");
        resp.put("description", session.getPurpose() == PaymentSession.Purpose.WALLET_TOPUP
            ? "Ritz Wallet Top-up"
            : "Food Order Payment");

        Map<String, Object> prefill = new LinkedHashMap<>();
        prefill.put("name", user.getName() != null ? user.getName() : "");
        prefill.put("contact", user.getMobileNumber() != null ? user.getMobileNumber() : "");
        if (user.getEmail() != null) prefill.put("email", user.getEmail());
        resp.put("prefill", prefill);

        Map<String, String> notes = new LinkedHashMap<>();
        notes.put("paymentSessionId", String.valueOf(session.getId()));
        notes.put("userId", String.valueOf(user.getId()));
        resp.put("notes", notes);
        return resp;
    }

    private void ensureConfigured() {
        if (!razorpayService.isConfigured()) {
            throw new IllegalStateException(
                "Razorpay is not configured on the server. Set RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET.");
        }
    }
}
