package pt.henrique.communityMarket.service;

import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import pt.henrique.communityMarket.CommunityMarket;
import pt.henrique.communityMarket.model.ClaimItem;
import pt.henrique.communityMarket.model.Listing;
import pt.henrique.communityMarket.model.PendingEarnings;
import pt.henrique.communityMarket.util.InventoryUtil;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Service layer for managing fixed-price market listings.
 * Handles creation, purchase, cancellation, and expiration of listings.
 */
public class ListingService {

    private final CommunityMarket plugin;

    // Simple cache for active listings
    private List<Listing> cachedListings;
    private long cacheExpiry = 0;

    // Track pending operations to prevent double-purchases
    private final Map<Integer, Boolean> pendingPurchases = new ConcurrentHashMap<>();

    public ListingService(CommunityMarket plugin) {
        this.plugin = plugin;
    }

    /**
     * Creates a new listing for a player
     *
     * @param player      The seller
     * @param item        The item to sell
     * @param amount      Amount of items
     * @param price       Total price for all items
     * @param durationHours Duration in hours
     * @return CompletableFuture with the listing ID or -1 if failed
     */
    public CompletableFuture<Integer> createListing(Player player, ItemStack item, int amount, double price, int durationHours) {
        // Validate
        if (item == null || item.getType().isAir()) {
            return CompletableFuture.completedFuture(-1);
        }

        if (plugin.getConfigManager().isMaterialBlacklisted(item.getType())) {
            return CompletableFuture.completedFuture(-1);
        }

        // Create listing object
        Listing listing = new Listing(
                player.getUniqueId(),
                player.getName(),
                item.clone(),
                amount,
                price,
                Instant.now().plusSeconds(durationHours * 3600L)
        );

        // Save to database
        return plugin.getDatabaseManager().createListing(listing)
                .thenApply(id -> {
                    if (id > 0) {
                        invalidateCache();
                        // Update cooldown
                        plugin.getDatabaseManager().updateLastListingTime(player.getUniqueId());
                    }
                    return id;
                });
    }

    /**
     * Gets all active listings with caching
     */
    public CompletableFuture<List<Listing>> getActiveListings() {
        long now = System.currentTimeMillis();

        // Return cached if still valid
        if (cachedListings != null && now < cacheExpiry) {
            return CompletableFuture.completedFuture(cachedListings);
        }

        return plugin.getDatabaseManager().getActiveListings()
                .thenApply(listings -> {
                    cachedListings = listings;
                    cacheExpiry = System.currentTimeMillis() +
                            (plugin.getConfigManager().getCacheDuration() * 1000L);
                    return listings;
                });
    }

    /**
     * Gets a specific listing by ID
     */
    public CompletableFuture<Optional<Listing>> getListing(int id) {
        return plugin.getDatabaseManager().getListing(id);
    }

    /**
     * Gets all listings for a specific player
     */
    public CompletableFuture<List<Listing>> getPlayerListings(UUID playerUuid) {
        return plugin.getDatabaseManager().getPlayerListings(playerUuid);
    }

    /**
     * Counts active listings for a player
     */
    public CompletableFuture<Integer> countPlayerListings(UUID playerUuid) {
        return plugin.getDatabaseManager().countPlayerListings(playerUuid);
    }

    /**
     * Checks if a player can create a new listing (not at limit and no cooldown)
     */
    public CompletableFuture<Boolean> canCreateListing(UUID playerUuid) {
        int maxListings = plugin.getConfigManager().getMaxListingsPerPlayer();

        return countPlayerListings(playerUuid)
                .thenCompose(count -> {
                    if (count >= maxListings) {
                        return CompletableFuture.completedFuture(false);
                    }

                    int cooldown = plugin.getConfigManager().getListingCooldown();
                    if (cooldown <= 0) {
                        return CompletableFuture.completedFuture(true);
                    }

                    return plugin.getDatabaseManager().getLastListingTime(playerUuid)
                            .thenApply(lastTime -> {
                                if (lastTime.isEmpty()) return true;
                                return Instant.now().isAfter(lastTime.get().plusSeconds(cooldown));
                            });
                });
    }

