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

import { PartnershipBondFactoryV3 } from "./PartnershipBondFactoryV3.sol";
import { AccountabilityBondFactoryV3 } from "./AccountabilityBondFactoryV3.sol";
import { IPilotAccessControllerV3 } from "../pilot/IPilotAccessControllerV3.sol";

interface IAccountabilityBondDeploymentStateV3 {
    function owner() external view returns (address);
    function consensus() external view returns (address);
    function disputeRegistry() external view returns (address);
    function pilotController() external view returns (address);
    function maxBondNativePrincipal() external view returns (uint256);
    function maxAggregateNativeLiability() external view returns (uint256);
    function nextBondId() external view returns (uint256);
    function nativeLiability() external view returns (uint256);
    function paused() external view returns (bool);
}

/// @title SafeBondFactoryV3
/// @notice Governance-owned deployment coordinator for V3 bond contracts.
/// @dev The specialized factories contain the child deployment bytecode,
///      avoiding a single factory that exceeds EIP-170. Both specialized
///      factories only accept calls from this coordinator. Every child is
///      constructed with `owner` directly, never with temporary EOA or factory
///      ownership.
contract SafeBondFactoryV3 {
    bytes32 public constant SUPPORTED_PILOT_MODULE_ID = keccak256("Vaultfire.BaseV3.SafeBondFactory");
    bytes32 public constant GOVERNANCE_POLICY_MODULE_ID = keccak256("Vaultfire.BaseV3.SafeBondFactory");
    uint256 private constant BASE_CHAIN_ID = 8453;
    uint256 private constant BASE_SEPOLIA_CHAIN_ID = 84532;
    error NotOwner();
    error ZeroAddress();
    error DeployDisabled();
    error FactoriesAlreadyConfigured();
    error InvalidFactory();
    error FactoryRuntimeCodeHashMismatch();
    error ChildRuntimeCodeHashMismatch();

    address public owner;
    address public immutable consensus;
    address public immutable disputeRegistry;
    IPilotAccessControllerV3 public immutable pilotController;
    uint256 public immutable partnershipMaxPartyNativePrincipal;
    uint256 public immutable partnershipMaxBondNativePrincipal;
    uint256 public immutable partnershipMaxAggregateNativeLiability;
    uint256 public immutable accountabilityMaxBondNativePrincipal;
    uint256 public immutable accountabilityMaxAggregateNativeLiability;
    /// @notice Exact deployed-code identities, including Solidity immutable patches.
    /// @dev These are constructor-pinned for fresh deployments. The specialized
    ///      factory hashes include their coordinator/owner/cap immutable values;
    ///      child hashes include their fixed constructor immutable values.
    bytes32 public immutable expectedPartnershipFactoryRuntimeCodeHash;
    bytes32 public immutable expectedAccountabilityFactoryRuntimeCodeHash;
    bytes32 public immutable expectedPartnershipBondRuntimeCodeHash;
    bytes32 public immutable expectedAccountabilityBondRuntimeCodeHash;
    bytes32 public immutable expectedAccountabilityBondInitCodeHash;
    bool public deployAllowed;
    PartnershipBondFactoryV3 public partnershipFactory;
    AccountabilityBondFactoryV3 public accountabilityFactory;

    event PartnershipBondDeployedV3(address indexed bond, address indexed designPartner);
    event AccountabilityBondDeployedV3(address indexed bond, address indexed designPartner);
    event DeployAllowedSetV3(bool allowed);
    event FactoriesConfiguredV3(address indexed partnershipFactory, address indexed accountabilityFactory);

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    function supportedPilotModuleId() external pure returns (bytes32) {
        return SUPPORTED_PILOT_MODULE_ID;
    }

    constructor(
        address _owner,
        address _consensus,
        address _disputeRegistry,
        address _pilotController,
        uint256 _partnershipMaxPartyNativePrincipal,
        uint256 _partnershipMaxBondNativePrincipal,
        uint256 _partnershipMaxAggregateNativeLiability,
        uint256 _accountabilityMaxBondNativePrincipal,
        uint256 _accountabilityMaxAggregateNativeLiability,
        bytes32 _expectedPartnershipFactoryRuntimeCodeHash,
        bytes32 _expectedAccountabilityFactoryRuntimeCodeHash,
        bytes32 _expectedPartnershipBondRuntimeCodeHash,
        bytes32 _expectedAccountabilityBondRuntimeCodeHash,
        bytes32 _expectedAccountabilityBondInitCodeHash
    ) {
        if (
            _owner == address(0) || _consensus == address(0)
                || ((block.chainid == BASE_CHAIN_ID || block.chainid == BASE_SEPOLIA_CHAIN_ID)
                    && _pilotController == address(0))
                || (_pilotController != address(0) && _pilotController.code.length == 0)
        ) revert ZeroAddress();
        if (
            _pilotController != address(0)
                && IPilotAccessControllerV3(_pilotController).requiredChainId() != block.chainid
        ) {
            revert ZeroAddress();
        }
        if (
            _partnershipMaxPartyNativePrincipal == 0 || _partnershipMaxBondNativePrincipal == 0
                || _partnershipMaxAggregateNativeLiability == 0 || _accountabilityMaxBondNativePrincipal == 0
                || _accountabilityMaxAggregateNativeLiability == 0
                || _partnershipMaxPartyNativePrincipal > _partnershipMaxBondNativePrincipal
                || _partnershipMaxBondNativePrincipal > _partnershipMaxAggregateNativeLiability
                || _accountabilityMaxBondNativePrincipal > _accountabilityMaxAggregateNativeLiability
                || _expectedPartnershipFactoryRuntimeCodeHash == bytes32(0)
                || _expectedAccountabilityFactoryRuntimeCodeHash == bytes32(0)
                || _expectedPartnershipBondRuntimeCodeHash == bytes32(0)
                || _expectedAccountabilityBondRuntimeCodeHash == bytes32(0)
                || _expectedAccountabilityBondInitCodeHash == bytes32(0)
        ) revert ZeroAddress();
        owner = _owner;
        consensus = _consensus;
        _disputeRegistry;
        disputeRegistry = address(0);
        pilotController = IPilotAccessControllerV3(_pilotController);
        partnershipMaxPartyNativePrincipal = _partnershipMaxPartyNativePrincipal;
        partnershipMaxBondNativePrincipal = _partnershipMaxBondNativePrincipal;
        partnershipMaxAggregateNativeLiability = _partnershipMaxAggregateNativeLiability;
        accountabilityMaxBondNativePrincipal = _accountabilityMaxBondNativePrincipal;
        accountabilityMaxAggregateNativeLiability = _accountabilityMaxAggregateNativeLiability;
        expectedPartnershipFactoryRuntimeCodeHash = _expectedPartnershipFactoryRuntimeCodeHash;
        expectedAccountabilityFactoryRuntimeCodeHash = _expectedAccountabilityFactoryRuntimeCodeHash;
        expectedPartnershipBondRuntimeCodeHash = _expectedPartnershipBondRuntimeCodeHash;
        expectedAccountabilityBondRuntimeCodeHash = _expectedAccountabilityBondRuntimeCodeHash;
        expectedAccountabilityBondInitCodeHash = _expectedAccountabilityBondInitCodeHash;
    }

    /// @notice One-time binding of separately deployed specialized factories.
    /// @dev This avoids embedding both child creation payloads in this
    ///      coordinator's initcode. Exact immutable-patched runtime hashes and
    ///      every immutable factory parameter are checked on-chain before the
    ///      deployment surface can be enabled.
    function configureFactories(address partnership, address accountability) external onlyOwner {
        if (address(partnershipFactory) != address(0) || address(accountabilityFactory) != address(0)) {
            revert FactoriesAlreadyConfigured();
        }
        if (partnership.code.length == 0 || accountability.code.length == 0) revert InvalidFactory();
        if (
            partnership.codehash != expectedPartnershipFactoryRuntimeCodeHash
                || accountability.codehash != expectedAccountabilityFactoryRuntimeCodeHash
        ) revert FactoryRuntimeCodeHashMismatch();
        PartnershipBondFactoryV3 p = PartnershipBondFactoryV3(partnership);
        AccountabilityBondFactoryV3 a = AccountabilityBondFactoryV3(accountability);
        if (
            p.coordinator() != address(this) || p.bondOwner() != owner || p.consensus() != consensus
                || p.disputeRegistry() != disputeRegistry || p.pilotController() != address(pilotController)
                || p.maxPartyNativePrincipal() != partnershipMaxPartyNativePrincipal
                || p.maxBondNativePrincipal() != partnershipMaxBondNativePrincipal
                || p.maxAggregateNativeLiability() != partnershipMaxAggregateNativeLiability
                || a.coordinator() != address(this) || a.bondOwner() != owner || a.consensus() != consensus
                || a.disputeRegistry() != disputeRegistry || a.pilotController() != address(pilotController)
                || a.maxBondNativePrincipal() != accountabilityMaxBondNativePrincipal
                || a.maxAggregateNativeLiability() != accountabilityMaxAggregateNativeLiability
                || a.childInitCodeHash() != expectedAccountabilityBondInitCodeHash
                || a.expectedChildRuntimeCodeHash() != expectedAccountabilityBondRuntimeCodeHash
        ) revert InvalidFactory();
        partnershipFactory = p;
        accountabilityFactory = a;
        emit FactoriesConfiguredV3(partnership, accountability);
    }

    function setDeployAllowed(bool v) external onlyOwner {
        if (
            v
                && (address(partnershipFactory) == address(0)
                    || address(accountabilityFactory) == address(0)
                    || accountabilityFactory.childInitCodeHash() != expectedAccountabilityBondInitCodeHash)
        ) revert DeployDisabled();
        deployAllowed = v;
        emit DeployAllowedSetV3(v);
    }

    /// @notice Deploy an initial-pilot partnership bond.
    /// @dev `rewardProgram` is retained only for ABI compatibility and must be
    ///      zero. The child enforces that fail-closed policy.
    function deployPartnership(address designPartner, address rewardProgram) external onlyOwner returns (address bond) {
        if (!deployAllowed || address(partnershipFactory) == address(0)) revert DeployDisabled();
        bond = partnershipFactory.deploy(rewardProgram);
        if (bond.codehash != expectedPartnershipBondRuntimeCodeHash) revert ChildRuntimeCodeHashMismatch();
        // The controller is intentionally the second deployment gate. It
        // checks that this coordinator is allowlisted, the partner is
        // allowlisted, native caps are configured, and deploys are enabled.
        if (address(pilotController) != address(0)) {
            pilotController.registerGuardianPauseablePosition(designPartner, bond, 0);
        }
        emit PartnershipBondDeployedV3(bond, designPartner);
    }

    function deployAccountability(address designPartner) external onlyOwner returns (address bond) {
        if (!deployAllowed || address(accountabilityFactory) == address(0)) revert DeployDisabled();
        bond = accountabilityFactory.deploy();
        if (bond.codehash != expectedAccountabilityBondRuntimeCodeHash) revert ChildRuntimeCodeHashMismatch();
        IAccountabilityBondDeploymentStateV3 child = IAccountabilityBondDeploymentStateV3(bond);
        if (
            child.owner() != owner || child.consensus() != consensus || child.disputeRegistry() != address(0)
                || child.pilotController() != address(pilotController)
                || child.maxBondNativePrincipal() != accountabilityMaxBondNativePrincipal
                || child.maxAggregateNativeLiability() != accountabilityMaxAggregateNativeLiability
                || child.nextBondId() != 1 || child.nativeLiability() != 0 || child.paused()
        ) revert ChildRuntimeCodeHashMismatch();
        if (address(pilotController) != address(0)) {
            pilotController.registerGuardianPauseablePosition(designPartner, bond, 0);
        }
        emit AccountabilityBondDeployedV3(bond, designPartner);
    }
}
