// 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 { RewardProgramV3 } from "../../../contracts/bonds/RewardProgramV3.sol";
import { PartnershipBondV3Safe } from "../../../contracts/bonds/PartnershipBondV3Safe.sol";
import { AccountabilityBondV3Safe } from "../../../contracts/bonds/AccountabilityBondV3Safe.sol";
import { SafeBondFactoryV3 } from "../../../contracts/bonds/SafeBondFactoryV3.sol";
import { PartnershipBondFactoryV3 } from "../../../contracts/bonds/PartnershipBondFactoryV3.sol";
import { AccountabilityBondFactoryV3 } from "../../../contracts/bonds/AccountabilityBondFactoryV3.sol";
import { TaskEscrowV3 } from "../../../contracts/escrow/TaskEscrowV3.sol";
import { DisputeRegistryV3 } from "../../../contracts/disputes/DisputeRegistryV3.sol";
import { ThresholdConsensusV3 } from "../../../contracts/consensus/ThresholdConsensusV3.sol";
import { EvidenceRegistryV3 } from "../../../contracts/evidence/EvidenceRegistryV3.sol";
import { StreetCredV3 } from "../../../contracts/reputation/StreetCredV3.sol";
import { ERC8004ValidationRegistryV3Safe } from "../../../contracts/reputation/ERC8004ValidationRegistryV3Safe.sol";
import { ERC8004IdentityAdapterV3 } from "../../../contracts/identity/ERC8004IdentityAdapterV3.sol";
import { LegacyKYAAdapterV3 } from "../../../contracts/identity/LegacyKYAAdapterV3.sol";
import { MandateRegistryV3 } from "../../../contracts/mandate/MandateRegistryV3.sol";
import { MutualAidPoolV3 } from "../../../contracts/mutualaid/MutualAidPoolV3.sol";
import { VaultfireBIPVaultV3 } from "../../../contracts/vault/VaultfireBIPVaultV3.sol";
import { MockERC20 } from "../../mocks/MockERC20.sol";

/// @dev Minimal pilot module used only to exercise controller hooks on the fork.
contract BasePilotForkModuleMock {
    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);
    }
}

