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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * @title AereYieldFarm
 * @notice Stake LP tokens (e.g. AereSwap pair tokens) and earn rewards in native AERE.
 * @dev Standard MasterChef-style accounting (accRewardPerShare * 1e12).
 *      Reward source: native AERE deposited via fundRewards() by treasury/governance.
 *      Pools are added by the owner; the same staking token can have multiple pools
 *      with different reward rates (e.g. boosted promotions).
 */
contract AereYieldFarm is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    struct PoolInfo {
        IERC20 stakeToken;
        uint256 rewardPerSecond;   // in wei AERE
        uint256 lastRewardTime;
        uint256 accRewardPerShare; // scaled 1e12
        uint256 totalStaked;
    }

    struct UserInfo {
        uint256 amount;
        uint256 rewardDebt;
        uint256 pending;
    }

    PoolInfo[] public pools;
    mapping(uint256 => mapping(address => UserInfo)) public users;

    event PoolAdded(uint256 indexed pid, address stakeToken, uint256 rewardPerSecond);
    event PoolRateUpdated(uint256 indexed pid, uint256 rewardPerSecond);
    event Deposited(uint256 indexed pid, address indexed user, uint256 amount);
    event Withdrawn(uint256 indexed pid, address indexed user, uint256 amount);
    event Harvested(uint256 indexed pid, address indexed user, uint256 amount);
    event RewardsFunded(uint256 amount);

    receive() external payable { emit RewardsFunded(msg.value); }

    function poolCount() external view returns (uint256) { return pools.length; }

    function addPool(address stakeToken, uint256 rewardPerSecond) external onlyOwner returns (uint256 pid) {
        require(stakeToken != address(0), "token");
        pools.push(PoolInfo({
            stakeToken: IERC20(stakeToken),
            rewardPerSecond: rewardPerSecond,
            lastRewardTime: block.timestamp,
            accRewardPerShare: 0,
            totalStaked: 0
        }));
        pid = pools.length - 1;
        emit PoolAdded(pid, stakeToken, rewardPerSecond);
    }

    function setRewardRate(uint256 pid, uint256 rewardPerSecond) external onlyOwner {
        _accrue(pid);
        pools[pid].rewardPerSecond = rewardPerSecond;
        emit PoolRateUpdated(pid, rewardPerSecond);
    }

    function fundRewards() external payable { emit RewardsFunded(msg.value); }

    function pendingReward(uint256 pid, address user) external view returns (uint256) {
        PoolInfo memory p = pools[pid];
        UserInfo memory u = users[pid][user];
        uint256 acc = p.accRewardPerShare;
        if (block.timestamp > p.lastRewardTime && p.totalStaked > 0) {
            uint256 reward = (block.timestamp - p.lastRewardTime) * p.rewardPerSecond;
            acc += (reward * 1e12) / p.totalStaked;
        }
        return u.pending + (u.amount * acc) / 1e12 - u.rewardDebt;
    }

    function deposit(uint256 pid, uint256 amount) external nonReentrant {
        _accrue(pid);
        PoolInfo storage p = pools[pid];
        UserInfo storage u = users[pid][msg.sender];
        if (u.amount > 0) {
            u.pending += (u.amount * p.accRewardPerShare) / 1e12 - u.rewardDebt;
        }
        if (amount > 0) {
            p.stakeToken.safeTransferFrom(msg.sender, address(this), amount);
            u.amount += amount;
            p.totalStaked += amount;
        }
        u.rewardDebt = (u.amount * p.accRewardPerShare) / 1e12;
        emit Deposited(pid, msg.sender, amount);
    }

    function withdraw(uint256 pid, uint256 amount) external nonReentrant {
        _accrue(pid);
        PoolInfo storage p = pools[pid];
        UserInfo storage u = users[pid][msg.sender];
        require(u.amount >= amount, "balance");
        u.pending += (u.amount * p.accRewardPerShare) / 1e12 - u.rewardDebt;
        u.amount -= amount;
        p.totalStaked -= amount;
        u.rewardDebt = (u.amount * p.accRewardPerShare) / 1e12;
        if (amount > 0) p.stakeToken.safeTransfer(msg.sender, amount);
        emit Withdrawn(pid, msg.sender, amount);
    }

    function harvest(uint256 pid) external nonReentrant {
        _accrue(pid);
        PoolInfo storage p = pools[pid];
        UserInfo storage u = users[pid][msg.sender];
        uint256 reward = u.pending + (u.amount * p.accRewardPerShare) / 1e12 - u.rewardDebt;
        u.pending = 0;
        u.rewardDebt = (u.amount * p.accRewardPerShare) / 1e12;
        require(reward > 0, "nothing");
        require(address(this).balance >= reward, "underfunded");
        (bool ok, ) = msg.sender.call{value: reward}("");
        require(ok, "transfer");
        emit Harvested(pid, msg.sender, reward);
    }

    function _accrue(uint256 pid) internal {
        PoolInfo storage p = pools[pid];
        if (block.timestamp <= p.lastRewardTime) return;
        if (p.totalStaked == 0) { p.lastRewardTime = block.timestamp; return; }
        uint256 reward = (block.timestamp - p.lastRewardTime) * p.rewardPerSecond;
        p.accRewardPerShare += (reward * 1e12) / p.totalStaked;
        p.lastRewardTime = block.timestamp;
    }
}
