// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.24;

import "./core/DAO.sol";
import "./interfaces/IAcceptAvailability.sol";

contract Availability is IAcceptAvailability, DAOContract {
    struct AvailabilityStake {
        address worker;
        uint256 amount;
        uint endTime;
        bool assigned;
    }

    mapping(uint => AvailabilityStake) public stakes;
    mapping(address worker => uint stakeIndex) activeWorkerStakes;
    uint public stakeCount;

    event AvailabilityStaked(uint stakeIndex);

    constructor(DAO dao) DAOContract(dao) {}

    /// Accept availability stakes as reputation token transfer
    function acceptAvailability(
        address worker,
        uint256 amount,
        uint duration
    ) external returns (uint refund) {
        require(
            msg.sender == address(dao),
            "acceptAvailability must only be called by DAO contract"
        );
        require(amount > 0, "No stake provided");
        // If we already have a stake for this worker, replace it
        uint stakeIndex = activeWorkerStakes[worker];
        if (stakeIndex == 0 && stakes[stakeIndex].worker != worker) {
            // We don't have an existing stake for this worker
            stakeIndex = stakeCount++;
            activeWorkerStakes[worker] = stakeIndex;
        } else if (stakes[stakeIndex].assigned) {
            // Stake has already been assigned; We need to create a new one
            stakeIndex = stakeCount++;
            activeWorkerStakes[worker] = stakeIndex;
        } else {
            // We are replacing an existing stake.
            // That means we can refund some of the granted allowance
            refund = stakes[stakeIndex].amount;
        }
        AvailabilityStake storage stake = stakes[stakeIndex];
        stake.worker = worker;
        stake.amount = amount;
        stake.endTime = block.timestamp + duration;
        emit AvailabilityStaked(stakeIndex);
    }

    /// Select a worker randomly from among the available workers, weighted by amount staked
    function randomWeightedSelection() internal view returns (uint stakeIndex) {
        uint totalStakes;
        for (uint i = 0; i < stakeCount; i++) {
            if (stakes[i].assigned) continue;
            if (block.timestamp > stakes[i].endTime) continue;
            totalStakes += stakes[i].amount;
        }
        require(totalStakes > 0, "No available worker stakes");
        uint select = block.prevrandao % totalStakes;
        uint acc;
        for (uint i = 0; i < stakeCount; i++) {
            if (stakes[i].assigned) continue;
            if (block.timestamp > stakes[i].endTime) continue;
            acc += stakes[i].amount;
            if (acc > select) {
                stakeIndex = i;
                break;
            }
        }
    }

    /// Assign a random available worker
    function assignWork() internal returns (uint stakeIndex) {
        stakeIndex = randomWeightedSelection();
        AvailabilityStake storage stake = stakes[stakeIndex];
        stake.assigned = true;
    }
}