package com.yourname.premiumah.manager;

import com.yourname.premiumah.config.ConfigManager;
import com.yourname.premiumah.config.MessageManager;
import com.yourname.premiumah.economy.EconomyService;
import com.yourname.premiumah.model.ActionResult;
import com.yourname.premiumah.model.ClaimReason;
import com.yourname.premiumah.model.ClaimRecord;
import com.yourname.premiumah.model.ClaimType;
import com.yourname.premiumah.model.Listing;
import com.yourname.premiumah.model.ListingCreationResult;
import com.yourname.premiumah.model.ListingStatus;
import com.yourname.premiumah.storage.StorageManager;
import com.yourname.premiumah.util.IdGenerator;
import com.yourname.premiumah.util.InventoryUtil;
import com.yourname.premiumah.util.MaterialUtil;
import com.yourname.premiumah.util.TextUtil;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

public final class AuctionHouseManager {
    private final JavaPlugin plugin;
    private final ConfigManager config;
    private final MessageManager messages;
    private final StorageManager storage;
    private final ListingManager listings;
    private final ClaimManager claims;
    private final EconomyService economy;
    private BukkitTask expirationTask;

    public AuctionHouseManager(JavaPlugin plugin,
                               ConfigManager config,
                               MessageManager messages,
                               StorageManager storage,
                               ListingManager listings,
                               ClaimManager claims,
                               EconomyService economy) {
        this.plugin = plugin;
        this.config = config;
        this.messages = messages;
        this.storage = storage;
        this.listings = listings;
        this.claims = claims;
        this.economy = economy;
    }

    public void startTasks() {
        stopTasks();
        expirationTask = Bukkit.getScheduler().runTaskTimer(plugin, this::expireListings, 100L, config.listingExpireCheckTicks());
    }

    public void stopTasks() {
        if (expirationTask != null) {
            expirationTask.cancel();
            expirationTask = null;
        }
    }

    public boolean marketplaceReady() {
        return config.economyEnabled() && economy.isAvailable();
    }

    public synchronized void saveAll() {
        listings.saveToStorage();
        claims.saveToStorage();
        storage.save();
    }

    public synchronized ListingCreationResult createListing(Player seller, ItemStack item, double price) {
        if (!seller.hasPermission("premiumah.sell")) {
            return ListingCreationResult.fail("no-permission", Map.of());
        }
        if (!marketplaceReady()) {
            return ListingCreationResult.fail("economy-unavailable", Map.of());
        }
        if (InventoryUtil.isAir(item)) {
            return ListingCreationResult.fail("no-item-in-hand", Map.of());
        }
        if (!Double.isFinite(price) || price <= 0.0D) {
            return ListingCreationResult.fail("invalid-price", Map.of());
        }

        boolean bypassRestrictions = seller.hasPermission("premiumah.bypass.restrictions");
        if (!bypassRestrictions && price < config.minPrice()) {
            return ListingCreationResult.fail("price-too-low", Map.of("min", economy.format(config.minPrice())));
        }
        if (!bypassRestrictions && price > config.maxPrice()) {
            return ListingCreationResult.fail("price-too-high", Map.of("max", economy.format(config.maxPrice())));
        }
        if (!config.isItemAllowed(seller, item.getType())) {
            return ListingCreationResult.fail("item-blocked", Map.of());
        }
        int limit = config.listingLimit(seller);
        if (listings.activeCount(seller.getUniqueId()) >= limit) {
            return ListingCreationResult.fail("listing-limit", Map.of("limit", String.valueOf(limit)));
        }

        double fee = seller.hasPermission("premiumah.bypass.fees") ? 0.0D : (config.listingFeeEnabled() ? config.listingFee() : 0.0D);
        if (fee > 0.0D) {
            if (!economy.has(seller, fee)) {
                return ListingCreationResult.fail("not-enough-money", Map.of("price", economy.format(fee)));
            }
            if (!economy.withdraw(seller, fee)) {
                return ListingCreationResult.fail("economy-unavailable", Map.of());
            }
        }

        String id = nextListingId();
        long now = System.currentTimeMillis();
        Listing listing = new Listing(
                id,
                seller.getUniqueId(),
                seller.getName(),
                item.clone(),
                price,
                now,
                now + config.defaultListingDurationMillis(),
                ListingStatus.ACTIVE,
                null,
                null,
                now
        );
        listings.add(listing);
        saveAll();

        Map<String, String> placeholders = placeholders(
                "id", listing.id(),
                "item", itemName(item),
                "price", economy.format(price),
                "fee", economy.format(fee)
        );
        return ListingCreationResult.success(listing, fee > 0.0D ? "listing-created-fee" : "listing-created", placeholders);
    }

