namespace KArtSell.Modules.SignalEngine.Domain;

/// <summary>
/// Immutable point-in-time input resolved server-side from an approved EvidenceSnapshot/read model.
/// Security weight and lot weight are intentionally separate: a lot-relative sell ratio must not be
/// multiplied by the whole security weight.
/// </summary>
public sealed record SellDecisionInput(
    Guid PositionLotId,
    Guid CycleId,
    string EvidenceId,
    string DatasetId,
    string ModelVersion,
    string ConfigVersion,
    string CodeSha,
    DateTimeOffset AsOf,
    DateTimeOffset PublishedAtCutoff,
    decimal CurrentSecurityPortfolioWeight,
    decimal CurrentLotPortfolioWeight,
    decimal StrategicCoreFloorWeight,
    bool HardImpairmentApproved,
    bool CapitalFloorBreached,
    decimal SurvivalSellRatioOfLot,
    decimal GapBelowFloorAtr,
    int ConsecutiveCloseBreaches,
    bool CooldownSatisfied,
    decimal ConcentrationSellRatioOfLot,
    decimal OpportunityEdgeLowerBound,
    decimal OpportunitySellRatioOfLot)
{
    public void EnsureValid()
    {
        if (PublishedAtCutoff > AsOf)
        {
            throw new InvalidOperationException("PublishedAtCutoff cannot be later than AsOf.");
        }

        if (CurrentSecurityPortfolioWeight is < 0m or > 1m)
        {
            throw new InvalidOperationException("CurrentSecurityPortfolioWeight must be between 0 and 1.");
        }

        if (CurrentLotPortfolioWeight is < 0m or > 1m
            || CurrentLotPortfolioWeight > CurrentSecurityPortfolioWeight)
        {
            throw new InvalidOperationException("CurrentLotPortfolioWeight must be between 0 and the security weight.");
        }

        if (StrategicCoreFloorWeight is < 0m or > 1m)
        {
            throw new InvalidOperationException("StrategicCoreFloorWeight must be between 0 and 1.");
        }
    }

    public decimal MaxSellRatioPreservingStrategicCore()
    {
        if (CurrentLotPortfolioWeight <= 0m)
        {
            return 0m;
        }

        var sellableSecurityWeight = Math.Max(0m, CurrentSecurityPortfolioWeight - StrategicCoreFloorWeight);
        return Math.Clamp(sellableSecurityWeight / CurrentLotPortfolioWeight, 0m, 1m);
    }

    public decimal TargetSecurityPortfolioWeightAfter(decimal sellRatioOfLot)
        => decimal.Round(
            Math.Max(0m, CurrentSecurityPortfolioWeight
                - CurrentLotPortfolioWeight * Math.Clamp(sellRatioOfLot, 0m, 1m)),
            8,
            MidpointRounding.ToEven);
}
