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

import { Test } from "forge-std/Test.sol";
import { MultisigGovernanceV3 } from "../../contracts/governance/MultisigGovernanceV3.sol";
import {
    BasePilotAccessControllerV3,
    BasePilotAccessControllerCoreV3
} from "../../contracts/pilot/BasePilotAccessControllerV3.sol";
import { DisputeRegistryV3 } from "../../contracts/disputes/DisputeRegistryV3.sol";
import { TaskEscrowV3 } from "../../contracts/escrow/TaskEscrowV3.sol";
import { MutualAidPoolV3 } from "../../contracts/mutualaid/MutualAidPoolV3.sol";
import { ERC8004ValidationRegistryV3Safe } from "../../contracts/reputation/ERC8004ValidationRegistryV3Safe.sol";

contract PilotModuleMock {
    bytes32 public constant supportedPilotModuleId = keccak256("Vaultfire.BaseV3.SafeBondFactory");
    address public immutable pilotController;

    constructor(address controller) {
        pilotController = controller;
    }

    function register(
        BasePilotAccessControllerV3 controller,
        address designPartner,
        address position,
        uint256 initialAllocation
    ) external {
        controller.registerPosition(designPartner, position, initialAllocation);
    }

    function increase(BasePilotAccessControllerV3 controller, address position, uint256 amount) external {
        controller.increasePositionAllocation(position, amount);
    }

    function release(BasePilotAccessControllerV3 controller, address position, uint256 amount) external {
        controller.releasePositionAllocation(position, amount);
    }
}

