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

import { Test } from "forge-std/Test.sol";
import { AccountabilityBondV3Safe } from "../../contracts/bonds/AccountabilityBondV3Safe.sol";
import { PartnershipBondV3Safe } from "../../contracts/bonds/PartnershipBondV3Safe.sol";
import { ThresholdConsensusV3 } from "../../contracts/consensus/ThresholdConsensusV3.sol";
import { DisputeRegistryV3 } from "../../contracts/disputes/DisputeRegistryV3.sol";

contract BondTermsHardeningV3Test is Test {
    address internal constant OWNER = address(0xA11CE);
    address internal constant PARTY_A = address(0xA);
    address internal constant PARTY_B = address(0xB);

    PartnershipBondV3Safe internal partnership;
    AccountabilityBondV3Safe internal accountability;

    function setUp() external {
        ThresholdConsensusV3 consensus = new ThresholdConsensusV3(OWNER, 2);
        DisputeRegistryV3 disputes = new DisputeRegistryV3(OWNER);
        partnership = new PartnershipBondV3Safe(
            OWNER, address(0), address(consensus), address(disputes), 1 ether, 2 ether, 10 ether, address(0)
        );
        accountability =
            new AccountabilityBondV3Safe(OWNER, address(consensus), address(disputes), 2 ether, 10 ether, address(0));
    }

    function test_PartnershipRejectsZeroChallengeWindowAndWrongVersion() external {
        PartnershipBondV3Safe.Terms memory terms = _partnershipTerms();
        terms.challengeWindow = 0;
        vm.expectRevert(PartnershipBondV3Safe.BadTerms.selector);
        partnership.createBond(terms, "", "");

        terms = _partnershipTerms();
        terms.protocolVersion = "2.0.0";
        vm.expectRevert(PartnershipBondV3Safe.BadTerms.selector);
        partnership.createBond(terms, "", "");
    }

    function test_AccountabilityRejectsZeroChallengeWindowAndWrongVersion() external {
        AccountabilityBondV3Safe.Terms memory terms = _accountabilityTerms();
        terms.challengeWindow = 0;
        vm.expectRevert(AccountabilityBondV3Safe.EnforcementRequired.selector);
        accountability.createBond(terms, "", "");

        terms = _accountabilityTerms();
        terms.protocolVersion = "2.0.0";
        vm.expectRevert(AccountabilityBondV3Safe.EnforcementRequired.selector);
        accountability.createBond(terms, "", "");
    }

    function test_PartnershipRejectsChallengeDeadlineOverflow() external {
        PartnershipBondV3Safe.Terms memory terms = _partnershipTerms();
        terms.activePeriodEnd = type(uint64).max;
        terms.challengeWindow = 1;
        vm.expectRevert(PartnershipBondV3Safe.BadTerms.selector);
        partnership.createBond(terms, "", "");
    }

    function test_AccountabilityRejectsChallengeDeadlineOverflow() external {
        AccountabilityBondV3Safe.Terms memory terms = _accountabilityTerms();
        terms.activePeriodEnd = type(uint64).max;
        terms.challengeWindow = 1;
        vm.expectRevert(AccountabilityBondV3Safe.EnforcementRequired.selector);
        accountability.createBond(terms, "", "");
    }

    function test_PartnershipRejectsChallengeWindowAbovePracticalMaximum() external {
        PartnershipBondV3Safe.Terms memory terms = _partnershipTerms();
        terms.challengeWindow = 30 days + 1;
        vm.expectRevert(PartnershipBondV3Safe.BadTerms.selector);
        partnership.createBond(terms, "", "");
    }

    function test_AccountabilityRejectsChallengeWindowAbovePracticalMaximum() external {
        AccountabilityBondV3Safe.Terms memory terms = _accountabilityTerms();
        terms.challengeWindow = 30 days + 1;
        vm.expectRevert(AccountabilityBondV3Safe.EnforcementRequired.selector);
        accountability.createBond(terms, "", "");
    }

    function test_PartnershipRejectsRewardProgramBindingAndSignedRewardTerms() external {
        ThresholdConsensusV3 consensus = new ThresholdConsensusV3(OWNER, 2);
        DisputeRegistryV3 disputes = new DisputeRegistryV3(OWNER);
        vm.expectRevert(PartnershipBondV3Safe.RewardProgramDisabled.selector);
        new PartnershipBondV3Safe(
            OWNER, address(0xCAFE), address(consensus), address(disputes), 1 ether, 2 ether, 10 ether, address(0)
        );

        PartnershipBondV3Safe.Terms memory terms = _partnershipTerms();
        terms.objectiveRuleClass = 0;
        terms.objectiveRuleSchemaVersion = 0;
        terms.flourishRewardConditionsHash = keccak256("not operational");
        vm.expectRevert(PartnershipBondV3Safe.RewardTermsDisabled.selector);
        partnership.createBond(terms, "", "");

        terms = _partnershipTerms();
        terms.objectiveRuleClass = 0;
        terms.objectiveRuleSchemaVersion = 0;
        terms.rewardProgramId = 1;
        vm.expectRevert(PartnershipBondV3Safe.RewardTermsDisabled.selector);
        partnership.createBond(terms, "", "");
    }

    function test_AccountabilityRejectsSignedRewardTerms() external {
        AccountabilityBondV3Safe.Terms memory terms = _accountabilityTerms();
        terms.objectiveRuleClass = 0;
        terms.objectiveRuleSchemaVersion = 0;
        terms.flourishRewardRulesHash = keccak256("not operational");
        vm.expectRevert(AccountabilityBondV3Safe.EnforcementRequired.selector);
        accountability.createBond(terms, "", "");

        terms = _accountabilityTerms();
        terms.objectiveRuleClass = 0;
        terms.objectiveRuleSchemaVersion = 0;
        terms.rewardProgramId = 1;
        vm.expectRevert(AccountabilityBondV3Safe.EnforcementRequired.selector);
        accountability.createBond(terms, "", "");
    }

    function test_ProhibitedOrOpaqueRuleTermsCannotCreateCustodyPositions() external {
        PartnershipBondV3Safe.Terms memory partnershipTerms = _partnershipTerms();
        partnershipTerms.objectiveRuleClass = 0; // No score/reputation/behavioral class exists.
        vm.expectRevert(PartnershipBondV3Safe.BadTerms.selector);
        partnership.createBond(partnershipTerms, "", "");

        partnershipTerms = _partnershipTerms();
        partnershipTerms.breachConditionsHash = keccak256("quality-score-is-not-a-rule");
        vm.expectRevert(PartnershipBondV3Safe.BadTerms.selector);
        partnership.createBond(partnershipTerms, "", "");

        AccountabilityBondV3Safe.Terms memory accountabilityTerms = _accountabilityTerms();
        accountabilityTerms.objectiveRuleClass = 7; // Unallowlisted classes fail closed.
        vm.expectRevert(AccountabilityBondV3Safe.EnforcementRequired.selector);
        accountability.createBond(accountabilityTerms, "", "");

        accountabilityTerms = _accountabilityTerms();
        accountabilityTerms.missionHash = keccak256("flourishing-score-is-not-a-rule");
        vm.expectRevert(AccountabilityBondV3Safe.EnforcementRequired.selector);
        accountability.createBond(accountabilityTerms, "", "");
    }

    function test_flourishingMissionIsPositiveButCannotControlProtectedDecisions() external view {
        assertEq(
            partnership.FLOURISHING_MISSION(), "Cooperation, accountable growth, freedom, and human/agent flourishing"
        );
        bytes32 boundary = keccak256("FLOURISHING_IS_A_MISSION_OUTCOME_NOT_A_SCORE_OR_PROTECTED_DECISION_INPUT");
        assertEq(partnership.FLOURISHING_NON_CONTROL_BOUNDARY(), boundary);
        assertEq(accountability.FLOURISHING_NON_CONTROL_BOUNDARY(), boundary);
        assertEq(partnership.rewardProgram(), address(0));
    }

    function _partnershipTerms() private view returns (PartnershipBondV3Safe.Terms memory terms) {
        terms = PartnershipBondV3Safe.Terms({
            bondId: 1,
            partyA: PARTY_A,
            partyB: PARTY_B,
            partyAStake: 1 ether,
            partyBStake: 1 ether,
            stakeAsset: address(0),
            partnershipType: keccak256("partnership"),
            breachConditionsHash: bytes32(0),
            objectiveRuleClass: 1,
            objectiveRuleSchemaVersion: 1,
            flourishRewardConditionsHash: bytes32(0),
            rewardProgramId: 0,
            proposalDeadline: uint64(block.timestamp + 1 days),
            activePeriodStart: uint64(block.timestamp + 2 days),
            activePeriodEnd: uint64(block.timestamp + 3 days),
            challengeWindow: 1 days,
            nonce: 1,
            chainId: block.chainid,
            protocolVersion: "3.0.0"
        });
    }

    function _accountabilityTerms() private view returns (AccountabilityBondV3Safe.Terms memory terms) {
        terms = AccountabilityBondV3Safe.Terms({
            bondId: 1,
            aiCompany: PARTY_A,
            counterparty: PARTY_B,
            companyStake: 1 ether,
            stakeAsset: address(0),
            missionHash: bytes32(0),
            objectiveBreachRulesHash: bytes32(0),
            objectiveRuleClass: 1,
            objectiveRuleSchemaVersion: 1,
            flourishRewardRulesHash: bytes32(0),
            rewardProgramId: 0,
            activePeriodStart: uint64(block.timestamp + 1 days),
            activePeriodEnd: uint64(block.timestamp + 2 days),
            challengeWindow: 1 days,
            nonce: 1,
            chainId: block.chainid,
            protocolVersion: "3.0.0"
        });
    }
}