    /**
     * Calculates the remaining cooldown time in seconds
     */
    public CompletableFuture<Long> getRemainingCooldown(UUID playerUuid) {
        int cooldown = plugin.getConfigManager().getListingCooldown();
        if (cooldown <= 0) {
            return CompletableFuture.completedFuture(0L);
        }

        return plugin.getDatabaseManager().getLastListingTime(playerUuid)
                .thenApply(lastTime -> {
                    if (lastTime.isEmpty()) return 0L;
                    long remaining = lastTime.get().plusSeconds(cooldown).getEpochSecond() - Instant.now().getEpochSecond();
                    return Math.max(0, remaining);
                });
    }

    /**
     * Attempts to purchase a listing.
     * This is an atomic operation that prevents double-purchases.
     *
     * @param listingId The listing to purchase
     * @param buyer     The buyer
     * @return CompletableFuture with success status
     */
    public CompletableFuture<PurchaseResult> purchaseListing(int listingId, Player buyer) {
        // Check if already processing this listing
        if (pendingPurchases.putIfAbsent(listingId, true) != null) {
            return CompletableFuture.completedFuture(PurchaseResult.ALREADY_PROCESSING);
        }

        try {
            return getListing(listingId)
                    .thenCompose(optListing -> {
                        if (optListing.isEmpty()) {
                            pendingPurchases.remove(listingId);
                            return CompletableFuture.completedFuture(PurchaseResult.NOT_FOUND);
                        }

                        Listing listing = optListing.get();

                        // Can't buy own listing
                        if (listing.getSellerUuid().equals(buyer.getUniqueId())) {
                            pendingPurchases.remove(listingId);
                            return CompletableFuture.completedFuture(PurchaseResult.OWN_LISTING);
                        }

                        // Check buyer funds
                        if (!plugin.getEconomyManager().has(buyer.getUniqueId(), listing.getPrice())) {
                            pendingPurchases.remove(listingId);
                            return CompletableFuture.completedFuture(PurchaseResult.INSUFFICIENT_FUNDS);
                        }

                        // Attempt atomic purchase in DB
                        return plugin.getDatabaseManager().purchaseListing(listingId, buyer.getUniqueId(), buyer.getName())
                                .thenApply(success -> {
                                    if (!success) {
                                        pendingPurchases.remove(listingId);
                                        return PurchaseResult.ALREADY_SOLD;
                                    }

                                    // Withdraw from buyer
                                    if (!plugin.getEconomyManager().withdraw(buyer.getUniqueId(), listing.getPrice())) {
                                        // Rollback DB change
                                        plugin.getDatabaseManager().updateListingStatus(listingId, Listing.ListingStatus.ACTIVE);
                                        pendingPurchases.remove(listingId);
                                        return PurchaseResult.ECONOMY_ERROR;
                                    }

                                    // Calculate seller earnings after tax
                                    double tax = plugin.getConfigManager().getMarketTax();
                                    double sellerEarnings = listing.getPrice() * (1 - tax / 100);

                                    // Add pending earnings for seller
                                    PendingEarnings earnings = new PendingEarnings(
                                            listing.getSellerUuid(),
                                            sellerEarnings,
                                            "Listing #" + listingId
                                    );
                                    plugin.getDatabaseManager().addPendingEarnings(earnings);

                                    // Give item to buyer or add to claim storage
                                    Bukkit.getScheduler().runTask(plugin, () -> {
                                        ItemStack item = listing.getItem().clone();
                                        item.setAmount(listing.getAmount());
                                        ItemStack leftover = InventoryUtil.giveItem(buyer, item);

                                        if (leftover != null) {
                                            // Couldn't fit in inventory, add to claim storage
                                            ClaimItem claimItem = new ClaimItem(
                                                    buyer.getUniqueId(),
                                                    leftover,
                                                    ClaimItem.ClaimReason.PURCHASE_FULL_INVENTORY,
                                                    "Listing #" + listingId
                                            );
                                            plugin.getDatabaseManager().addClaimItem(claimItem);
                                            buyer.sendMessage(plugin.getMessageManager().getPrefixed("messages.claim-inventory-full"));
                                        }
                                    });

                                    // Notify seller if online
                                    if (plugin.getConfigManager().isNotifyOnSale()) {
                                        Player seller = Bukkit.getPlayer(listing.getSellerUuid());
                                        if (seller != null && seller.isOnline()) {
                                            Map<String, String> placeholders = Map.of(
                                                    "item", listing.getItem().getType().name(),
                                                    "amount", String.valueOf(listing.getAmount()),
                                                    "buyer", buyer.getName(),
                                                    "price", plugin.getMessageManager().formatCurrency(listing.getPrice())
                                            );
                                            seller.sendMessage(plugin.getMessageManager().getPrefixed("messages.listing-sold", placeholders));
                                        }
                                    }

                                    invalidateCache();
                                    pendingPurchases.remove(listingId);
                                    return PurchaseResult.SUCCESS;
                                });
                    });
        } catch (Exception e) {
            pendingPurchases.remove(listingId);
            throw e;
        }
    }