contract BasePilotAccessControllerV3Test is Test {
    address internal constant SIGNER_A = address(0xA11CE);
    address internal constant SIGNER_B = address(0xB0B);
    address internal constant GUARDIAN = address(0x600D);
    address internal constant DESIGN_PARTNER = address(0xD3516);
    address internal constant ROTATED_DESIGN_PARTNER = address(0xD3517);
    address internal constant POSITION_A = address(0xA001);
    address internal constant POSITION_B = address(0xB001);
    address internal constant POSITION_C = address(0xC001);

    uint256 internal constant TIMELOCK = 1 days;
    uint256 internal constant MAX_GLOBAL_CAP = 1_000;
    uint256 internal constant MAX_PER_POSITION_CAP = 400;

    MultisigGovernanceV3 internal governance;
    BasePilotAccessControllerV3 internal controller;
    PilotModuleMock internal module;

    function setUp() external {
        vm.chainId(8453);
        address[] memory signers = new address[](2);
        signers[0] = SIGNER_A;
        signers[1] = SIGNER_B;
        address[] memory noTargets = new address[](0);
        uint64 nonce = vm.getNonce(address(this));
        address predictedGovernance = vm.computeCreateAddress(address(this), nonce + 1);
        BasePilotAccessControllerV3 referenceController =
            new BasePilotAccessControllerV3(predictedGovernance, MAX_GLOBAL_CAP, MAX_PER_POSITION_CAP);
        address[] memory canonicalTargets = new address[](1);
        canonicalTargets[0] = vm.computeCreateAddress(address(this), nonce + 2);
        bytes32[] memory canonicalHashes = new bytes32[](1);
        canonicalHashes[0] = address(referenceController).codehash;
        governance = new MultisigGovernanceV3(
            signers, 2, TIMELOCK, TIMELOCK, GUARDIAN, TIMELOCK, noTargets, canonicalTargets, canonicalHashes
        );
        controller = new BasePilotAccessControllerV3(address(governance), MAX_GLOBAL_CAP, MAX_PER_POSITION_CAP);
        assertEq(address(controller), canonicalTargets[0]);
        module = new PilotModuleMock(address(controller));
        vm.startPrank(address(governance));
        governance.pinCanonicalAddress(address(module), address(module).codehash);
        governance.setCallPolicy(
            address(controller), controller.setDesignPartner.selector, true, 0, address(controller).codehash
        );
        governance.setCallPolicy(
            address(controller), controller.setModule.selector, true, 0, address(controller).codehash
        );
        governance.setCallPolicy(
            address(controller), controller.setCaps.selector, true, 0, address(controller).codehash
        );
        governance.setCallPolicy(
            address(controller), controller.setDeployEnabled.selector, true, 0, address(controller).codehash
        );
        governance.setCallPolicy(
            address(controller), controller.setWriteEnabled.selector, true, 0, address(controller).codehash
        );
        vm.stopPrank();
    }

    function testIsBaseOnlyAndDefaultsToClosed() external {
        assertFalse(controller.deployEnabled());
        assertFalse(controller.writeEnabled());
        assertEq(controller.totalAllocated(), 0);

        vm.chainId(1);
        vm.expectRevert(BasePilotAccessControllerCoreV3.UnsupportedChain.selector);
        new BasePilotAccessControllerV3(address(governance), MAX_GLOBAL_CAP, MAX_PER_POSITION_CAP);
    }

    function testOnlyGovernanceCanConfigurePilot() external {
        vm.expectRevert(BasePilotAccessControllerCoreV3.NotGovernance.selector);
        controller.setDesignPartner(DESIGN_PARTNER, true);

        _govern(abi.encodeCall(controller.setDesignPartner, (DESIGN_PARTNER, true)));
        _govern(abi.encodeCall(controller.setModule, (address(module), true)));
        _govern(abi.encodeCall(controller.setCaps, (MAX_GLOBAL_CAP, MAX_PER_POSITION_CAP)));

        assertTrue(controller.isDesignPartner(DESIGN_PARTNER));
        assertTrue(controller.isModule(address(module)));
        assertEq(controller.globalCap(), MAX_GLOBAL_CAP);
        assertEq(controller.perPositionCap(), MAX_PER_POSITION_CAP);
        assertEq(controller.maxGlobalCap(), MAX_GLOBAL_CAP);
        assertEq(controller.maxPerPositionCap(), MAX_PER_POSITION_CAP);
        assertEq(controller.designPartnerCount(), 1);
    }

    function testPilotAllowsExactlyOneDesignPartnerAndSupportsExplicitRotation() external {
        _govern(abi.encodeCall(controller.setDesignPartner, (DESIGN_PARTNER, true)));
        _governExpectCallFailure(abi.encodeCall(controller.setDesignPartner, (ROTATED_DESIGN_PARTNER, true)));

        assertTrue(controller.isDesignPartner(DESIGN_PARTNER));
        assertFalse(controller.isDesignPartner(ROTATED_DESIGN_PARTNER));
        assertEq(controller.designPartnerCount(), 1);

        _govern(abi.encodeCall(controller.setDesignPartner, (DESIGN_PARTNER, false)));
        _govern(abi.encodeCall(controller.setDesignPartner, (ROTATED_DESIGN_PARTNER, true)));

        assertFalse(controller.isDesignPartner(DESIGN_PARTNER));
        assertTrue(controller.isDesignPartner(ROTATED_DESIGN_PARTNER));
        assertEq(controller.designPartnerCount(), 1);
    }

    function testClosedPilotRejectsDeploysAndWritesUntilGovernanceEnablesEachPath() external {
        _configure(1_000, 400);

        vm.expectRevert(BasePilotAccessControllerCoreV3.DeployDisabled.selector);
        module.register(controller, DESIGN_PARTNER, POSITION_A, 100);

        _govern(abi.encodeCall(controller.setDeployEnabled, (true)));
        module.register(controller, DESIGN_PARTNER, POSITION_A, 100);

        vm.expectRevert(BasePilotAccessControllerCoreV3.WriteDisabled.selector);
        module.increase(controller, POSITION_A, 1);

        _govern(abi.encodeCall(controller.setWriteEnabled, (true)));
        module.increase(controller, POSITION_A, 50);
        assertEq(controller.positionAllocated(POSITION_A), 150);
        assertEq(controller.totalAllocated(), 150);
    }

    function testEnforcesAllowlistGlobalAndPerPositionCaps() external {
        _configure(1_000, 400);
        _govern(abi.encodeCall(controller.setDeployEnabled, (true)));
        _govern(abi.encodeCall(controller.setWriteEnabled, (true)));

        vm.expectRevert(BasePilotAccessControllerCoreV3.DesignPartnerNotAllowed.selector);
        module.register(controller, address(0xBAD), POSITION_A, 1);

        vm.expectRevert(BasePilotAccessControllerCoreV3.PositionCapExceeded.selector);
        module.register(controller, DESIGN_PARTNER, POSITION_A, 401);

        module.register(controller, DESIGN_PARTNER, POSITION_A, 400);
        assertEq(controller.positionCap(POSITION_A), 400);
        vm.expectRevert(BasePilotAccessControllerCoreV3.PositionCapExceeded.selector);
        module.increase(controller, POSITION_A, 1);

        module.register(controller, DESIGN_PARTNER, POSITION_B, 400);
        vm.expectRevert(BasePilotAccessControllerCoreV3.GlobalCapExceeded.selector);
        module.register(controller, DESIGN_PARTNER, POSITION_C, 201);
        assertEq(controller.totalAllocated(), 800);
    }

    function testReleaseRemainsAvailableWhenWritesAndDeploysAreDisabled() external {
        _configure(1_000, 400);
        _govern(abi.encodeCall(controller.setDeployEnabled, (true)));
        module.register(controller, DESIGN_PARTNER, POSITION_A, 300);

        _govern(abi.encodeCall(controller.setDeployEnabled, (false)));
        _govern(abi.encodeCall(controller.setWriteEnabled, (false)));
        module.release(controller, POSITION_A, 125);

        assertEq(controller.positionAllocated(POSITION_A), 175);
        assertEq(controller.totalAllocated(), 175);
    }

    function testOnlyApprovedModulesCanUseHooksAndCapsCannotDropBelowAllocatedValue() external {
        _configure(1_000, 400);
        _govern(abi.encodeCall(controller.setDeployEnabled, (true)));

        vm.expectRevert(BasePilotAccessControllerCoreV3.NotModule.selector);
        controller.registerPosition(DESIGN_PARTNER, POSITION_A, 1);

        module.register(controller, DESIGN_PARTNER, POSITION_A, 300);
        _governExpectCallFailure(abi.encodeCall(controller.setCaps, (299, 400)));
    }

    function testConstructorRejectsZeroOrInconsistentImmutableMaximumCaps() external {
        vm.expectRevert(BasePilotAccessControllerCoreV3.InvalidMaximumCap.selector);
        new BasePilotAccessControllerV3(address(governance), 0, 1);

        vm.expectRevert(BasePilotAccessControllerCoreV3.InvalidMaximumCap.selector);
        new BasePilotAccessControllerV3(address(governance), 1, 0);

        vm.expectRevert(BasePilotAccessControllerCoreV3.InvalidMaximumCap.selector);
        new BasePilotAccessControllerV3(address(governance), 1, 2);
    }

    function test_retiredModulesCannotBeControllerAuthorized() external {
        address[4] memory retired = [
            address(new DisputeRegistryV3(address(governance))),
            address(new TaskEscrowV3(address(governance), address(1), 0, 1, 1, 1)),
            address(new MutualAidPoolV3(address(governance), address(0), 1, 1)),
            address(new ERC8004ValidationRegistryV3Safe(address(governance), address(1), 1))
        ];
        for (uint256 i; i < retired.length; ++i) {
            vm.expectRevert(BasePilotAccessControllerCoreV3.UnsupportedModule.selector);
            vm.prank(address(governance));
            controller.setModule(retired[i], true);
        }
    }

    function testGovernanceCanLowerCapsButCannotExceedImmutableMaximums() external {
        _configure(900, 300);
        assertEq(controller.globalCap(), 900);
        assertEq(controller.perPositionCap(), 300);

        _govern(abi.encodeCall(controller.setCaps, (MAX_GLOBAL_CAP, MAX_PER_POSITION_CAP)));
        assertEq(controller.globalCap(), MAX_GLOBAL_CAP);
        assertEq(controller.perPositionCap(), MAX_PER_POSITION_CAP);

        _governExpectCallFailure(abi.encodeCall(controller.setCaps, (MAX_GLOBAL_CAP + 1, MAX_PER_POSITION_CAP)));
        _governExpectCallFailure(abi.encodeCall(controller.setCaps, (MAX_GLOBAL_CAP, MAX_PER_POSITION_CAP + 1)));
        _governExpectCallFailure(abi.encodeCall(controller.setCaps, (399, 400)));
    }

    function _configure(uint256 globalCap, uint256 perPositionCap) internal {
        _govern(abi.encodeCall(controller.setDesignPartner, (DESIGN_PARTNER, true)));
        _govern(abi.encodeCall(controller.setModule, (address(module), true)));
        _govern(abi.encodeCall(controller.setCaps, (globalCap, perPositionCap)));
    }

    function _govern(bytes memory data) internal {
        vm.prank(SIGNER_A);
        uint256 id = governance.propose(address(controller), 0, data);
        vm.prank(SIGNER_A);
        governance.confirm(id);
        vm.prank(SIGNER_B);
        governance.confirm(id);
        vm.warp(block.timestamp + TIMELOCK);
        vm.prank(SIGNER_A);
        governance.execute(id);
    }

    function _governExpectCallFailure(bytes memory data) internal {
        vm.expectPartialRevert(MultisigGovernanceV3.PolicyDenied.selector);
        vm.prank(SIGNER_A);
        governance.propose(address(controller), 0, data);
    }
}
