// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {Army, Raider} from "../src/RaidGeldStructs.sol";
import "../src/RaidGeldUtils.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";

contract raid_geldTest is Test {
    function test_0_unit_price() public pure {
        // buying 1 unit of moloch_denier
        uint256 basePriceMolochDenier = RaidGeldUtils.calculateUnitPrice(0, 0, 1);
        assertEq(basePriceMolochDenier, RaidGeldUtils.MOLOCH_DENIER_BASE_COST);

        // buying 3 units
        // has to be a bit more than 3 * 38 = 114
        uint256 price = RaidGeldUtils.calculateUnitPrice(0, 0, 3);
        assertGt(price, 3 * basePriceMolochDenier);

        uint256 basePriceChamp = RaidGeldUtils.calculateUnitPrice(0, 0, 3);
        // buying 3 units of champions
        // has to be a bit more than 4 * 38 * 3 = 114
        price = RaidGeldUtils.calculateUnitPrice(3, 0, 3);
        assertGt(price, 3 * basePriceChamp);

        // buying 12 units of champions when u already have 10
        price = RaidGeldUtils.calculateUnitPrice(3, 10, 12);
        // price should scale up
        assertGt(price, basePriceChamp * 12);
    }

    function test_1_profits_per_second(uint16 _dLvl, uint16 _apLvl, uint16 _anLvl, uint16 _cLvl) public pure {
        Army memory army = Army({
            moloch_denier: Raider({level: 1}),
            apprentice: Raider({level: 0}),
            anointed: Raider({level: 0}),
            champion: Raider({level: 0}),
            profit_per_second: 0 // irrelevant for this test
        });
        uint256 profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
        assertEq(profits_per_second, RaidGeldUtils.MOLOCH_DENIER_PROFIT);

        army = Army({
            moloch_denier: Raider({level: _dLvl}),
            apprentice: Raider({level: _apLvl}),
            anointed: Raider({level: _anLvl}),
            champion: Raider({level: _cLvl}),
            profit_per_second: 0 // irrelevant for this test
        });
        profits_per_second = RaidGeldUtils.calculateProfitsPerSecond(army);
        uint256 expected = _dLvl * RaidGeldUtils.MOLOCH_DENIER_PROFIT + _apLvl * RaidGeldUtils.APPRENTICE_PROFIT
            + _anLvl * RaidGeldUtils.ANOINTED_PROFIT + _cLvl * RaidGeldUtils.CHAMPION_PROFIT;
        assertEq(profits_per_second, expected);
    }

    function getConfidenceInterval(uint8 bossLevel)
        internal
        pure
        returns (uint256 lower, uint256 upper)
    {
        /*
            man i struggled getting this math with just integers so its 
            easier to just do it like this (precalculated):
            z = 1.96 (95%)
            Probability | Expected | Variance | Margin | Lower Bound | Upper Bound
            99%	990	0.00099	62	928	1052
            89%	890	0.000979	61	829	951
            80%	800	0.00016	49	751	849
            70%	700	0.00021	45	655	745
            62%	620	0.0002356	48	572	668
            51%	510	0.0002499	49	461	559
            40%	400	0.00024	48	352	448
        */
        if (bossLevel == 0) {
            return (928, 1000);
        } else if (bossLevel == 1) {
            return (829, 951);
        } else if (bossLevel == 2) {
            return (751, 849);
        } else if (bossLevel == 3) {
            return (655, 745);
        } else if (bossLevel == 4) {
            return (572, 668);
        } else if (bossLevel == 5) {
            return (461, 559);
        } else if (bossLevel == 6) {
            return (352, 448);
        }
    }

    function test_21_random(uint256 seed, uint256 min, uint256 max) public pure {
        vm.assume(min < max);
        vm.assume(max < type(uint256).max);
        uint256 random = RaidGeldUtils.random(seed, min, max);
        vm.assertTrue(random >= min && random <= max, "random() fell outside its range");
    }

    function test_22_random_range(uint256 min, uint256 max) public {
        vm.assume(max > min);
        vm.assume(max - min < 100);
        vm.assume(max < type(uint256).max);
        uint256 range = max - min + 1;
        bool[] memory seen = new bool[](range);
        uint256 count = 0;
        for (uint256 i = 0; i < 100000; i++) {
            uint256 result = RaidGeldUtils.random(block.prevrandao + i, min, max);
            uint256 index = result - min;
            if (!seen[index]) {
                seen[index] = true;
                count++;
            }
            if (count == range) break;
        }
        assertEq(count, range, "Not all values in the interval were generated");
    }

    function test_3_calculateBossFight_probabilities() public {
        uint256[7] memory probabilities =
            [uint256(99), uint256(89), uint256(80), uint256(70), uint256(62), uint256(51), uint256(40)];
        uint256 totalRuns = 1000;
        console.log("Checking boss rolls (1000 times)");
        for (uint8 bossLevel = 0; bossLevel < 7; bossLevel++) {
            console.log("Boss level: ", bossLevel);
            uint256 bossPower = RaidGeldUtils.getBossPower(bossLevel);
            uint256 geldBurnt = bossPower;
            uint256 successCount = 0;
            for (uint256 testRun = 0; testRun < totalRuns; testRun++) {
                uint256 prevrandao = uint256(keccak256(abi.encodePacked(block.prevrandao, testRun * 1e9))); // Unique seed each time
                if (RaidGeldUtils.calculateBossFight(bossLevel, geldBurnt, prevrandao)) {
                    successCount++;
                }
            }
            (uint256 lowerProb, uint256 upperProb) = getConfidenceInterval(bossLevel);
            console.log("expected at least wins", lowerProb);
            console.log("expected at most wins", upperProb);
            console.log("actual wins", successCount);
            console.log("----------");
            vm.assertTrue(successCount >= lowerProb && successCount <= upperProb, "Success rate not within expected range");
        }
    }

    function test_4_print_boss_rewards() public {
        uint256 total = 0;
        for (uint8 i = 0; i < 7; i++) {
            uint256 reward = RaidGeldUtils.calculateBossReward(i, 500e18);
            console.log("Reward", i,reward);
            total += reward;
        }
        console.log("Total", total);
    }

    function test_5_last_boss_is_unique(uint256 _randomSeed) public {
        uint8[7] memory bosses = RaidGeldUtils.generate_boss_variants(_randomSeed);
        vm.assertEq(bosses[6], 6);
    }
}
