public async Task <ClaimResult> EditPickupBid(PickupBid bid, PublisherGame?conditionalDropPublisherGame, uint bidAmount)
    {
        if (bid.Successful != null)
        {
            return(new ClaimResult(new List <ClaimError>()
            {
                new ClaimError("Bid has already been processed", false)
            }, null));
        }

        if (bidAmount < bid.LeagueYear.Options.MinimumBidAmount)
        {
            return(new ClaimResult(new List <ClaimError>()
            {
                new ClaimError("That bid does not meet the league's minimum bid.", false)
            }, null));
        }

        if (bidAmount > bid.Publisher.Budget)
        {
            return(new ClaimResult(new List <ClaimError>()
            {
                new ClaimError("You do not have enough budget to make that bid.", false)
            }, null));
        }

        int?validDropSlot = null;

        if (conditionalDropPublisherGame is not null)
        {
            if (bid.CounterPick)
            {
                return(new ClaimResult("Cannot make a counter pick bid with a conditional drop.", null));
            }

            var dropResult = await MakeDropRequest(bid.LeagueYear, bid.Publisher, conditionalDropPublisherGame, true);

            if (dropResult.Result.IsFailure)
            {
                return(new ClaimResult(dropResult.Result.Error, null));
            }

            validDropSlot = conditionalDropPublisherGame.SlotNumber;
        }

        await _fantasyCriticRepo.EditPickupBid(bid, conditionalDropPublisherGame, bidAmount);

        var currentDate = _clock.GetToday();
        MasterGameWithEligibilityFactors eligibilityFactors = bid.LeagueYear.GetEligibilityFactorsForMasterGame(bid.MasterGame, currentDate);
        var slotResult = SlotEligibilityService.GetPublisherSlotAcquisitionResult(bid.Publisher, bid.LeagueYear.Options, eligibilityFactors, bid.CounterPick, validDropSlot, false);

        if (!slotResult.SlotNumber.HasValue)
        {
            return(new ClaimResult(slotResult.ClaimErrors, null));
        }

        return(new ClaimResult(slotResult.SlotNumber.Value));
    }
    public static PossibleMasterGameYear GetPossibleMasterGameYear(MasterGameYear masterGame, IReadOnlyList <PublisherSlot> openNonCounterPickSlots,
                                                                   HashSet <MasterGame> publisherStandardMasterGames, HashSet <MasterGame> myPublisherMasterGames,
                                                                   MasterGameWithEligibilityFactors eligibilityFactors, LocalDate currentDate)
    {
        bool isEligible           = SlotEligibilityService.GameIsEligibleInLeagueYear(eligibilityFactors);
        bool taken                = publisherStandardMasterGames.Contains(masterGame.MasterGame);
        bool alreadyOwned         = myPublisherMasterGames.Contains(masterGame.MasterGame);
        bool isReleased           = masterGame.MasterGame.IsReleased(currentDate);
        bool willRelease          = masterGame.WillRelease();
        bool hasScore             = masterGame.MasterGame.CriticScore.HasValue;
        bool isEligibleInOpenSlot = SlotEligibilityService.GameIsEligibleInOpenSlot(openNonCounterPickSlots, eligibilityFactors);

        PossibleMasterGameYear possibleMasterGame = new PossibleMasterGameYear(masterGame, taken, alreadyOwned, isEligible, isEligibleInOpenSlot, isReleased, willRelease, hasScore);

        return(possibleMasterGame);
    }
    public PossibleMasterGameYear GetPossibleMasterGameYear(MasterGameYear masterGame, IReadOnlyList <PublisherSlot> openNonCounterPickSlots,
                                                            HashSet <MasterGame> publisherStandardMasterGames, HashSet <MasterGame> myPublisherMasterGames,
                                                            MasterGameWithEligibilityFactors eligibilityFactors, IEnumerable <LeagueTagStatus> tagsForSlot, LocalDate currentDate)
    {
        var  tagsToUse            = eligibilityFactors.TagOverrides.Any() ? eligibilityFactors.TagOverrides : masterGame.MasterGame.Tags;
        var  claimErrors          = LeagueTagExtensions.GameHasValidTags(tagsForSlot, new List <LeagueTagStatus>(), masterGame.MasterGame, tagsToUse, currentDate);
        bool isEligible           = !claimErrors.Any();
        bool taken                = publisherStandardMasterGames.Contains(masterGame.MasterGame);
        bool alreadyOwned         = myPublisherMasterGames.Contains(masterGame.MasterGame);
        bool isReleased           = masterGame.MasterGame.IsReleased(currentDate);
        bool willRelease          = masterGame.WillRelease();
        bool hasScore             = masterGame.MasterGame.CriticScore.HasValue;
        bool isEligibleInOpenSlot = SlotEligibilityService.GameIsEligibleInOpenSlot(openNonCounterPickSlots, eligibilityFactors);

        PossibleMasterGameYear possibleMasterGame = new PossibleMasterGameYear(masterGame, taken, alreadyOwned, isEligible, isEligibleInOpenSlot, isReleased, willRelease, hasScore);

        return(possibleMasterGame);
    }
    private static IReadOnlyList <ClaimError> GetClaimErrorsForLeagueYear(MasterGameWithEligibilityFactors eligibilityFactors)
    {
        //This function returns a list of errors if a game is not eligible in ANY slot
        if (eligibilityFactors.GameIsSpecificallyAllowed)
        {
            return(new List <ClaimError>());
        }

        if (eligibilityFactors.GameIsSpecificallyBanned)
        {
            return(new List <ClaimError>()
            {
                new ClaimError("That game has been specifically banned by your league.", false)
            });
        }

        var baseEligibilityResult = eligibilityFactors.CheckGameAgainstTags(eligibilityFactors.Options.LeagueTags, new List <LeagueTagStatus>());

        if (!baseEligibilityResult.Any())
        {
            return(baseEligibilityResult);
        }

        var specialGameSlots = eligibilityFactors.Options.SpecialGameSlots;

        foreach (var specialGameSlot in specialGameSlots)
        {
            var tagsForSlot = specialGameSlot.Tags.Select(x => new LeagueTagStatus(x, TagStatus.Required));
            var specialEligibilityResult = eligibilityFactors.CheckGameAgainstTags(eligibilityFactors.Options.LeagueTags, tagsForSlot);
            if (!specialEligibilityResult.Any())
            {
                return(specialEligibilityResult);
            }
        }

        //In this case, the game did not match the base rules, nor any special slots, so the errors we return will be for the base rules.
        return(baseEligibilityResult);
    }
    private IReadOnlyList <ClaimError> GetGenericSlotMasterGameErrors(LeagueYear leagueYear, MasterGame masterGame, int year, bool dropping,
                                                                      LocalDate currentDate, LocalDate dateOfPotentialAcquisition, bool counterPick, bool counterPickedGameIsManualWillNotRelease, bool drafting)
    {
        MasterGameWithEligibilityFactors eligibilityFactors = leagueYear.GetEligibilityFactorsForMasterGame(masterGame, dateOfPotentialAcquisition);
        List <ClaimError> claimErrors = new List <ClaimError>();

        bool manuallyEligible = eligibilityFactors.OverridenEligibility.HasValue && eligibilityFactors.OverridenEligibility.Value;
        bool released         = masterGame.IsReleased(currentDate);

        if (released)
        {
            claimErrors.Add(new ClaimError("That game has already been released.", true));
        }

        if (currentDate != dateOfPotentialAcquisition)
        {
            bool releaseBeforeNextBids = masterGame.IsReleased(dateOfPotentialAcquisition);
            if (releaseBeforeNextBids)
            {
                if (!dropping)
                {
                    claimErrors.Add(new ClaimError("That game will release before bids are processed.", true));
                }
                else
                {
                    claimErrors.Add(new ClaimError("That game will release before drops are processed.", true));
                }
            }
        }

        if (released && masterGame.ReleaseDate.HasValue && masterGame.ReleaseDate.Value.Year < year)
        {
            claimErrors.Add(new ClaimError($"That game was released prior to the start of {year}.", false));
        }

        bool willRelease = masterGame.MinimumReleaseDate.Year == year && !counterPickedGameIsManualWillNotRelease;

        if (!dropping && !released && !willRelease && !manuallyEligible)
        {
            claimErrors.Add(new ClaimError($"That game is not scheduled to be released in {year}.", true));
        }

        if (counterPick && !drafting)
        {
            if (masterGame.DelayContention)
            {
                claimErrors.Add(new ClaimError("That game is in 'delay contention', and therefore cannot be counter picked.", false));
            }

            bool confirmedWillRelease   = masterGame.ReleaseDate.HasValue && masterGame.ReleaseDate.Value.Year == year;
            bool acquiringAfterDeadline = dateOfPotentialAcquisition >= leagueYear.CounterPickDeadline;
            if (!confirmedWillRelease && acquiringAfterDeadline && willRelease)
            {
                claimErrors.Add(new ClaimError($"That game does not have a confirmed release date in {year}, and the 'counter pick deadline' has already passed (or will have by the time bids process).", false));
            }
        }

        bool hasScore = masterGame.CriticScore.HasValue;

        if (hasScore && !manuallyEligible)
        {
            claimErrors.Add(new ClaimError("That game already has a score.", true));
        }

        return(claimErrors);
    }
    public static bool GameIsEligibleInLeagueYear(MasterGameWithEligibilityFactors eligibilityFactors)
    {
        var leagueYearClaimErrors = GetClaimErrorsForLeagueYear(eligibilityFactors);

        return(!leagueYearClaimErrors.Any());
    }
    public static IReadOnlyList <ClaimError> GetClaimErrorsForSlot(PublisherSlot publisherSlot, MasterGameWithEligibilityFactors eligibilityFactors)
    {
        //This function returns a list of errors if a game is not eligible in THIS slot
        if (publisherSlot.CounterPick)
        {
            return(new List <ClaimError>());
        }

        if (eligibilityFactors.GameIsSpecificallyAllowed)
        {
            return(new List <ClaimError>());
        }

        if (eligibilityFactors.GameIsSpecificallyBanned)
        {
            return(new List <ClaimError>()
            {
                new ClaimError("That game has been specifically banned by your league.", false)
            });
        }

        if (publisherSlot.SpecialGameSlot is null)
        {
            var baseEligibilityResult = eligibilityFactors.CheckGameAgainstTags(eligibilityFactors.Options.LeagueTags, new List <LeagueTagStatus>());
            return(baseEligibilityResult);
        }

        //This is a special slot
        var tagsForSlot = publisherSlot.SpecialGameSlot.Tags.Select(x => new LeagueTagStatus(x, TagStatus.Required));
        var specialEligibilityResult = eligibilityFactors.CheckGameAgainstTags(eligibilityFactors.Options.LeagueTags, tagsForSlot);

        return(specialEligibilityResult);
    }
    public static bool GameIsEligibleInOpenSlot(IReadOnlyList <PublisherSlot> openNonCounterPickSlots, MasterGameWithEligibilityFactors eligibilityFactors)
    {
        foreach (var openSlot in openNonCounterPickSlots)
        {
            var claimErrorsForSlot = GetClaimErrorsForSlot(openSlot, eligibilityFactors);
            if (!claimErrorsForSlot.Any())
            {
                return(true);
            }
        }

        return(false);
    }
    public static int?GetTradeSlotResult(Publisher publisher, LeagueOptions leagueOptions, MasterGameYearWithCounterPick masterGameYearWithCounterPick, MasterGameWithEligibilityFactors eligibilityFactors, IEnumerable <int> openSlotNumbers)
    {
        var slots     = publisher.GetPublisherSlots(leagueOptions);
        var openSlots = slots.Where(x => x.CounterPick == masterGameYearWithCounterPick.CounterPick && openSlotNumbers.Contains(x.SlotNumber)).OrderBy(x => x.SlotNumber).ToList();

        if (!openSlots.Any())
        {
            return(null);
        }

        //At this point, there is an open slot. Which one is best?
        //We want to check the special slots first, then the regular slots.
        var openSpotsToCheckOrder = openSlots
                                    .OrderByDescending(x => x.SpecialGameSlot is not null)
                                    .ThenBy(x => x.SlotNumber).ToList();

        foreach (var openSlot in openSpotsToCheckOrder)
        {
            var claimErrorsForSlot = GetClaimErrorsForSlot(openSlot, eligibilityFactors);
            if (!claimErrorsForSlot.Any())
            {
                return(openSlot.SlotNumber);
            }
        }

        //This game isn't eligible in any slots, so we will just take the first open one.
        var bestSlot = openSlots.First();

        return(bestSlot.SlotNumber);
    }