//! FIFA 17 economy POLICY mappers (pure, game-specific).
//!
//! These translate FIFA 17 wire semantics into the generic amounts the host
//! feeds to Core economy authority. They own NO state — Core owns balances and
//! inventory; these are the FIFA-specific numbers/derivations. Pack prices come
//! from the Store catalogue; the transfer-market fee is the FUT-era 5%.
//!
//! Match result mapping + reward-body shaping live in [`crate::fut::match_wire`].

use crate::fut::store_catalog::pack_by_id;

/// The Store buy-now price for a pack id (`None` for unknown/owned-only packs,
/// which are never purchasable).
pub fn pack_price(pack_id: u64) -> Option<u64> {
    pack_by_id(pack_id)
        .filter(|p| !p.owned_only)
        .map(|p| p.price)
}

/// FIFA 17 transfer-market fee, in PERCENT of the gross sale price.
///
/// FIFA17-HISTORICAL: 5% is the well-documented FUT transfer tax of the era. It
/// was not recovered from our client binary — no `tax`/`netPrice`/`sellerProceeds`
/// wire field exists (`docs/FIFA17_TRANSFER_MARKET_WIRE.md`), because the client
/// is told only the GROSS price and the deduction is server-side.
pub const TRANSFER_MARKET_FEE_PERCENT: i64 = 5;

/// Fee withheld from a completed sale of `gross` coins.
///
/// Integer arithmetic only — coin settlement never touches floating point, where
/// `0.05` is not representable and a large price could round a coin into or out of
/// existence. Widening to `i128` for the multiply makes overflow unreachable for
/// any `i64` price, so no ceiling has to be assumed.
///
/// ROUNDING, and it is a CHOICE that needs live confirmation: the fee is FLOORED,
/// so the seller keeps the fractional coin. That is deliberate — it makes
/// `fee + proceeds == gross` hold exactly for every input, which is the property
/// the accounting invariant depends on. The discriminating case against the
/// alternative (flooring the seller's 95% instead) is a gross of 150: this rule
/// pays 143, the alternative 142. Nothing in the corpus settles which the real
/// server did, so this MUST NOT be treated as confirmed FIFA behaviour.
///
/// A negative gross is not a sale; it yields a zero fee rather than inventing a
/// negative one, and `settle_sale` rejects the price itself.
pub fn transfer_market_fee(gross: i64) -> i64 {
    if gross <= 0 {
        return 0;
    }
    ((gross as i128 * TRANSFER_MARKET_FEE_PERCENT as i128) / 100) as i64
}

/// What the seller is credited for a completed sale of `gross` coins.
///
/// Defined as `gross - fee` rather than as its own percentage, so the pair can
/// never disagree about where a rounded coin went.
pub fn seller_proceeds(gross: i64) -> i64 {
    gross.max(0) - transfer_market_fee(gross)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pack_price_rejects_unknown_and_owned_only() {
        assert!(pack_price(1).is_some());
        assert_eq!(pack_price(65534), None); // sentinel absent from catalogue
        assert_eq!(pack_price(70), None); // owned-only reward pack, not purchasable
    }

    /// Pins the rounding rule at every boundary the fee can turn over. If one of
    /// these ever changes, monetary behaviour changed — that must be deliberate.
    #[test]
    fn transfer_market_fee_is_floored_five_percent() {
        // (gross, expected fee, expected proceeds)
        let cases = [
            (0i64, 0i64, 0i64),
            (1, 0, 1),   // 0.05 -> 0
            (19, 0, 19), // 0.95 -> 0, the last fee-free price
            (20, 1, 19), // exactly 1.0, the first price that pays
            (21, 1, 20), // 1.05 -> 1
            (39, 1, 38), // 1.95 -> 1
            (40, 2, 38), // exactly 2.0
            (100, 5, 95),
            (101, 5, 96),   // 5.05 -> 5
            (119, 5, 114),  // 5.95 -> 5, last price paying 5
            (120, 6, 114),  // exactly 6.0
            (149, 7, 142),  // 7.45 -> 7
            (150, 7, 143),  // 7.5 -> 7: THE discriminating case (alternative: 8/142)
            (151, 7, 144),  // 7.55 -> 7 (a HALF-UP rule would pay 8 here too)
            (199, 9, 190),  // 9.95 -> 9
            (200, 10, 190), // exactly 10.0
            (1_000, 50, 950),
            (15_000, 750, 14_250),             // the canonical fixture
            (15_000_000, 750_000, 14_250_000), // FUT's practical price ceiling
            (i64::MAX, i64::MAX / 20, i64::MAX - i64::MAX / 20), // no overflow
        ];
        for (gross, fee, proceeds) in cases {
            assert_eq!(transfer_market_fee(gross), fee, "fee for gross {gross}");
            assert_eq!(
                seller_proceeds(gross),
                proceeds,
                "proceeds for gross {gross}"
            );
        }
    }

    /// The property the whole settlement's accounting rests on: the fee and the
    /// seller's proceeds account for the gross EXACTLY, with no coin created or
    /// destroyed by rounding, at every price.
    #[test]
    fn fee_plus_proceeds_is_exactly_gross() {
        for gross in (0i64..2_000).chain([15_000, 999_999, 15_000_000, i64::MAX]) {
            assert_eq!(
                transfer_market_fee(gross) + seller_proceeds(gross),
                gross,
                "fee + proceeds != gross at {gross}"
            );
        }
    }

    /// A non-sale must not invent a negative fee.
    #[test]
    fn negative_gross_yields_no_fee() {
        assert_eq!(transfer_market_fee(-1), 0);
        assert_eq!(transfer_market_fee(i64::MIN), 0);
        assert_eq!(seller_proceeds(-100), 0);
    }
}
