// 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 Directed Base-only regression for factory registration and
/// controller/native-liability lockstep accounting.
contract SafeBondFactoryControllerIntegrationV3Test is V3Fixtures {
    BasePilotAccessControllerV3 internal controller;
    SafeBondFactoryV3 internal factory;
    PartnershipBondV3Safe internal partnership;
    PartnershipBondFactoryV3 internal partnershipFactory;
    AccountabilityBondFactoryV3 internal accountabilityFactory;
    mapping(address => bool) internal guardianPauseTarget;

    /// @dev The controller reads its governance contract's pause-target
    /// registry. This test contract is that governance fixture.
    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);
        partnershipFactory = new PartnershipBondFactoryV3(
            predictedCoordinator,
            owner,
            address(consensus),
            address(disputeRegistry),
            0.01 ether,
            0.02 ether,
            0.2 ether,
            address(controller)
        );
        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");
    }

    function test_orderedConstructionThenTracksNativeLiabilityThroughWithdrawal() external {
        // Exact governed construction order: controller gates first, then
        // one-time reviewed factory binding, factory deploy gate, and children.
        controller.setModule(address(factory), true);
        controller.setDesignPartner(partyA, true);
        controller.setCaps(0.4 ether, 0.2 ether);
        controller.setDeployEnabled(true);
        _configureFactories();
        vm.prank(owner);
        factory.setDeployAllowed(true);

        vm.prank(owner);
        partnership = PartnershipBondV3Safe(factory.deployPartnership(partyA, address(0)));
        assertEq(partnership.rewardProgram(), address(0), "pilot bond must not bind a reward program");
        assertEq(controller.positionAllocated(address(partnership)), 0);
        assertEq(address(partnership.pilotController()), address(controller));

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

        PartnershipBondV3Safe.Terms memory terms = _partnershipTerms(1, 0.01 ether, 0.01 ether);
        terms.objectiveRuleClass = 0;
        terms.objectiveRuleSchemaVersion = 0;
        uint256 id = _createPartnership(partnership, terms);
        vm.deal(partyA, 0.01 ether);
        vm.prank(partyA);
        partnership.fundNative{ value: 0.01 ether }(id);

        controller.setWriteEnabled(false);
        vm.deal(partyB, 0.01 ether);
        vm.prank(partyB);
        vm.expectRevert(BasePilotAccessControllerCoreV3.WriteDisabled.selector);
        partnership.fundNative{ value: 0.01 ether }(id);
        controller.setWriteEnabled(true);
        vm.prank(partyB);
        partnership.fundNative{ value: 0.01 ether }(id);
        PartnershipBondV3Safe.BondData memory funded = partnership.bondOf(id);
        vm.warp(funded.terms.activePeriodStart);
        partnership.activate(id);
        assertEq(partnership.nativeLiability(), 0.02 ether);
        assertEq(controller.positionAllocated(address(partnership)), 0.02 ether);
        assertEq(controller.totalAllocated(), 0.02 ether);

        PartnershipBondV3Safe.BondData memory bond = partnership.bondOf(id);
        vm.warp(bond.terms.activePeriodEnd);
        partnership.markExpired(id);
        partnership.forceExit(id);
        assertEq(controller.totalAllocated(), 0.02 ether, "credit conversion must retain cap occupancy");

        vm.prank(partyA);
        partnership.withdrawCredit(address(0));
        vm.prank(partyB);
        partnership.withdrawCredit(address(0));
        assertEq(partnership.nativeLiability(), 0);
        assertEq(controller.positionAllocated(address(partnership)), 0);
        assertEq(controller.totalAllocated(), 0);
    }

    function testFuzz_factoryFundingNeverExceedsControllerOrNativeCap(uint96 firstRaw, uint96 secondRaw) external {
        controller.setModule(address(factory), true);
        controller.setDesignPartner(partyA, true);
        controller.setCaps(0.4 ether, 0.2 ether);
        controller.setDeployEnabled(true);
        _configureFactories();
        vm.prank(owner);
        factory.setDeployAllowed(true);
        vm.prank(owner);
        partnership = PartnershipBondV3Safe(factory.deployPartnership(partyA, address(0)));
        guardianPauseTarget[address(partnership)] = true;
        controller.setWriteEnabled(true);

        uint256 a = bound(uint256(firstRaw), 1, 0.01 ether);
        uint256 b = bound(uint256(secondRaw), 1, 0.01 ether);
        PartnershipBondV3Safe.Terms memory terms = _partnershipTerms(2, a, b);
        terms.objectiveRuleClass = 0;
        terms.objectiveRuleSchemaVersion = 0;
        uint256 id = _createPartnership(partnership, terms);
        vm.deal(partyA, a);
        vm.prank(partyA);
        partnership.fundNative{ value: a }(id);
        vm.deal(partyB, b);
        vm.prank(partyB);
        partnership.fundNative{ value: b }(id);

        assertLe(controller.totalAllocated(), controller.globalCap());
        assertEq(controller.totalAllocated(), partnership.nativeLiability());
        assertLe(partnership.nativeLiability(), partnership.maxAggregateNativeLiability());
    }

    function _configureFactories() private {
        if (address(factory.partnershipFactory()) == address(0)) {
            // The dedicated factory deployments are part of the setup, but the
            // coordinator binding intentionally occurs only in construction.
            vm.prank(owner);
            factory.configureFactories(address(partnershipFactory), address(accountabilityFactory));
        }
    }
}
