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

import {Test, console} from "forge-std/Test.sol";
import {RaidGeld, Army, Player} from "../src/RaidGeld.sol";
import "../src/RaidGeldUtils.sol";

contract raid_geldTest is Test {
    RaidGeld public raid_geld;
    address public player1;
    address public player2;
    address public owner;

    function setUp() public {
        owner = address(0x126);
        player1 = address(0x123);
        vm.deal(owner, 10 ether);
        vm.deal(player1, 10 ether);
        vm.prank(owner);
        raid_geld = new RaidGeld();
    }

    function registerPlayer() private {
        raid_geld.register{value: raid_geld.BUY_IN_AMOUNT()}();
    }

    function test_00_no_fallback() public {
        vm.expectRevert();
        // Send Ether with some data to trigger fallback
        (bool success,) = address(raid_geld).call{value: 0.1 ether}("0x1234");
    }

    function test_01_no_receive() public {
        vm.startPrank(player1);
        vm.expectRevert();
        payable(address(raid_geld)).transfer(0.1 ether);
    }

    function test_02_registration() public {
        vm.startPrank(player1);

        uint256 initialBalance = address(raid_geld).balance;

        // Send registration fee ETH to the contract
        registerPlayer();

        // Check that initialraid_geld.is received by the player
        assertEq(raid_geld.balanceOf(player1), raid_geld.INITIAL_GELD());

        // Verify the contract balance is updated
        assertEq(address(raid_geld).balance, initialBalance + raid_geld.BUY_IN_AMOUNT());

        // Verify player is set initially
        Player memory player = raid_geld.getPlayer(player1);
        assertEq(player.total_minted, raid_geld.INITIAL_GELD());
        assertEq(player.created_at, block.timestamp);
        assertEq(player.last_raided_at, block.timestamp);

        Army memory army = raid_geld.getArmy(player1);

        assertEq(army.moloch_denier.level, 0);
        assertEq(army.apprentice.level, 0);
        assertEq(army.anointed.level, 0);
        assertEq(army.champion.level, 0);
    }

    function test_03_funds_can_be_withdrawn() public {
        uint256 initialBalance = owner.balance;

        // Switch to Player 1 and register it
        vm.startPrank(player1);
        registerPlayer();

        // Switch back to owner and withdraw funds
        vm.startPrank(owner);
        raid_geld.withdraw();
        uint256 newBalance = owner.balance;
        uint256 newContractBalance = address(raid_geld).balance;

        // contract balance should be empty
        assertEq(newContractBalance, 0);
        // owner should have the extra funds
        assertEq(newBalance, initialBalance + raid_geld.BUY_IN_AMOUNT());
    }

    function test_04_only_owner_can_withdraw() public {
        // Register player 1
        vm.startPrank(player1);
        registerPlayer();

        // attempt to withdraw with player 1, it should fail
        vm.expectRevert();
        raid_geld.withdraw();
    }

    function test_05_is_registered() public {
        bool is_registered = raid_geld.isRegistered(player1);
        assertEq(is_registered, false);
        vm.startPrank(player1);
        registerPlayer();
        is_registered = raid_geld.isRegistered(player1);
        assertEq(is_registered, true);
    }

    function test_06_add_unit() public {
        vm.startPrank(player1);
        registerPlayer();

        vm.expectRevert();
        // units should be in range
        raid_geld.addUnit(100, 1);

        // Player should have enough tokens to burn to get units
        vm.expectRevert();
        raid_geld.addUnit(1, 100);

        Army memory army = raid_geld.getArmy(player1);
        uint256 unit_level = army.moloch_denier.level;
        uint256 balance = raid_geld.balanceOf(player1);
        uint256 income_per_sec = army.profit_per_second;
        // Add 1 unit
        raid_geld.addUnit(0, 1);
        uint256 unitPrice = RaidGeldUtils.calculateUnitPrice(0, 0, 1);

        // Check that those tokens were burnt
        // WARN: In addUnit will mint additional tokens but they are not calculated
        // into this because the test doesnt move the blockchain so no extra tokens
        // are minted to the user
        uint256 newBalance = raid_geld.balanceOf(player1);
        assertEq(newBalance, balance - unitPrice);
        army = raid_geld.getArmy(player1);

        // Check that unit level increased
        uint256 new_unit_level = army.moloch_denier.level;
        assertEq(new_unit_level, unit_level + 1);
        // Check that user income per second increased
        uint256 new_income_per_sec = army.profit_per_second;
        assertLt(income_per_sec, new_income_per_sec);
    }

    function test_07_raid() public {
        // Let some time pass so we dont start at block timestamp 0
        vm.warp(120);

        // Register player 1
        vm.startPrank(player1);
        registerPlayer();

        // bought 1 moloch_denier
        raid_geld.addUnit(0, 1);
        vm.warp(block.timestamp + 15);

        uint256 balance = raid_geld.balanceOf(player1);

        // Trigger raid funds minting
        raid_geld.raid();

        // New balance should be larger
        uint256 newBalance = raid_geld.balanceOf(player1);
        Player memory player = raid_geld.getPlayer(player1);
        uint256 last_raided_at = player.last_raided_at;
        assertLt(balance, newBalance);

        // After wait time passes raid should bring in profits again
        vm.warp(block.timestamp + 15);
        raid_geld.raid();

        // Balance should reflect that
        uint256 newestBalance = raid_geld.balanceOf(player1);
        player = raid_geld.getPlayer(player1);
        uint256 last_raided_at_2 = player.last_raided_at;
        assertLt(newBalance, newestBalance);
        assertLt(last_raided_at, last_raided_at_2);
    }
}
