package com.rit.canteen.sales.controller;

import com.rit.canteen.sales.model.*;
import com.rit.canteen.sales.repository.*;
import com.rit.canteen.sales.service.DeveloperApiKeyService;
import com.rit.canteen.sales.service.TokenService;
import com.rit.canteen.sales.service.SystemNotificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/developer/v1")
public class DeveloperApiController {

    @Autowired
    private DeveloperApiKeyService keyService;

    @Autowired
    private StallRepository stallRepository;

    @Autowired
    private ProductRepository productRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private TokenTransactionRepository transactionRepository;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private SystemNotificationService notificationService;

    @Autowired
    private StockUpdateController stockUpdateController;

    // ── Helper to authenticate key ─────────────────────────────────────────
    private DeveloperApiKey authenticate(String headerKey) {
        if (headerKey == null || headerKey.trim().isEmpty()) {
            return null;
        }
        return keyService.validateAndUseKey(headerKey).orElse(null);
    }

    private boolean checkPermission(DeveloperApiKey key, String requiredScope) {
        if (key == null) return false;
        
        // If the operation requires write access but the key doesn't have it, block immediately.
        if (requiredScope.startsWith("WRITE_") && !key.isWriteAccess()) {
            return false;
        }
        
        // If it's a customer key, they only get default read-only scopes.
        if (!"SYSTEM".equalsIgnoreCase(key.getUserType())) {
            return requiredScope.startsWith("READ_");
        }
        
        // System users get custom scopes.
        String scopes = key.getPermissions();
        if (scopes == null || scopes.trim().isEmpty()) {
            return false;
        }
        
        return Arrays.stream(scopes.split(","))
                .map(String::trim)
                .anyMatch(scope -> scope.equalsIgnoreCase(requiredScope));
    }

