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

import { V3Fixtures } from "../helpers/V3Fixtures.sol";
import {
    BasePilotAccessControllerV3,
    BasePilotAccessControllerCoreV3
} from "../../contracts/pilot/BasePilotAccessControllerV3.sol";
import { SafeBondFactoryV3 } from "../../contracts/bonds/SafeBondFactoryV3.sol";
import { PartnershipBondFactoryV3 } from "../../contracts/bonds/PartnershipBondFactoryV3.sol";
import { AccountabilityBondFactoryV3 } from "../../contracts/bonds/AccountabilityBondFactoryV3.sol";
import { PartnershipBondV3Safe } from "../../contracts/bonds/PartnershipBondV3Safe.sol";
import { AccountabilityBondV3Safe } from "../../contracts/bonds/AccountabilityBondV3Safe.sol";

/// @notice Regression coverage for the Base pilot's child binding and
/// guardian-pause activation gates.
contract SafeBondFactoryPilotControlsV3Test is V3Fixtures {
    BasePilotAccessControllerV3 internal controller;
    SafeBondFactoryV3 internal factory;
    mapping(address => bool) internal guardianPauseTarget;

    /// @dev The controller reads this governance-fixture registry by its
    /// production selector, matching MultisigGovernanceV3.
    function isGuardianPauseTarget(address target) external view returns (bool) {
        return guardianPauseTarget[target];
    }

    function setUp() public {
        vm.chainId(8453);
        _setUpDependencies();
        controller = new BasePilotAccessControllerV3(address(this), 0.4 ether, 0.2 ether);
        PartnershipBondV3Safe partnershipIdentity = new PartnershipBondV3Safe(
            owner,
            address(0),
            address(consensus),
            address(disputeRegistry),
            0.01 ether,
            0.02 ether,
            0.2 ether,
            address(controller)
        );
        AccountabilityBondV3Safe accountabilityIdentity = new AccountabilityBondV3Safe(
            owner, address(consensus), address(disputeRegistry), 0.02 ether, 0.2 ether, address(controller)
        );
        address predictedCoordinator = vm.computeCreateAddress(address(this), vm.getNonce(address(this)) + 2);
        PartnershipBondFactoryV3 partnershipFactory = new PartnershipBondFactoryV3(
            predictedCoordinator,
            owner,
            address(consensus),
            address(disputeRegistry),
            0.01 ether,
            0.02 ether,
            0.2 ether,
            address(controller)
        );
        AccountabilityBondFactoryV3 accountabilityFactory = new AccountabilityBondFactoryV3(
            predictedCoordinator,
            owner,
            address(consensus),
            address(disputeRegistry),
            0.02 ether,
            0.2 ether,
            address(controller),
            _accountabilityInitCode(
                owner, address(consensus), address(disputeRegistry), 0.02 ether, 0.2 ether, address(controller)
            ),
            address(accountabilityIdentity).codehash
        );
        factory = new SafeBondFactoryV3(
            owner,
            address(consensus),
            address(disputeRegistry),
            address(controller),
            0.01 ether,
            0.02 ether,
            0.2 ether,
            0.02 ether,
            0.2 ether,
            address(partnershipFactory).codehash,
            address(accountabilityFactory).codehash,
            address(partnershipIdentity).codehash,
            address(accountabilityIdentity).codehash,
            keccak256(
                _accountabilityInitCode(
                    owner, address(consensus), address(disputeRegistry), 0.02 ether, 0.2 ether, address(controller)
                )
            )
        );
        assertEq(address(factory), predictedCoordinator, "coordinator prediction drifted");
        vm.prank(owner);
        factory.configureFactories(address(partnershipFactory), address(accountabilityFactory));
    }

    function test_factoryChildrenRequireGuardianCoverageBeforeControllerWrites() external {
        _configureForPartner(partyA);
        PartnershipBondV3Safe partnership = _deployPartnership(partyA);
        AccountabilityBondV3Safe accountability = _deployAccountability(partyA);

        assertFalse(controller.hasGuardianPauseCoverage(address(partnership)));
        assertFalse(controller.hasGuardianPauseCoverage(address(accountability)));
        vm.expectRevert(
            abi.encodeWithSelector(
                BasePilotAccessControllerCoreV3.GuardianPauseCoverageMissing.selector, address(partnership)
            )
        );
        controller.setWriteEnabled(true);

        guardianPauseTarget[address(partnership)] = true;
        vm.expectRevert(
            abi.encodeWithSelector(
                BasePilotAccessControllerCoreV3.GuardianPauseCoverageMissing.selector, address(accountability)
            )
        );
        controller.setWriteEnabled(true);

        guardianPauseTarget[address(accountability)] = true;
        controller.setWriteEnabled(true);
        assertTrue(controller.writeEnabled());
    }

    function test_partnershipFactoryChildRejectsArbitraryOffListTermsAndPartnerRotation() external {
        _configureForPartner(partyA);
        PartnershipBondV3Safe partnership = _deployPartnership(partyA);

        PartnershipBondV3Safe.Terms memory offList = _partnershipTerms(1, 0.01 ether, 0.01 ether);
        offList.partyA = company;
        offList.partyB = counterparty;
        bytes memory offListSigA = _signPartnership(partnership, offList, COMPANY_KEY);
        bytes memory offListSigB = _signPartnership(partnership, offList, COUNTERPARTY_KEY);
        vm.expectRevert(PartnershipBondV3Safe.DesignPartnerNotParticipant.selector);
        partnership.createBond(offList, offListSigA, offListSigB);

        // A partner rotation cannot rewrite a registered child's association.
        controller.setDesignPartner(partyA, false);
        controller.setDesignPartner(company, true);
        assertEq(controller.positionDesignPartner(address(partnership)), partyA);

        PartnershipBondV3Safe.Terms memory formerPartnerTerms = _partnershipTerms(2, 0.01 ether, 0.01 ether);
        bytes memory formerPartnerSigA = _signPartnership(partnership, formerPartnerTerms, PARTY_A_KEY);
        bytes memory formerPartnerSigB = _signPartnership(partnership, formerPartnerTerms, PARTY_B_KEY);
        vm.expectRevert(PartnershipBondV3Safe.DesignPartnerNotParticipant.selector);
        partnership.createBond(formerPartnerTerms, formerPartnerSigA, formerPartnerSigB);

        PartnershipBondV3Safe.Terms memory rotatedPartnerTerms = _partnershipTerms(3, 0.01 ether, 0.01 ether);
        rotatedPartnerTerms.partyA = company;
        bytes memory rotatedPartnerSigA = _signPartnership(partnership, rotatedPartnerTerms, COMPANY_KEY);
        bytes memory rotatedPartnerSigB = _signPartnership(partnership, rotatedPartnerTerms, PARTY_B_KEY);
        vm.expectRevert(PartnershipBondV3Safe.DesignPartnerNotParticipant.selector);
        partnership.createBond(rotatedPartnerTerms, rotatedPartnerSigA, rotatedPartnerSigB);
    }

    function test_accountabilityFactoryChildBindsPartnerToCompanyAndRejectsOffListTerms() external {
        _configureForPartner(company);
        AccountabilityBondV3Safe accountability = _deployAccountability(company);

        AccountabilityBondV3Safe.Terms memory offList = _accountabilityTerms(1, 0.01 ether);
        offList.aiCompany = partyA;
        offList.counterparty = partyB;
        AccountabilityBondV3Safe.SignerConfig memory offListSigners = _accountabilitySigners();
        AccountabilityBondV3Safe.EnforcementConfig memory offListEnforcement =
            _accountabilityEnforcement(offList, offListSigners);
        offList.objectiveBreachRulesHash = accountability.hashEnforcementConfig(offListEnforcement);
        bytes memory offListSigCompany = _signAccountability(accountability, offList, PARTY_A_KEY);
        bytes memory offListSigCounterparty = _signAccountability(accountability, offList, PARTY_B_KEY);
        vm.expectRevert(AccountabilityBondV3Safe.DesignPartnerNotCompany.selector);
        accountability.createBond(
            offList, offListEnforcement, offListSigners, offListSigCompany, offListSigCounterparty
        );

        // The partner cannot be moved to the non-funding counterparty role.
        AccountabilityBondV3Safe.Terms memory wrongRole = _accountabilityTerms(2, 0.01 ether);
        wrongRole.aiCompany = partyA;
        wrongRole.counterparty = company;
        AccountabilityBondV3Safe.SignerConfig memory wrongRoleSigners = _accountabilitySigners();
        AccountabilityBondV3Safe.EnforcementConfig memory wrongRoleEnforcement =
            _accountabilityEnforcement(wrongRole, wrongRoleSigners);
        wrongRole.objectiveBreachRulesHash = accountability.hashEnforcementConfig(wrongRoleEnforcement);
        bytes memory wrongRoleSigCompany = _signAccountability(accountability, wrongRole, PARTY_A_KEY);
        bytes memory wrongRoleSigCounterparty = _signAccountability(accountability, wrongRole, COMPANY_KEY);
        vm.expectRevert(AccountabilityBondV3Safe.DesignPartnerNotCompany.selector);
        accountability.createBond(
            wrongRole, wrongRoleEnforcement, wrongRoleSigners, wrongRoleSigCompany, wrongRoleSigCounterparty
        );

        uint256 id = _createAccountability(accountability, _accountabilityTerms(3, 0.01 ether));
        assertEq(id, 1);
    }

    function test_coveredChildRetainsPauseSafeRefundAndWithdrawalPath() external {
        _configureForPartner(partyA);
        PartnershipBondV3Safe partnership = _deployPartnership(partyA);
        guardianPauseTarget[address(partnership)] = true;
        controller.setWriteEnabled(true);

        uint256 id = _createPartnership(partnership, _partnershipTerms(1, 0.01 ether, 0.01 ether));
        vm.deal(partyA, 0.01 ether);
        vm.prank(partyA);
        partnership.fundNative{ value: 0.01 ether }(id);
        assertEq(controller.totalAllocated(), 0.01 ether);

        vm.prank(owner);
        partnership.pause();
        vm.prank(partyA);
        partnership.cancelBeforeActivation(id);
        vm.prank(partyA);
        partnership.withdrawCredit(address(0));

        assertEq(partnership.nativeLiability(), 0);
        assertEq(controller.totalAllocated(), 0);
    }

    function test_childDeployedAfterWriteEnablementCannotAcceptValueBeforeLiveCoverageProof() external {
        _configureForPartner(company);
        PartnershipBondV3Safe existing = _deployPartnership(company);
        guardianPauseTarget[address(existing)] = true;
        controller.setWriteEnabled(true);

        AccountabilityBondV3Safe laterChild = _deployAccountability(company);
        assertTrue(controller.guardianPauseCoverageRequired(address(laterChild)));
        uint256 id = _createAccountability(laterChild, _accountabilityTerms(1, 0.01 ether));
        vm.deal(company, 0.01 ether);
        vm.prank(company);
        vm.expectRevert(
            abi.encodeWithSelector(
                BasePilotAccessControllerCoreV3.GuardianPauseCoverageMissing.selector, address(laterChild)
            )
        );
        laterChild.fundNative{ value: 0.01 ether }(id);

        guardianPauseTarget[address(laterChild)] = true;
        vm.prank(company);
        laterChild.fundNative{ value: 0.01 ether }(id);
        assertEq(controller.positionAllocated(address(laterChild)), 0.01 ether);
    }

    function _configureForPartner(address partner) private {
        controller.setModule(address(factory), true);
        controller.setDesignPartner(partner, true);
        controller.setCaps(0.4 ether, 0.2 ether);
        controller.setDeployEnabled(true);
        vm.prank(owner);
        factory.setDeployAllowed(true);
    }

    function _deployPartnership(address partner) private returns (PartnershipBondV3Safe) {
        vm.prank(owner);
        return PartnershipBondV3Safe(factory.deployPartnership(partner, address(0)));
    }

    function _deployAccountability(address partner) private returns (AccountabilityBondV3Safe) {
        vm.prank(owner);
        return AccountabilityBondV3Safe(factory.deployAccountability(partner));
    }
}
