// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IERC20 {
    function transfer(address recipient, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);
}

// Hats off to Synthetix for their staking contract.
// There's not much left of her here now, but she still hums quietly under the hood.
// https://app.aggressive.wtf

// Stake with caution, there are no withdrawal functions.
// This is an active-risk contract, staked tokens accrue a dynamic risk of loss.
// Paused staking does not affect claiming rewards.

contract pool {
    address public tokenAddress; // ERC20 being staked.
    address public treasuryAddress = 0x6de77170E1F71B80642D55c29f595aC37b91eBf6; // Treasury.
    address public burnAddress = 0x000000000000000000000000000000000000dEaD; // Burn.
    address public owner; // Contract owner (initialized in constructor as deployer).
    uint256 public rewardPercentage; // Reward generated per hour (as a percentage of staker.amount).
    uint256 public riskModifier; // Additional risk generated per hour (as a flat percentage).
    uint256 public rewardChance; // Risk.
    
    bool public stakingPaused = false; // Pauses the ability to stake, claiming cannot be paused.

    struct Staker {
        uint256 amount;
        uint256 time;
        uint256 wins;
        uint256 losses;
    }

    mapping(address => Staker) public stakers;
    mapping(address => bool) public admins;

    constructor(address _tokenAddress, uint256 _rewardChance) {
        tokenAddress = _tokenAddress;
        rewardChance = _rewardChance;
        rewardPercentage = 10; // 1%
        riskModifier = 10000; // 1%
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only the contract owner can call this function");
        _;
    }

    modifier notPaused() {
        require(!stakingPaused, "The game is currently paused, you can not play at this time.");
        _;
    }

    modifier onlyAdmin() {
    require(admins[msg.sender], "Only admins can call this function");
    _;
    }

    function stakeTokens(uint256 _amount) public notPaused { // Users stake an amount of tokens.
        Staker storage staker = stakers[msg.sender];
        require(_amount > 0, "Amount cannot be zero");
        require(staker.time == 0, "Complete your current staking cycle first.");
        require(IERC20(tokenAddress).transferFrom(msg.sender, address(this), _amount), "Token transfer failed");
        
        staker.amount += _amount;
        if (staker.time == 0) {
            staker.time = block.timestamp; // Set the start time if it's not already set
        }

        uint256 stakerTime = staker.time;
        emit TokensStaked(msg.sender, _amount, stakerTime);
    }

    event TokensStaked(address indexed staker, uint256 _amount, uint256 time);

    function claimReward() public { // Users claim their rewards to see if they've won or lost.
        Staker memory staker = stakers[msg.sender];
        require(staker.amount > 0, "No tokens staked");
        require(block.timestamp >= staker.time + 1 hours, "You need to stake your tokens for a minimum of 1 hour(s), try again soon.");

        uint256 elapsedTime = (block.timestamp - staker.time); // Calculate elapsed time in seconds

        uint256 reward = staker.amount + (staker.amount * elapsedTime * rewardPercentage) / (1000 * 3600);

        uint256 additionalModifier = (riskModifier / 3600) * elapsedTime; // Adjusted modifier for seconds instead of hours
        uint256 currentChance = rewardChance + additionalModifier;

        if (currentChance > 0 && block.timestamp % 1000000 < currentChance) {

            // Transfer fee and clear balance
            uint256 burnAmount = staker.amount / 5;
            uint256 trueBurnAmount = burnAmount / 2;
            uint256 treasuryAmount = burnAmount / 2;
            if (burnAmount > 0) {
                IERC20(tokenAddress).transfer(burnAddress, trueBurnAmount);
                IERC20(tokenAddress).transfer(treasuryAddress, treasuryAmount);
            }

            // Clear stakers balance
            stakers[msg.sender].amount = 0;
            stakers[msg.sender].time = 0;
            stakers[msg.sender].losses += 1;
            emit Loss(msg.sender, reward, currentChance, burnAmount);
        } else {

            // Transfer fee and the rest to the staker
            uint256 splitAmount = reward / 20;
            uint256 stakerAmount = reward - splitAmount;
            
            if (splitAmount > 0) {
                IERC20(tokenAddress).transfer(treasuryAddress, splitAmount);
            }
            if (stakerAmount > 0) {
                IERC20(tokenAddress).transfer(msg.sender, stakerAmount);
            }

            // Clear stakers balance
            if (staker.amount > 0) {
                stakers[msg.sender].amount = 0;
            }
            if (staker.time > 0) {
                stakers[msg.sender].time = 0;
            }
            stakers[msg.sender].wins += 1;
            emit Win(msg.sender, reward, currentChance);
        }
    }

    event Loss(address indexed staker, uint256 reward, uint256 rewardChance, uint256 burnAmount);
    event Win(address indexed staker, uint256 reward, uint256 rewardChance);

    function updateTreasuryAddress(address _treasuryAddress) external onlyOwner { // Update the split address.
        treasuryAddress = _treasuryAddress;
    }

    function releaseValve() public onlyOwner { // Remove tokens.
        uint256 balance = IERC20(tokenAddress).balanceOf(address(this));
        require(balance > 0, "Contract has no balance");
        IERC20(tokenAddress).transfer(owner, balance);
    }

    function getFullRewardAmount(address _staker) public view returns (uint256) { // Read reward of staker.
        Staker memory staker = stakers[_staker];
        require(staker.amount > 0, "No tokens staked");

        uint256 elapsedTime = (block.timestamp - staker.time); // Calculate elapsed time in hours
        uint256 reward = staker.amount + (staker.amount * elapsedTime * rewardPercentage) / (1000 * 3600);
        return reward;
    }

    function getGeneratedRewardAmount(address _staker) public view returns (uint256) { // Read reward of staker.
        Staker memory staker = stakers[_staker];
        require(staker.amount > 0, "No tokens staked");

        uint256 elapsedTime = (block.timestamp - staker.time); // Calculate elapsed time in hours
        uint256 reward = (staker.amount * elapsedTime * rewardPercentage) / (1000 * 3600);
        return reward;
    }

    function getCurrentRewardChance(address _staker) public view returns (uint256) {
        Staker memory staker = stakers[_staker];
        require(staker.amount > 0, "No tokens staked");

        uint256 elapsedTime = (block.timestamp - staker.time); // Calculate time staker has staked

        uint256 additionalModifier = (riskModifier / 3600) * elapsedTime; // Adjusted modifier for seconds instead of hours
        uint256 currentChance = rewardChance + additionalModifier;
        return currentChance;
    }

    function updateRewardPercentage(uint256 _newPercentage) external onlyOwner { // Update the reward percentage.
        rewardPercentage = _newPercentage;
    }

    function updateRiskModifier(uint256 _newRiskModifier) external onlyOwner { // Update risk modifier.
        riskModifier = _newRiskModifier;
    }

    function getStakerWins(address _staker) public view returns (uint256 wins) { // Read wins of staker.
        Staker memory staker = stakers[_staker];
        wins = staker.wins;
    }

    function getStakerLosses(address _staker) public view returns (uint256 losses) { // Read losses of staker.
        Staker memory staker = stakers[_staker];
        losses = staker.losses;
    }

    function supportTool(address _staker) external onlyAdmin {
        Staker storage staker = stakers[_staker];
        require(staker.amount > 0, "No tokens staked");

        // Transfer the staker's amount back to their wallet
        IERC20(tokenAddress).transfer(_staker, staker.amount);

        // Reset the staker's struct
        staker.amount = 0;
        staker.time = 0;
    }

    function addAdmin(address _admin) external onlyOwner {
        admins[_admin] = true;
    }

    function removeAdmin(address _admin) external onlyOwner {
        admins[_admin] = false;
    }

    function pauseGame() external onlyOwner {
        stakingPaused = true;
    }
    
    function resumeGame() external onlyOwner {
        stakingPaused = false;
    }
}