// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "forge-std/Test.sol";
import { TaskEscrowV3 } from "../../contracts/escrow/TaskEscrowV3.sol";
import { StreetCredV3 } from "../../contracts/reputation/StreetCredV3.sol";

/// @notice Invariant #12 — no user-controlled unbounded loops in a
///         state-changing function. TaskEscrowV3.placeBid enforces a hard
///         MAX_BIDS cap; this test verifies the cap actually reverts.
contract Inv12_NoUnboundedLoops is Test {
    TaskEscrowV3 internal escrow;
    StreetCredV3 internal streetCred;
    address internal owner = address(0xBEEF);
    address internal treasury = address(0xCAFE);
    address internal creator = address(0xC0FFEE);

    function setUp() public {
        escrow = new TaskEscrowV3(owner, treasury, 0, 100 ether, 1_000 ether, 1 days);
        streetCred = new StreetCredV3(owner);
        vm.prank(owner);
        streetCred.setAttester(address(this), true);
    }

    function test_max_bids_cap_enforced() external {
        // Create a native-asset task.
        vm.deal(creator, 1 ether);
        vm.prank(creator);
        uint256 taskId = escrow.createTaskNative{ value: 1 ether }(
            uint64(block.timestamp + 7 days), uint64(block.timestamp + 8 days), uint64(block.timestamp + 9 days)
        );

        // Fill up to MAX_BIDS bidders.
        for (uint256 i = 0; i < escrow.MAX_BIDS(); ++i) {
            address bidder = address(uint160(0xB1D000 + i));
            vm.prank(bidder);
            escrow.placeBid(taskId);
        }
        assertEq(escrow.biddersCount(taskId), escrow.MAX_BIDS());

        // Next bidder must revert.
        vm.prank(address(uint160(0xB1DEEE)));
        vm.expectRevert(TaskEscrowV3.TooManyBids.selector);
        escrow.placeBid(taskId);
    }

    function test_street_cred_recompute_lookback_is_bounded() external {
        uint256 maxLookback = streetCred.MAX_RECOMPUTE_LOOKBACK();
        streetCred.recompute(bytes32("subject"), maxLookback);

        vm.expectRevert(StreetCredV3.InvalidLookback.selector);
        streetCred.recompute(bytes32("subject"), maxLookback + 1);
    }
}