    /**
     * Cancels a listing and returns the item to the seller's claim storage
     *
     * @param listingId The listing to cancel
     * @param playerUuid The player attempting to cancel (must be seller or admin)
     * @param isAdmin Whether this is an admin action
     * @return CompletableFuture with success status
     */
    public CompletableFuture<Boolean> cancelListing(int listingId, UUID playerUuid, boolean isAdmin) {
        return getListing(listingId)
                .thenCompose(optListing -> {
                    if (optListing.isEmpty()) {
                        return CompletableFuture.completedFuture(false);
                    }

                    Listing listing = optListing.get();

                    // Check permission
                    if (!isAdmin && !listing.getSellerUuid().equals(playerUuid)) {
                        return CompletableFuture.completedFuture(false);
                    }

                    // Update status atomically (only if still ACTIVE) so a concurrent
                    // purchase/expiry can never both deliver the item AND return it here.
                    return plugin.getDatabaseManager().updateListingStatusIfActive(listingId, Listing.ListingStatus.CANCELLED)
                            .thenApply(success -> {
                                if (success) {
                                    // Return item to claim storage
                                    ItemStack item = listing.getItem().clone();
                                    item.setAmount(listing.getAmount());
                                    ClaimItem claimItem = new ClaimItem(
                                            listing.getSellerUuid(),
                                            item,
                                            ClaimItem.ClaimReason.CANCELLED_LISTING,
                                            "Listing #" + listingId
                                    );
                                    plugin.getDatabaseManager().addClaimItem(claimItem);
                                    invalidateCache();
                                }
                                return success;
                            });
                });
    }

    /**
     * Processes expired listings - moves items to claim storage
     */
    public CompletableFuture<Void> processExpiredListings() {
        return plugin.getDatabaseManager().getExpiredListings()
                .thenAccept(listings -> {
                    for (Listing listing : listings) {
                        // Update status to expired atomically (only if still ACTIVE) so a
                        // concurrent purchase can't both sell the item and return it to claim.
                        plugin.getDatabaseManager().updateListingStatusIfActive(listing.getId(), Listing.ListingStatus.EXPIRED)
                                .thenAccept(success -> {
                                    if (success) {
                                        // Return item to claim storage
                                        ItemStack item = listing.getItem().clone();
                                        item.setAmount(listing.getAmount());
                                        ClaimItem claimItem = new ClaimItem(
                                                listing.getSellerUuid(),
                                                item,
                                                ClaimItem.ClaimReason.EXPIRED_LISTING,
                                                "Listing #" + listing.getId()
                                        );
                                        plugin.getDatabaseManager().addClaimItem(claimItem);

                                        // Notify seller if online
                                        if (plugin.getConfigManager().isNotifyOnExpire()) {
                                            Player seller = Bukkit.getPlayer(listing.getSellerUuid());
                                            if (seller != null && seller.isOnline()) {
                                                seller.sendMessage(plugin.getMessageManager().getPrefixed(
                                                        "messages.listing-expired",
                                                        "id", String.valueOf(listing.getId())
                                                ));
                                            }
                                        }
                                    }
                                });
                    }
                    if (!listings.isEmpty()) {
                        invalidateCache();
                    }
                });
    }

    /**
     * Invalidates the listing cache
     */
    public void invalidateCache() {
        cachedListings = null;
        cacheExpiry = 0;
    }

    /**
     * Result of a purchase attempt
     */
    public enum PurchaseResult {
        SUCCESS,
        NOT_FOUND,
        ALREADY_SOLD,
        OWN_LISTING,
        INSUFFICIENT_FUNDS,
        ECONOMY_ERROR,
        ALREADY_PROCESSING
    }
}