    // ── 1. Validate API Key ────────────────────────────────────────────────
    @GetMapping("/validate")
    public ResponseEntity<?> validateKey(@RequestHeader(value = "X-Developer-Key", required = false) String headerKey) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        return ResponseEntity.ok(Map.of(
            "status", "VALID",
            "name", key.getName(),
            "ownerType", key.getUserType(),
            "createdAt", key.getCreatedAt(),
            "readOnly", !key.isWriteAccess(),
            "permissions", key.getPermissions() != null ? Arrays.asList(key.getPermissions().split(",")) : Collections.emptyList()
        ));
    }

    // ── 2. Get Stalls (Read-only, non-financial) ───────────────────────────
    @GetMapping("/stalls")
    public ResponseEntity<?> getStalls(@RequestHeader(value = "X-Developer-Key", required = false) String headerKey) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "READ_STALLS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: READ_STALLS permission required"));
        }
        
        List<Stall> stalls = stallRepository.findAll();
        // Map to DTO to avoid circular references and hide sensitive fields
        List<Map<String, Object>> result = stalls.stream().map(s -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", s.getId());
            map.put("name", s.getName());
            map.put("description", s.getDescription());
            map.put("active", s.isActive());
            map.put("temporarilyClosed", s.isTemporarilyClosed());
            return map;
        }).collect(Collectors.toList());
        
        return ResponseEntity.ok(result);
    }

    // ── 3. Get Products (Read-only, non-financial) ─────────────────────────
    @GetMapping("/products")
    public ResponseEntity<?> getProducts(@RequestHeader(value = "X-Developer-Key", required = false) String headerKey) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "READ_PRODUCTS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: READ_PRODUCTS permission required"));
        }
        
        List<Product> products = productRepository.findAll();
        List<Map<String, Object>> result = products.stream().map(p -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", p.getId());
            map.put("productId", p.getProductId());
            map.put("name", p.getName());
            map.put("category", p.getCategory());
            map.put("price", p.getPrice()); // List price is fine for catalog, not a financial report
            map.put("stock", p.getStock());
            map.put("active", p.isActive());
            map.put("isDraft", p.isDraft());
            return map;
        }).collect(Collectors.toList());
        
        return ResponseEntity.ok(result);
    }

    @GetMapping("/wallet")
    public ResponseEntity<?> getWallet(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @RequestParam(required = false) String mobileNumber,
            @RequestParam(required = false) Long userId) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "READ_WALLETS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: READ_WALLETS permission required"));
        }

        // ── Check if checking balance for another user by mobileNumber or userId ──
        if (mobileNumber != null || userId != null) {
            Optional<User> targetUserOpt = Optional.empty();
            if (userId != null) {
                targetUserOpt = userRepository.findById(userId);
            } else {
                targetUserOpt = userRepository.findByMobileNumber(mobileNumber);
            }

            if (targetUserOpt.isPresent()) {
                User user = targetUserOpt.get();
                return ResponseEntity.ok(Map.of(
                    "userMobile", user.getMobileNumber(),
                    "userName", user.getName() != null ? user.getName() : "Customer",
                    "ritzTokenBalance", user.getRitzTokenBalance(),
                    "currency", "Ritz Token",
                    "queryScope", "SPECIFIC_USER"
                ));
            } else {
                return ResponseEntity.status(404).body(Map.of("error", "Target user not found"));
            }
        }

        if ("CUSTOMER".equals(key.getUserType())) {
            // Customer key: return their specific wallet balance
            Optional<User> userOpt = userRepository.findById(key.getUserId());
            if (userOpt.isPresent()) {
                User user = userOpt.get();
                return ResponseEntity.ok(Map.of(
                    "userMobile", user.getMobileNumber(),
                    "userName", user.getName() != null ? user.getName() : "Customer",
                    "ritzTokenBalance", user.getRitzTokenBalance(),
                    "currency", "Ritz Token",
                    "queryScope", "OWNER"
                ));
            }
            return ResponseEntity.status(404).body(Map.of("error", "Owner user not found"));
        } else {
            // System key: return overall token circulation stats, but NO individual user accounts or detailed ledgers
            List<User> allUsers = userRepository.findAll();
            BigDecimal totalCirculation = allUsers.stream()
                .map(User::getRitzTokenBalance)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
            
            return ResponseEntity.ok(Map.of(
                "scope", "SYSTEM_CIRCULATION",
                "activeWalletsCount", allUsers.size(),
                "totalCirculationBalance", totalCirculation,
                "currency", "Ritz Token",
                "queryScope", "SYSTEM"
            ));
        }
    }

    // ── 5. Get Orders (requires READ_ORDERS) ────────────────────────────────
    @GetMapping("/orders")
    public ResponseEntity<?> getOrders(@RequestHeader(value = "X-Developer-Key", required = false) String headerKey) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "READ_ORDERS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: READ_ORDERS permission required"));
        }

        List<Order> orders;
        if ("CUSTOMER".equals(key.getUserType())) {
            orders = orderRepository.findByUserIdOrderByCreatedAtDesc(key.getUserId());
        } else {
            orders = orderRepository.findAll();
        }

        // Map to DTO. Financial details (totalAmount, paymentMethod, item prices) are only exposed for staff/SYSTEM keys.
        List<Map<String, Object>> result = orders.stream().map(o -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", o.getId());
            map.put("orderNumber", o.getOrderNumber());
            map.put("displayOrderId", o.getDisplayOrderId());
            map.put("status", o.getStatus());
            map.put("createdAt", o.getCreatedAt());
            map.put("orderType", o.getOrderType());
            
            if ("SYSTEM".equalsIgnoreCase(key.getUserType())) {
                map.put("totalAmount", o.getTotalAmount());
                map.put("paymentMethod", o.getPaymentMethod());
            }
            
            List<Map<String, Object>> itemsList = o.getItems().stream().map(item -> {
                Map<String, Object> itemMap = new HashMap<>();
                itemMap.put("productName", item.getProductName());
                itemMap.put("quantity", item.getQuantity());
                itemMap.put("stallName", item.getStallName());
                if ("SYSTEM".equalsIgnoreCase(key.getUserType())) {
                    itemMap.put("price", item.getPrice());
                }
                return itemMap;
            }).collect(Collectors.toList());
            
            map.put("items", itemsList);
            return map;
        }).collect(Collectors.toList());

        return ResponseEntity.ok(result);
    }

    // ── 5a. Get Wallet Transactions (requires READ_WALLETS & SYSTEM key) ─────
    @GetMapping("/wallet/transactions")
    public ResponseEntity<?> getWalletTransactions(@RequestHeader(value = "X-Developer-Key", required = false) String headerKey) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!"SYSTEM".equalsIgnoreCase(key.getUserType())) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: Only staff keys can view general transaction records"));
        }
        if (!checkPermission(key, "READ_WALLETS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: READ_WALLETS permission required"));
        }
        return ResponseEntity.ok(tokenService.getAllTransactions());
    }

    // ── 5b. Get Wallet Circulation Stats (requires READ_WALLETS & SYSTEM key) ──
    @GetMapping("/wallet/stats")
    public ResponseEntity<?> getWalletStats(@RequestHeader(value = "X-Developer-Key", required = false) String headerKey) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!"SYSTEM".equalsIgnoreCase(key.getUserType())) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: Only staff keys can view wallet statistics"));
        }
        if (!checkPermission(key, "READ_WALLETS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: READ_WALLETS permission required"));
        }
        return ResponseEntity.ok(tokenService.getGlobalStats());
    }

    // ── 6. Create Stall (requires WRITE_STALLS) ───────────────────────────
    @PostMapping("/stalls")
    public ResponseEntity<?> createStall(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @RequestBody Stall stall) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "WRITE_STALLS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: WRITE_STALLS permission required"));
        }
        Stall saved = stallRepository.save(stall);
        return ResponseEntity.status(201).body(saved);
    }

    // ── 7. Update Stall (requires WRITE_STALLS) ───────────────────────────
    @PutMapping("/stalls/{id}")
    @Transactional
    public ResponseEntity<?> updateStall(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @PathVariable Long id,
            @RequestBody Stall updatedStall) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "WRITE_STALLS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: WRITE_STALLS permission required"));
        }
        return stallRepository.findById(id).map(stall -> {
            stall.setName(updatedStall.getName());
            stall.setDescription(updatedStall.getDescription());
            stall.setActive(updatedStall.isActive());
            stall.setTemporarilyClosed(updatedStall.isTemporarilyClosed());
            stall.setSessionOptional(updatedStall.isSessionOptional());
            return ResponseEntity.ok(stallRepository.save(stall));
        }).orElse(ResponseEntity.notFound().build());
    }

    // ── 8. Delete Stall (requires WRITE_STALLS) ───────────────────────────
    @DeleteMapping("/stalls/{id}")
    public ResponseEntity<?> deleteStall(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @PathVariable Long id) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "WRITE_STALLS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: WRITE_STALLS permission required"));
        }
        return stallRepository.findById(id).map(stall -> {
            stallRepository.delete(stall);
            return ResponseEntity.ok(Map.of("success", true, "message", "Stall deleted successfully"));
        }).orElse(ResponseEntity.notFound().build());
    }

    // ── 9. Create Product (requires WRITE_PRODUCTS) ───────────────────────
    @PostMapping("/products")
    @Transactional
    public ResponseEntity<?> createProduct(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @RequestBody Product product) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "WRITE_PRODUCTS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: WRITE_PRODUCTS permission required"));
        }
        Product saved = productRepository.save(product);
        if (saved.isDraft()) {
            notificationService.createNotification(
                "Draft Product Created via API",
                "A product draft '" + saved.getName() + "' was created via developer API key.",
                "PRODUCT",
                "/inventory/products"
            );
        }
        return ResponseEntity.status(201).body(saved);
    }

    // ── 10. Update Product (requires WRITE_PRODUCTS) ───────────────────────
    @PutMapping("/products/{id}")
    @Transactional
    public ResponseEntity<?> updateProduct(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @PathVariable Long id,
            @RequestBody Product details) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "WRITE_PRODUCTS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: WRITE_PRODUCTS permission required"));
        }
        return productRepository.findById(id).map(p -> {
            p.setProductId(details.getProductId());
            p.setName(details.getName());
            p.setCategory(details.getCategory());
            p.setDescription(details.getDescription());
            p.setPrice(details.getPrice());
            p.setStock(details.getStock());
            p.setActive(details.isActive());
            p.setVeg(details.isVeg());
            
            Product updated = productRepository.save(p);
            stockUpdateController.broadcastStockUpdate(updated.getId(), updated.getStock());
            return ResponseEntity.ok(updated);
        }).orElse(ResponseEntity.notFound().build());
    }

    // ── 11. Delete Product (requires WRITE_PRODUCTS) ──────────────────────
    @DeleteMapping("/products/{id}")
    public ResponseEntity<?> deleteProduct(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @PathVariable Long id) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "WRITE_PRODUCTS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: WRITE_PRODUCTS permission required"));
        }
        return productRepository.findById(id).map(p -> {
            productRepository.delete(p);
            return ResponseEntity.ok(Map.of("success", true, "message", "Product deleted successfully"));
        }).orElse(ResponseEntity.notFound().build());
    }

    // ── 12. Update Order Status (requires WRITE_ORDERS) ───────────────────
    @PatchMapping("/orders/{id}/status")
    @Transactional
    public ResponseEntity<?> updateOrderStatus(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @PathVariable Long id,
            @RequestBody Map<String, String> body) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "WRITE_ORDERS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: WRITE_ORDERS permission required"));
        }
        String newStatus = body.get("status");
        if (newStatus == null || newStatus.isEmpty()) {
            return ResponseEntity.badRequest().body(Map.of("error", "Status field is required"));
        }
        return orderRepository.findById(id).map(order -> {
            String oldStatus = order.getStatus();
            String nextStatus = newStatus.toUpperCase();
            if ("CANCELLED".equals(nextStatus) && !"CANCELLED".equals(oldStatus)) {
                if ("RITZ_TOKEN".equals(order.getPaymentMethod())) {
                    tokenService.refund(order.getUserId(), "ORD-" + order.getDisplayOrderId(),
                            order.getTotalAmount(), "Status changed to CANCELLED via API");
                }
            }
            order.setStatus(nextStatus);
            orderRepository.save(order);
            return ResponseEntity.ok(Map.of("success", true, "message", "Order status updated to " + nextStatus));
        }).orElse(ResponseEntity.notFound().build());
    }

    // ── 13. Wallet Topup (requires WRITE_WALLETS) ──────────────────────────
    @PostMapping("/wallet/topup")
    @Transactional
    public ResponseEntity<?> walletTopup(
            @RequestHeader(value = "X-Developer-Key", required = false) String headerKey,
            @RequestBody Map<String, Object> body) {
        DeveloperApiKey key = authenticate(headerKey);
        if (key == null) {
            return ResponseEntity.status(401).body(Map.of("error", "Invalid or missing X-Developer-Key header"));
        }
        if (!checkPermission(key, "WRITE_WALLETS")) {
            return ResponseEntity.status(403).body(Map.of("error", "Access Denied: WRITE_WALLETS permission required"));
        }
        
        try {
            Long targetUserId = null;
            if (body.containsKey("userId") && body.get("userId") != null) {
                targetUserId = Long.valueOf(body.get("userId").toString());
            } else if (body.containsKey("mobileNumber") && body.get("mobileNumber") != null) {
                String mobile = body.get("mobileNumber").toString();
                User targetUser = userRepository.findByMobileNumber(mobile).orElse(null);
                if (targetUser != null) {
                    targetUserId = targetUser.getId();
                } else {
                    return ResponseEntity.status(404).body(Map.of("error", "User with mobile number " + mobile + " not found"));
                }
            } else {
                return ResponseEntity.badRequest().body(Map.of("error", "userId or mobileNumber is required"));
            }

            BigDecimal amount = new BigDecimal(body.get("amount").toString());
            if (amount.compareTo(new BigDecimal("50")) < 0) {
                return ResponseEntity.badRequest().body(Map.of("error", "Minimum top up amount is 50 tokens"));
            }
            if (amount.compareTo(new BigDecimal("5000")) > 0) {
                return ResponseEntity.badRequest().body(Map.of("error", "Single transaction limit exceeded (Max: 5000 tokens)"));
            }

            String ref = body.getOrDefault("referenceId", "API-TOPUP-" + System.currentTimeMillis()).toString();
            User updatedUser = tokenService.topUp(targetUserId, amount, ref);
            return ResponseEntity.ok(Map.of(
                "success", true,
                "newBalance", updatedUser.getRitzTokenBalance(),
                "message", "Successfully added " + amount + " Ritz Tokens via API Key"
            ));
        } catch (Exception e) {
            return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
        }
    }
}