/// @notice Fork-only, non-broadcasting Base pilot deployment and rollback simulation.
/// @dev If BASE_RPC_URL is absent, this test logs a skip and returns before making
///      any RPC request. It never calls startBroadcast or reads a private key.
contract BasePilotDeploymentRollbackV3ForkTest is Test {
    uint256 internal constant BASE_CHAIN_ID = 8453;
    uint256 internal constant SIGNER_A_KEY = 0xA11CE;
    // The former 0xB0B fixture key resolves to an address with deployed code on
    // Base, which correctly routes signature validation through ERC-1271.
    // Use a deterministic EOA fixture address for the ECDSA-path rehearsal.
    uint256 internal constant SIGNER_B_KEY = 0xC0FFEE1234;
    address internal constant SIGNER_C = address(0xC0C);
    address internal constant SIGNER_D = address(0xD0D);
    address internal constant SIGNER_E = address(0xE0E);
    address internal constant GUARDIAN = address(0x600D);
    address internal constant POSITION = address(0xA001);
    uint256 internal constant TIMELOCK = 24 hours;

    // Checked-in V2 Base inventory witnesses. The simulation only reads them.
    address internal constant V2_MISSION_ENFORCEMENT = 0xBc4208A7A59af3B7E438d3308C3dc0d299d739a7;
    address internal constant V2_ERC8004_IDENTITY = 0xa7BD20bf5De63df949cA5Be2F20835978eCba81A;

    MultisigGovernanceV3 internal governance;
    BasePilotAccessControllerV3 internal controller;
    BasePilotForkModuleMock internal module;
    RewardProgramV3 internal rewards;
    ThresholdConsensusV3 internal consensus;
    PartnershipBondV3Safe internal partnershipBond;
    AccountabilityBondV3Safe internal accountabilityBond;
    SafeBondFactoryV3 internal bondFactory;
    PartnershipBondFactoryV3 internal partnershipFactory;
    AccountabilityBondFactoryV3 internal accountabilityFactory;
    TaskEscrowV3 internal taskEscrow;
    DisputeRegistryV3 internal disputes;
    EvidenceRegistryV3 internal evidence;
    StreetCredV3 internal streetCred;
    ERC8004ValidationRegistryV3Safe internal validation;
    ERC8004IdentityAdapterV3 internal identityAdapter;
    LegacyKYAAdapterV3 internal legacyAdapter;
    MandateRegistryV3 internal mandates;
    MutualAidPoolV3 internal mutualAid;
    VaultfireBIPVaultV3 internal vault;
    address internal signerA;
    address internal signerB;

    function testFork_BasePilotDeploymentAndRollbackSimulation() external {
        if (!_selectBaseForkOrSkip()) return;

        // The selected V2 contracts are read before and after all simulated V3 writes.
        bytes32 missionCodeHashBefore = V2_MISSION_ENFORCEMENT.codehash;
        uint256 missionBalanceBefore = V2_MISSION_ENFORCEMENT.balance;
        bytes32 identityCodeHashBefore = V2_ERC8004_IDENTITY.codehash;
        uint256 identityBalanceBefore = V2_ERC8004_IDENTITY.balance;
        assertEq(V2_MISSION_ENFORCEMENT.code.length, 4917, "unexpected V2 MissionEnforcement bytecode");
        assertEq(V2_ERC8004_IDENTITY.code.length, 9387, "unexpected V2 ERC8004 identity bytecode");

        _deployInConstructorOrder();
        _assertOwnerAndGovernanceBindings();
        _assertDefaultClosedAndAllowlists();
        _exerciseRollbackGuardianPauseAndSafeExits();

        assertEq(V2_MISSION_ENFORCEMENT.codehash, missionCodeHashBefore, "V2 code changed");
        assertEq(V2_MISSION_ENFORCEMENT.balance, missionBalanceBefore, "V2 balance changed");
        assertEq(V2_ERC8004_IDENTITY.codehash, identityCodeHashBefore, "V2 identity code changed");
        assertEq(V2_ERC8004_IDENTITY.balance, identityBalanceBefore, "V2 identity balance changed");
    }

    function _selectBaseForkOrSkip() internal returns (bool) {
        string memory rpcUrl = vm.envOr("BASE_RPC_URL", string(""));
        if (bytes(rpcUrl).length == 0) {
            emit log("SKIP Base fork simulation: BASE_RPC_URL is not configured");
            return false;
        }

        vm.createSelectFork(rpcUrl);
        assertEq(block.chainid, BASE_CHAIN_ID, "BASE_RPC_URL did not select Base");
        return true;
    }

    /// @dev This full-pilot rehearsal predicts governance so pause targets can
    ///      bind their final owner at construction. The read-only profile has
    ///      no constructor-cycle requirement because it starts with no targets.
    function _deployInConstructorOrder() internal {
        signerA = vm.addr(SIGNER_A_KEY);
        signerB = vm.addr(SIGNER_B_KEY);
        assertEq(signerA.code.length, 0, "signer A must be an EOA fixture");
        assertEq(signerB.code.length, 0, "signer B must be an EOA fixture");
        uint64 firstNonce = vm.getNonce(address(this));
        address predictedGovernance = vm.computeCreateAddress(address(this), uint256(firstNonce) + 6);

        // 1-6: controller-bound dependencies and pauseable targets bind directly to final governance.
        rewards = new RewardProgramV3(predictedGovernance);
        consensus = new ThresholdConsensusV3(predictedGovernance, 3);
        disputes = new DisputeRegistryV3(predictedGovernance);
        controller = new BasePilotAccessControllerV3(predictedGovernance, 1_000 ether, 400 ether);
        partnershipBond = new PartnershipBondV3Safe(
            predictedGovernance,
            address(0),
            address(consensus),
            address(disputes),
            10 ether,
            20 ether,
            100 ether,
            address(controller)
        );
        accountabilityBond = new AccountabilityBondV3Safe(
            predictedGovernance, address(consensus), address(disputes), 10 ether, 100 ether, address(controller)
        );

        // 7: immutable guardian allowlist can now contain the actual pause targets.
        address[] memory signers = new address[](5);
        signers[0] = signerA;
        signers[1] = signerB;
        signers[2] = SIGNER_C;
        signers[3] = SIGNER_D;
        signers[4] = SIGNER_E;
        address[] memory guardianPauseTargets = new address[](2);
        guardianPauseTargets[0] = address(partnershipBond);
        guardianPauseTargets[1] = address(accountabilityBond);
        governance = new MultisigGovernanceV3(
            signers, 3, TIMELOCK, TIMELOCK, GUARDIAN, TIMELOCK, guardianPauseTargets, new address[](0), new bytes32[](0)
        );
        assertEq(address(governance), predictedGovernance, "constructor ordering changed");

        // The specialized factories embed the coordinator address. Predict the
        // following coordinator deployment so its constructor can pin their
        // immutable-patched code hashes before the one-time binding proposal.
        address predictedCoordinator = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 2);
        partnershipFactory = new PartnershipBondFactoryV3(
            predictedCoordinator,
            address(governance),
            address(consensus),
            address(disputes),
            10 ether,
            20 ether,
            100 ether,
            address(controller)
        );
        accountabilityFactory = new AccountabilityBondFactoryV3(
            predictedCoordinator,
            address(governance),
            address(consensus),
            address(disputes),
            10 ether,
            100 ether,
            address(controller),
            abi.encodePacked(
                type(AccountabilityBondV3Safe).creationCode,
                abi.encode(
                    address(governance), address(consensus), address(disputes), 10 ether, 100 ether, address(controller)
                )
            ),
            address(accountabilityBond).codehash
        );
        bondFactory = new SafeBondFactoryV3(
            address(governance),
            address(consensus),
            address(disputes),
            address(controller),
            10 ether,
            20 ether,
            100 ether,
            10 ether,
            100 ether,
            address(partnershipFactory).codehash,
            address(accountabilityFactory).codehash,
            address(partnershipBond).codehash,
            address(accountabilityBond).codehash,
            accountabilityFactory.childInitCodeHash()
        );
        assertEq(address(bondFactory), predictedCoordinator, "factory constructor ordering changed");
        // All remaining constructors receive final governance/treasury bindings.
        module = new BasePilotForkModuleMock();
        _governTarget(
            address(bondFactory),
            abi.encodeCall(
                bondFactory.configureFactories, (address(partnershipFactory), address(accountabilityFactory))
            )
        );
        taskEscrow = new TaskEscrowV3(address(governance), address(governance), 100, 100 ether, 1_000 ether, 1 days);
        evidence = new EvidenceRegistryV3(address(governance));
        streetCred = new StreetCredV3(address(governance));
        validation = new ERC8004ValidationRegistryV3Safe(address(governance), address(governance), 1 ether);
        identityAdapter =
            new ERC8004IdentityAdapterV3(address(governance), V2_ERC8004_IDENTITY, V2_ERC8004_IDENTITY.codehash);
        legacyAdapter = new LegacyKYAAdapterV3(V2_MISSION_ENFORCEMENT);
        mandates =
            new MandateRegistryV3(address(governance), address(identityAdapter), address(identityAdapter).codehash);
        mutualAid = new MutualAidPoolV3(address(governance), address(0), 1 ether, 10 ether);
        MockERC20 asset = new MockERC20();
        vault = new VaultfireBIPVaultV3(address(asset), address(governance), 1_000 ether);
    }

    function _assertOwnerAndGovernanceBindings() internal view {
        assertEq(rewards.owner(), address(governance));
        assertEq(consensus.owner(), address(governance));
        assertEq(disputes.owner(), address(governance));
        assertEq(partnershipBond.owner(), address(governance));
        assertEq(partnershipBond.rewardProgram(), address(0));
        assertEq(address(partnershipBond.consensus()), address(consensus));
        assertEq(address(partnershipBond.disputeRegistry()), address(0));
        assertEq(accountabilityBond.owner(), address(governance));
        assertEq(address(accountabilityBond.consensus()), address(consensus));
        assertEq(address(accountabilityBond.disputeRegistry()), address(0));
        assertEq(bondFactory.owner(), address(governance));
        assertEq(bondFactory.consensus(), address(consensus));
        assertEq(bondFactory.disputeRegistry(), address(0));
        assertEq(address(bondFactory.partnershipFactory()), address(partnershipFactory));
        assertEq(address(bondFactory.accountabilityFactory()), address(accountabilityFactory));
        assertEq(partnershipFactory.coordinator(), address(bondFactory));
        assertEq(accountabilityFactory.coordinator(), address(bondFactory));
        assertEq(partnershipFactory.bondOwner(), address(governance));
        assertEq(accountabilityFactory.bondOwner(), address(governance));
        assertEq(controller.governance(), address(governance));
        assertEq(taskEscrow.owner(), address(governance));
        assertEq(taskEscrow.treasury(), address(governance));
        assertEq(taskEscrow.maxTaskAmount(), 100 ether);
        assertEq(taskEscrow.maxAggregateLiabilityPerAsset(), 1_000 ether);
        assertEq(taskEscrow.disputeDecisionTimeout(), 1 days);
        assertEq(evidence.owner(), address(governance));
        assertEq(streetCred.owner(), address(governance));
        assertEq(validation.owner(), address(governance));
        assertEq(validation.treasury(), address(governance));
        assertEq(identityAdapter.owner(), address(governance));
        assertEq(address(identityAdapter.registry()), V2_ERC8004_IDENTITY);
        assertEq(address(legacyAdapter.legacy()), V2_MISSION_ENFORCEMENT);
        assertEq(mandates.owner(), address(governance));
        assertEq(mutualAid.owner(), address(governance));
        assertEq(vault.owner(), address(governance));
        assertEq(vault.totalAssetCap(), 1_000 ether);
        assertEq(governance.threshold(), 3);
        assertEq(governance.timelockSeconds(), 24 hours);
        assertEq(governance.minimumTimelockSeconds(), 24 hours);
        assertFalse(governance.isSigner(GUARDIAN));
        assertTrue(governance.isSigner(signerA));
        assertTrue(governance.isSigner(signerB));
        assertTrue(governance.isSigner(SIGNER_C));
        assertTrue(governance.isSigner(SIGNER_D));
        assertTrue(governance.isSigner(SIGNER_E));
        assertTrue(governance.isGuardianPauseTarget(address(partnershipBond)));
        assertTrue(governance.isGuardianPauseTarget(address(accountabilityBond)));
    }

    function _assertDefaultClosedAndAllowlists() internal {
        assertFalse(controller.deployEnabled(), "deploys must default closed");
        assertFalse(controller.writeEnabled(), "writes must default closed");
        assertEq(controller.globalCap(), 0, "global cap must default zero");
        assertEq(controller.perPositionCap(), 0, "position cap must default zero");

        vm.expectRevert(BasePilotAccessControllerCoreV3.NotGovernance.selector);
        controller.setDeployEnabled(true);
        vm.expectRevert(BasePilotAccessControllerCoreV3.NotModule.selector);
        controller.registerPosition(signerA, POSITION, 1);

        _govern(abi.encodeCall(controller.setDesignPartner, (signerA, true)));
        _govern(abi.encodeCall(controller.setModule, (address(module), true)));
        _govern(abi.encodeCall(controller.setModule, (address(bondFactory), true)));
        _govern(abi.encodeCall(controller.setCaps, (1_000 ether, 400 ether)));
        _govern(abi.encodeCall(controller.setDeployEnabled, (true)));

        module.register(controller, signerA, POSITION, 100 ether);
        module.register(controller, signerA, address(partnershipBond), 0);
        module.register(controller, signerA, address(accountabilityBond), 0);
        assertTrue(controller.isDesignPartner(signerA));
        assertEq(controller.positionAllocated(POSITION), 100 ether);
        assertEq(controller.totalAllocated(), 100 ether);

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

        _govern(abi.encodeCall(controller.setWriteEnabled, (true)));
        module.increase(controller, POSITION, 25 ether);
        assertEq(controller.positionAllocated(POSITION), 125 ether);
    }

    function _exerciseRollbackGuardianPauseAndSafeExits() internal {
        uint256 fundedPartnershipAllocation = _createExpiredPartnershipCredit();

        // Rollback closes all new pilot paths through the timelocked governance.
        _govern(abi.encodeCall(controller.setWriteEnabled, (false)));
        _govern(abi.encodeCall(controller.setDeployEnabled, (false)));
        assertFalse(controller.writeEnabled());
        assertFalse(controller.deployEnabled());

        // The guardian can pause only constructor-allowlisted contracts.
        vm.prank(GUARDIAN);
        governance.guardianPause(address(partnershipBond));
        assertTrue(partnershipBond.paused());
        vm.prank(GUARDIAN);
        governance.guardianPause(address(accountabilityBond));
        assertTrue(accountabilityBond.paused());
        vm.prank(GUARDIAN);
        vm.expectRevert(MultisigGovernanceV3.GuardianTargetNotAllowed.selector);
        governance.guardianPause(address(controller));

        vm.expectRevert(BasePilotAccessControllerCoreV3.DeployDisabled.selector);
        module.register(controller, signerA, address(0xA002), 1 ether);
        vm.expectRevert(BasePilotAccessControllerCoreV3.WriteDisabled.selector);
        module.increase(controller, POSITION, 1 ether);

        // Release is deliberately not paused; controller accounting cannot trap exits.
        module.release(controller, POSITION, 25 ether);
        assertEq(controller.positionAllocated(POSITION), 100 ether);
        assertEq(
            controller.positionAllocated(address(partnershipBond)),
            fundedPartnershipAllocation,
            "partnership position must retain its explicit funded allocation"
        );
        uint256 expectedTotalAllocation = controller.positionAllocated(POSITION) + fundedPartnershipAllocation;
        assertEq(controller.totalAllocated(), expectedTotalAllocation, "registered positions must aggregate exactly");

        // Pull-payment withdrawal remains available after the guardian pause.
        uint256 beforeBalance = signerA.balance;
        vm.prank(signerA);
        partnershipBond.withdrawCredit(address(0));
        assertEq(signerA.balance, beforeBalance + 1 ether);
        assertEq(partnershipBond.creditOf(address(0), signerA), 0);
    }

    function _createExpiredPartnershipCredit() internal returns (uint256 fundedPartnershipAllocation) {
        PartnershipBondV3Safe.Terms memory terms = PartnershipBondV3Safe.Terms({
            bondId: 1,
            partyA: signerA,
            partyB: signerB,
            partyAStake: 1 ether,
            partyBStake: 1 ether,
            stakeAsset: address(0),
            partnershipType: bytes32("base-fork"),
            breachConditionsHash: bytes32(0),
            objectiveRuleClass: 1,
            objectiveRuleSchemaVersion: 1,
            flourishRewardConditionsHash: bytes32(0),
            rewardProgramId: 0,
            proposalDeadline: uint64(block.timestamp + 1 days),
            activePeriodStart: uint64(block.timestamp + 1 days),
            activePeriodEnd: uint64(block.timestamp + 2 days),
            challengeWindow: uint64(1 days),
            nonce: 1,
            chainId: block.chainid,
            protocolVersion: "3.0.0"
        });
        bytes memory signatureA = _signPartnership(terms, SIGNER_A_KEY);
        bytes memory signatureB = _signPartnership(terms, SIGNER_B_KEY);
        partnershipBond.createBond(terms, signatureA, signatureB);

        uint256 partyAFunding = terms.partyAStake;
        uint256 partyBFunding = terms.partyBStake;
        vm.deal(signerA, partyAFunding);
        vm.deal(signerB, partyBFunding);
        vm.prank(signerA);
        partnershipBond.fundNative{ value: partyAFunding }(1);
        vm.prank(signerB);
        partnershipBond.fundNative{ value: partyBFunding }(1);
        vm.warp(block.timestamp + 1 days);
        partnershipBond.activate(1);
        vm.warp(block.timestamp + 1 days + 1);
        partnershipBond.markExpired(1);
        partnershipBond.forceExit(1);
        assertEq(partnershipBond.creditOf(address(0), signerA), 1 ether);
        return partyAFunding + partyBFunding;
    }

    function _signPartnership(PartnershipBondV3Safe.Terms memory terms, uint256 key) internal returns (bytes memory) {
        bytes32 digest = keccak256(
            abi.encodePacked("\x19\x01", partnershipBond.DOMAIN_SEPARATOR(), partnershipBond.hashTerms(terms))
        );
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(key, digest);
        return abi.encodePacked(r, s, v);
    }

    function _govern(bytes memory data) internal {
        _governTarget(address(controller), data);
    }

    function _governTarget(address target, bytes memory data) internal {
        vm.prank(signerA);
        uint256 id = governance.propose(target, 0, data);
        vm.prank(signerA);
        governance.confirm(id);
        vm.prank(signerB);
        governance.confirm(id);
        vm.prank(SIGNER_C);
        governance.confirm(id);
        vm.warp(block.timestamp + TIMELOCK);
        vm.prank(signerA);
        governance.execute(id);
    }
}
