// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MysteryBox {
    struct StakeData {
        address staker;
        address tokenAddress;
        uint256 tokenId;
    }

    event Stake(address staker, address tokenAddress, uint256 tokenId);
    event Claim(address staker, address tokenAddress, uint256 tokenId);

    uint256 public immutable deadlineBlock; //NFTの受付期限
    uint256 public constant unlockMargin = 100; //結果公開までのブロック数
    bytes32 public randomSeed;
    uint256 public constant stakeValue = 0.001 ether;

    uint256 public totalStaked;
    mapping(uint256 => StakeData) public stakes; //1 start index
    mapping(address => uint256) public stakeOf;
    mapping(address => bool) public claimed;

    constructor(uint256 deadlineBlock_) {
        require(
            deadlineBlock_ > block.number,
            "deadlineBlock should be in the future"
        );
        deadlineBlock = deadlineBlock_;
    }

    function stake(address tokenAddress, uint256 tokenId) public payable {
        require(deadlineBlock > block.number, "deadlineBlock has passed");
        require(msg.value >= stakeValue, "msg.value should be 5 ether");
        require(
            stakeOf[msg.sender] == 0 && stakes[0].staker != msg.sender,
            "You have already staked"
        );
        require(
            IERC721(tokenAddress).ownerOf(tokenId) == msg.sender,
            "You are not the owner of the token"
        );

        stakes[totalStaked] = StakeData(msg.sender, tokenAddress, tokenId);
        stakeOf[msg.sender] = totalStaked;
        totalStaked++;
        IERC721(tokenAddress).transferFrom(msg.sender, address(this), tokenId);

        unchecked {
            randomSeed ^= keccak256(
                abi.encodePacked(
                    blockhash(block.number - 1),
                    block.number,
                    msg.sender,
                    tokenAddress,
                    tokenId
                )
            );
        }

        emit Stake(msg.sender, tokenAddress, tokenId);
    }

    function stakeId(address staker) public view returns (uint256) {
        return stakeOf[staker];
    }

    function stakesByIndex(
        uint256 index,
        uint256 count
    ) public view returns (StakeData[] memory) {
        require(totalStaked >= index + count, "index+count is out of range");
        StakeData[] memory result = new StakeData[](count);
        for (uint256 i = 0; i < count; i++) {
            result[i] = stakes[index + i];
        }
        return result;
    }

    function claim() public {
        require(
            block.number > deadlineBlock + unlockMargin,
            "unlockMargin has not passed"
        );
        require(!claimed[msg.sender], "You have already claimed");
        require(
            stakes[stakeOf[msg.sender]].staker == msg.sender,
            "You have not staked"
        );

        bytes32 seed = keccak256(
            abi.encodePacked(
                blockhash(deadlineBlock + unlockMargin),
                deadlineBlock + unlockMargin,
                randomSeed
            )
        );
        uint256 awardStakeId = (uint256(seed) + stakeOf[msg.sender]) %
            totalStaked;
        StakeData memory awardStake = stakes[awardStakeId];

        claimed[msg.sender] = true;
        IERC721(awardStake.tokenAddress).transferFrom(
            address(this),
            awardStake.staker,
            awardStake.tokenId
        );
        payable(msg.sender).transfer(stakeValue);

        emit Claim(msg.sender, awardStake.tokenAddress, awardStake.tokenId);
    }
}
[{"inputs":[{"internalType":"uint256","name":"deadlineBlock_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"address","name":"tokenAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"address","name":"tokenAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Stake","type":"event"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"claimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deadlineBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"randomSeed","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"stakeId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stakeOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakeValue","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"stakes","outputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"count","type":"uint256"}],"name":"stakesByIndex","outputs":[{"components":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"internalType":"struct MysteryBox.StakeData[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlockMargin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]