// Tries to bid and buy items based on their prices and chances set in configs
void AuctionBotBuyer::BuyAndBidItems(BuyerConfiguration& config)
{
    time_t now = time(nullptr);
    AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());
    CheckEntryMap& items = config.EligibleItems;

    // Max amount of items to buy or bid
    uint32 cycles = sAuctionBotConfig->GetItemPerCycleNormal();
    if (items.size() > sAuctionBotConfig->GetItemPerCycleBoost())
    {
        // set more cycles if there is a huge influx of items
        cycles = sAuctionBotConfig->GetItemPerCycleBoost();
        TC_LOG_DEBUG("ahbot", "AHBot: Boost value used for Buyer! (if this happens often adjust both ItemsPerCycle in worldserver.conf)");
    }

    // Process items eligible to be bidded or bought
    CheckEntryMap::iterator itr = items.begin();
    while (cycles && itr != items.end())
    {
        AuctionEntry* auction = auctionHouse->GetAuction(itr->second.AuctionId);
        if (!auction)
        {
            TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?", itr->second.AuctionId);
            items.erase(itr++);
            continue;
        }

        // Check if the item has been checked once before
        // If it has been checked and it was recently, skip it
        if (itr->second.LastChecked && (now - itr->second.LastChecked) <= _checkInterval)
        {
            TC_LOG_DEBUG("ahbot", "AHBot: In time interval wait for entry %u!", auction->Id);
            ++itr;
            continue;
        }

        Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
        if (!item)
        {
            // auction item not accessible, possible auction in payment pending mode
            items.erase(itr++);
            continue;
        }

        // price to bid if bidding
        uint32 bidPrice;
        if (auction->bid >= auction->startbid)
        {
            // get bid price to outbid previous bidder
            bidPrice = auction->bid + auction->GetAuctionOutBid();
        }
        else
        {
            // no previous bidders - use starting bid
            bidPrice = auction->startbid;
        }

        const BuyerItemInfo* ahInfo = nullptr;
        BuyerItemInfoMap::const_iterator sameItemItr = config.SameItemInfo.find(item->GetEntry());
        if (sameItemItr != config.SameItemInfo.end())
            ahInfo = &sameItemItr->second;

        TC_LOG_DEBUG("ahbot", "AHBot: Rolling for AHentry %u:", auction->Id);

        // Roll buy and bid chances
        bool successBuy = RollBuyChance(ahInfo, item, auction, bidPrice);
        bool successBid = RollBidChance(ahInfo, item, auction, bidPrice);

        // If roll bidding succesfully and bid price is above buyout -> buyout
        // If roll for buying was successful but not for bid, buyout directly
        // If roll bidding was also successful, buy the entry with 20% chance
        // - Better bid than buy since the item is bought by bot if no player bids after
        // Otherwise bid if roll for bid was successful
        if ((auction->buyout && successBid && bidPrice >= auction->buyout) ||
            (successBuy && (!successBid || urand(1, 5) == 1)))
            BuyEntry(auction, auctionHouse); // buyout
        else if (successBid)
            PlaceBidToEntry(auction, bidPrice); // bid

        itr->second.LastChecked = now;
        --cycles;
        ++itr;
    }

    // Clear not needed entries
    config.SameItemInfo.clear();
}
void AuctionBotBuyer::AddNewAuctionBuyerBotBid(BuyerConfiguration& config)
{
    AuctionHouseObject* auctionHouse = sAuctionMgr->GetAuctionsMap(config.GetHouseType());

    PrepareListOfEntry(config);

    time_t now = time(nullptr);
    uint32 buyCycles;
    if (config.CheckedEntry.size() > sAuctionBotConfig->GetItemPerCycleBoost())
    {
        buyCycles = sAuctionBotConfig->GetItemPerCycleBoost();
        TC_LOG_DEBUG("ahbot", "AHBot: Boost value used for Buyer! (if this happens often adjust both ItemsPerCycle in worldserver.conf)");
    }
    else
        buyCycles = sAuctionBotConfig->GetItemPerCycleNormal();

    for (CheckEntryMap::iterator itr = config.CheckedEntry.begin(); itr != config.CheckedEntry.end();)
    {
        AuctionEntry* auction = auctionHouse->GetAuction(itr->second.AuctionId);
        if (!auction) // is auction not active now
        {
            TC_LOG_DEBUG("ahbot", "AHBot: Entry %u doesn't exists, perhaps bought already?",
                itr->second.AuctionId);

            config.CheckedEntry.erase(itr++);
            continue;
        }

        if (itr->second.LastChecked != 0 && (now - itr->second.LastChecked) <= _checkInterval)
        {
            TC_LOG_DEBUG("ahbot", "AHBot: In time interval wait for entry %u!", auction->Id);
            ++itr;
            continue;
        }

        if (buyCycles == 0)
            break;

        uint32 maxChance = 5000;

        Item* item = sAuctionMgr->GetAItem(auction->itemGUIDLow);
        if (!item) // auction item not accessible, possible auction in payment pending mode
        {
            config.CheckedEntry.erase(itr++);
            continue;
        }

        ItemTemplate const* prototype = item->GetTemplate();

        uint32 basePrice = sAuctionBotConfig->GetConfig(CONFIG_AHBOT_BUYPRICE_BUYER) ? prototype->BuyPrice : prototype->SellPrice;
        basePrice *= item->GetCount();

        uint32 maxBuyablePrice = (basePrice * config.BuyerPriceRatio) / 100;
        BuyerItemInfoMap::iterator sameItemItr = config.SameItemInfo.find(item->GetEntry());
        uint32 buyoutPrice = auction->buyout / item->GetCount();

        uint32 bidPrice;
        uint32 bidPriceByItem;
        uint32 minBidPrice;
        uint32 minBuyPrice;
        if (auction->bid >= auction->startbid)
        {
            bidPrice = auction->GetAuctionOutBid();
            bidPriceByItem = auction->bid / item->GetCount();
        }
        else
        {
            bidPrice = auction->startbid;
            bidPriceByItem = auction->startbid / item->GetCount();
        }

        double inGameBuyPrice;
        double inGameBidPrice;
        if (sameItemItr == config.SameItemInfo.end())
        {
            inGameBuyPrice = 0;
            inGameBidPrice = 0;
            minBidPrice = 0;
            minBuyPrice = 0;
        }
        else
        {
            if (sameItemItr->second.ItemCount == 1)
                maxBuyablePrice = maxBuyablePrice * 5; // if only one item exist can be bought if the price is high too.
            inGameBuyPrice = sameItemItr->second.BuyPrice / sameItemItr->second.ItemCount;
            inGameBidPrice = sameItemItr->second.BidPrice / sameItemItr->second.ItemCount;
            minBidPrice = sameItemItr->second.MinBidPrice;
            minBuyPrice = sameItemItr->second.MinBuyPrice;
        }

        uint32 maxBidablePrice = maxBuyablePrice - (maxBuyablePrice / 30); // Max Bidable price defined to 70% of max buyable price

        TC_LOG_DEBUG("ahbot", "AHBot: Auction added with data:");
        TC_LOG_DEBUG("ahbot", "AHBot: MaxPrice of Entry %u is %.1fg.", itr->second.AuctionId, double(maxBuyablePrice) / 10000.0);
        TC_LOG_DEBUG("ahbot", "AHBot: GamePrice buy=%.1fg, bid=%.1fg.", inGameBuyPrice / 10000, inGameBidPrice / 10000);
        TC_LOG_DEBUG("ahbot", "AHBot: Minimal price see in AH Buy=%ug, Bid=%ug.",
            minBuyPrice / 10000, minBidPrice / 10000);
        TC_LOG_DEBUG("ahbot", "AHBot: Actual Entry price,  Buy=%ug, Bid=%ug.", buyoutPrice / 10000, bidPrice / 10000);

        if (!auction->owner)                // Original auction owner
            maxChance = maxChance / 5;      // if Owner is AHBot this mean player placed bid on this auction. We divide by 5 chance for AhBuyer to place bid on it. (This make more challenge than ignore entry)
        if (auction->buyout != 0)           // Is the item directly buyable?
        {
            if (IsBuyableEntry(buyoutPrice, inGameBuyPrice, maxBuyablePrice, minBuyPrice, maxChance, config.FactionChance))
            {
                if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance))
                {
                    if (urand(0, 5) == 0)
                        PlaceBidToEntry(auction, bidPrice);
                    else
                        BuyEntry(auction);
                }
                else
                    BuyEntry(auction);
            }
            else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance / 2, config.FactionChance))
                PlaceBidToEntry(auction, bidPrice);
        }
        else if (IsBidableEntry(bidPriceByItem, inGameBuyPrice, maxBidablePrice, minBidPrice, maxChance, config.FactionChance))
            PlaceBidToEntry(auction, bidPrice);

        itr->second.LastChecked = now;
        --buyCycles;

        ++itr;
    }
}