    public synchronized ActionResult buyListing(Player buyer, String listingId) {
        if (!buyer.hasPermission("premiumah.buy")) {
            return ActionResult.fail("no-permission", Map.of());
        }
        if (!marketplaceReady()) {
            return ActionResult.fail("economy-unavailable", Map.of());
        }

        Optional<Listing> optional = listings.get(listingId);
        if (optional.isEmpty()) {
            return ActionResult.fail("listing-not-found", Map.of());
        }
        Listing listing = optional.get();
        long now = System.currentTimeMillis();
        if (!listing.isActive(now)) {
            if (listing.status() == ListingStatus.ACTIVE && listing.expiresAt() <= now) {
                expireListingInternal(listing);
                saveAll();
            }
            return ActionResult.fail("listing-no-longer-available", Map.of());
        }
        if (!config.allowSellerSelfPurchase() && listing.sellerUuid().equals(buyer.getUniqueId())) {
            return ActionResult.fail("cannot-buy-own", Map.of());
        }

        ItemStack item = listing.item();
        boolean fitsInventory = InventoryUtil.canFit(buyer.getInventory(), item);
        if (!fitsInventory && (config.requireInventorySpaceToBuy() || !config.claimFullInventoryPurchases())) {
            return ActionResult.fail("inventory-full", Map.of());
        }
        if (!economy.has(buyer, listing.price())) {
            return ActionResult.fail("not-enough-money", Map.of("price", economy.format(listing.price())));
        }
        if (!economy.withdraw(buyer, listing.price())) {
            return ActionResult.fail("economy-unavailable", Map.of());
        }

        double sellerAmount = sellerProceeds(listing);
        OfflinePlayer seller = Bukkit.getOfflinePlayer(listing.sellerUuid());
        if (config.instantSellerPayment()) {
            if (!economy.deposit(seller, sellerAmount)) {
                economy.deposit(buyer, listing.price());
                return ActionResult.fail("economy-unavailable", Map.of());
            }
        } else {
            addMoneyClaim(listing.sellerUuid(), listing.sellerName(), listing.id(), sellerAmount, ClaimReason.SALE_PAYMENT);
        }

        listing.soldTo(buyer.getUniqueId(), buyer.getName());

        boolean deliveredToInventory = true;
        if (fitsInventory) {
            Map<Integer, ItemStack> leftovers = InventoryUtil.addItem(buyer.getInventory(), item);
            if (!leftovers.isEmpty()) {
                deliveredToInventory = false;
                for (ItemStack leftover : leftovers.values()) {
                    addItemClaim(buyer.getUniqueId(), buyer.getName(), listing.id(), leftover, ClaimReason.PURCHASE_DELIVERY);
                }
            }
        } else {
            deliveredToInventory = false;
            addItemClaim(buyer.getUniqueId(), buyer.getName(), listing.id(), item, ClaimReason.PURCHASE_DELIVERY);
        }

        saveAll();
        Player onlineSeller = Bukkit.getPlayer(listing.sellerUuid());
        if (onlineSeller != null) {
            messages.send(onlineSeller, "sold-notify", placeholders(
                    "buyer", buyer.getName(),
                    "price", economy.format(listing.price()),
                    "item", itemName(item),
                    "id", listing.id()
            ));
        }

        return ActionResult.success(deliveredToInventory ? "purchase-success" : "purchase-claim", placeholders(
                "price", economy.format(listing.price()),
                "item", itemName(item),
                "seller", listing.sellerName(),
                "id", listing.id()
        ));
    }

