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

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

contract GeldTest is Test {
    Geld public geld;
    address public player1;
    address public player2;
    address public owner;

    function setUp() public {
        owner = address(0x126);
        player1 = address(0x123);
        player2 = address(0x124);
        vm.deal(owner, 10 ether);
        vm.deal(player1, 10 ether);
        vm.deal(player2, 10 ether);
        vm.prank(owner);
        geld = new Geld();
    }

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

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

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

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

        uint256 initialBalance = address(geld).balance;
        uint256 initialTotalMinted = geld.total_minted();

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

        // Check that initial GELD is received by the player
        assertEq(geld.balanceOf(player1), geld.INITIAL_GELD());

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

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

        Army memory army = geld.getArmy();

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

        // Verify that total_minted is updated
        assertEq(geld.total_minted(), initialTotalMinted + geld.INITIAL_GELD());
    }

    function test_03_fundsCanBeWithdrawn() 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);
        geld.withdraw();
        uint256 newBalance = owner.balance;
        uint256 newContractBalance = address(geld).balance;

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

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

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

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

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

        uint256 balance = geld.balanceOf(player1);
        uint256 total_minted = geld.total_minted();

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

        // New balance should be larger
        uint256 newBalance = geld.balanceOf(player1);
        uint256 newTotalMinted = geld.total_minted();
        assertLt(balance, newBalance);
        assertLt(total_minted, newTotalMinted);

        // Expect fail if we raid again, we need to wait a bit
        vm.expectRevert();
        geld.raid();

        // After wait time passes raid should work again
        vm.warp(block.timestamp + geld.RAID_WAIT());
        geld.raid();

        // Balance should reflect that
        uint256 newestBalance = geld.balanceOf(player1);
        uint256 newestTotalMinted = geld.total_minted();
        assertLt(newTotalMinted, newestTotalMinted);
        assertLt(newBalance, newestBalance);
    }
}