    public synchronized ActionResult cancelListing(Player player, String listingId) {
        if (!config.allowCancelActiveListings()) {
            return ActionResult.fail("listing-no-longer-available", Map.of());
        }
        Optional<Listing> optional = listings.get(listingId);
        if (optional.isEmpty()) {
            return ActionResult.fail("listing-not-found", Map.of());
        }
        Listing listing = optional.get();
        if (!listing.sellerUuid().equals(player.getUniqueId()) && !player.hasPermission("premiumah.admin.remove")) {
            return ActionResult.fail("no-permission", Map.of());
        }
        if (!listing.isActive(System.currentTimeMillis())) {
            return ActionResult.fail("listing-no-longer-available", Map.of());
        }
        listing.status(ListingStatus.CANCELLED);
        addItemClaim(listing.sellerUuid(), listing.sellerName(), listing.id(), listing.item(), ClaimReason.CANCELLED_LISTING);
        saveAll();
        return ActionResult.success("listing-cancelled", placeholders("id", listing.id()));
    }

    public synchronized ActionResult adminRemoveListing(String listingId) {
        Optional<Listing> optional = listings.get(listingId);
        if (optional.isEmpty()) {
            return ActionResult.fail("listing-not-found", Map.of());
        }
        Listing listing = optional.get();
        if (listing.status() == ListingStatus.ACTIVE) {
            listing.status(ListingStatus.REMOVED);
            if (config.reclaimAdminRemovedItems()) {
                addItemClaim(listing.sellerUuid(), listing.sellerName(), listing.id(), listing.item(), ClaimReason.ADMIN_REMOVED);
            }
        } else {
            listing.status(ListingStatus.REMOVED);
        }
        saveAll();
        Player seller = Bukkit.getPlayer(listing.sellerUuid());
        if (seller != null) {
            messages.send(seller, "listing-removed-admin", placeholders("id", listing.id()));
        }
        return ActionResult.success("listing-removed-admin", placeholders("id", listing.id()));
    }

    public synchronized ActionResult forceExpireListing(String listingId) {
        Optional<Listing> optional = listings.get(listingId);
        if (optional.isEmpty()) {
            return ActionResult.fail("listing-not-found", Map.of());
        }
        Listing listing = optional.get();
        if (listing.status() != ListingStatus.ACTIVE) {
            return ActionResult.fail("listing-no-longer-available", Map.of());
        }
        expireListingInternal(listing);
        saveAll();
        return ActionResult.success("listing-expired", placeholders("id", listing.id()));
    }

    public synchronized ActionResult claim(Player player, String claimId) {
        if (!player.hasPermission("premiumah.expired")) {
            return ActionResult.fail("no-permission", Map.of());
        }
        Optional<ClaimRecord> optional = claims.get(claimId);
        if (optional.isEmpty()) {
            return ActionResult.fail("nothing-to-claim", Map.of());
        }
        ClaimRecord claim = optional.get();
        if (!claim.ownerUuid().equals(player.getUniqueId())) {
            return ActionResult.fail("no-permission", Map.of());
        }

        if (claim.type() == ClaimType.MONEY) {
            if (!marketplaceReady()) {
                return ActionResult.fail("economy-unavailable", Map.of());
            }
            if (!economy.deposit(player, claim.moneyAmount())) {
                return ActionResult.fail("economy-unavailable", Map.of());
            }
            claims.remove(claim.id());
            saveAll();
            return ActionResult.success("item-reclaimed", placeholders("item", economy.format(claim.moneyAmount()), "id", claim.listingId()));
        }

        ItemStack item = claim.item();
        if (InventoryUtil.isAir(item)) {
            claims.remove(claim.id());
            saveAll();
            return ActionResult.fail("nothing-to-claim", Map.of());
        }
        if (!InventoryUtil.canFit(player.getInventory(), item)) {
            return ActionResult.fail("inventory-full", Map.of());
        }
        Map<Integer, ItemStack> leftovers = InventoryUtil.addItem(player.getInventory(), item);
        if (!leftovers.isEmpty()) {
            return ActionResult.fail("inventory-full", Map.of());
        }
        claims.remove(claim.id());
        markListingClaimedIfComplete(claim.listingId());
        saveAll();
        return ActionResult.success("item-reclaimed", placeholders("item", itemName(item), "id", claim.listingId()));
    }

    public synchronized void expireListings() {
        long now = System.currentTimeMillis();
        boolean changed = false;
        for (Listing listing : listings.expiredActiveListings(now)) {
            expireListingInternal(listing);
            Player seller = Bukkit.getPlayer(listing.sellerUuid());
            if (seller != null) {
                messages.send(seller, "listing-expired", placeholders("id", listing.id()));
            }
            changed = true;
        }
        if (changed) {
            saveAll();
        }
    }

    public ClaimRecord addItemClaim(UUID ownerUuid, String ownerName, String listingId, ItemStack item, ClaimReason reason) {
        ClaimRecord claim = new ClaimRecord(nextClaimId(), ownerUuid, ownerName, listingId, ClaimType.ITEM, reason, item, 0.0D, System.currentTimeMillis());
        claims.add(claim);
        return claim;
    }

    public ClaimRecord addMoneyClaim(UUID ownerUuid, String ownerName, String listingId, double amount, ClaimReason reason) {
        ClaimRecord claim = new ClaimRecord(nextClaimId(), ownerUuid, ownerName, listingId, ClaimType.MONEY, reason, null, amount, System.currentTimeMillis());
        claims.add(claim);
        return claim;
    }

    public double sellerProceeds(Listing listing) {
        double amount = listing.price();
        if (!config.salesTaxEnabled()) {
            return amount;
        }
        Player seller = Bukkit.getPlayer(listing.sellerUuid());
        if (seller != null && seller.hasPermission("premiumah.bypass.tax")) {
            return amount;
        }
        double tax = amount * (config.salesTaxPercent() / 100.0D);
        return Math.max(0.0D, amount - tax);
    }

    public String formatMoney(double amount) {
        return economy.format(amount);
    }

    public String itemName(ItemStack item) {
        if (InventoryUtil.isAir(item)) {
            return "Air";
        }
        ItemMeta meta = item.getItemMeta();
        String name;
        if (meta != null && meta.hasDisplayName()) {
            name = TextUtil.plain(meta.displayName());
        } else {
            name = MaterialUtil.pretty(item.getType());
        }
        if (item.getAmount() > 1) {
            return name + " x" + item.getAmount();
        }
        return name;
    }

    private void expireListingInternal(Listing listing) {
        if (listing.status() != ListingStatus.ACTIVE) {
            return;
        }
        listing.status(ListingStatus.EXPIRED);
        if (!claims.hasItemClaimForListing(listing.id())) {
            addItemClaim(listing.sellerUuid(), listing.sellerName(), listing.id(), listing.item(), ClaimReason.EXPIRED_LISTING);
        }
    }

    private void markListingClaimedIfComplete(String listingId) {
        if (listingId == null || claims.hasItemClaimForListing(listingId)) {
            return;
        }
        listings.get(listingId).ifPresent(listing -> {
            if (listing.status() == ListingStatus.EXPIRED
                    || listing.status() == ListingStatus.CANCELLED
                    || listing.status() == ListingStatus.REMOVED) {
                listing.status(ListingStatus.CLAIMED);
            }
        });
    }

    private String nextListingId() {
        String id;
        do {
            id = IdGenerator.listingId();
        } while (listings.contains(id));
        return id;
    }

    private String nextClaimId() {
        String id;
        do {
            id = IdGenerator.claimId();
        } while (claims.get(id).isPresent());
        return id;
    }

    private Map<String, String> placeholders(String... pairs) {
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i + 1 < pairs.length; i += 2) {
            map.put(pairs[i], pairs[i + 1] == null ? "" : pairs[i + 1]);
        }
        return map;
    }
}
