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

import { V3Fixtures } from "../helpers/V3Fixtures.sol";
import { AccountabilityBondV3Safe } from "../../contracts/bonds/AccountabilityBondV3Safe.sol";
import { BondStateV3 } from "../../contracts/bonds/BondStateV3.sol";

contract ProhibitedInputCanonicalSourceMock {
    bytes32 public constant SOURCE_SCHEMA = keccak256("Vaultfire.ObjectiveEvidence.CanonicalRecordedAt.v1");
    bool public reputationFlag;

    function setReputationFlag(bool value) external {
        reputationFlag = value;
    }

    function recordedAt(bytes32, bytes32) external view returns (uint64) {
        return reputationFlag ? uint64(block.timestamp) : 0;
    }
}

contract AccountabilityTypedEnforcementV3Test is V3Fixtures {
    AccountabilityBondV3Safe internal bond;

    function setUp() public {
        _setUpDependencies();
        bond = new AccountabilityBondV3Safe(
            owner, address(consensus), address(disputeRegistry), 10 ether, 100 ether, address(0)
        );
    }

    function test_canonicalAbsenceAppliesOnlyPreAgreedSlashWithoutSigners() external {
        (uint256 id, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(4 ether, 41);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        _completeEnforcement(id);

        AccountabilityBondV3Safe.BondData memory settled = bond.bondOf(id);
        assertEq(uint256(settled.state), uint256(BondStateV3.State.Settled));
        assertEq(settled.slashedAmount, 2 ether);
        assertEq(bond.creditOf(address(0), counterparty), 2 ether);
        assertEq(bond.creditOf(address(0), company), 2 ether);
    }

    function test_forgedCallerTimestampCannotOverrideCanonicalSource() external {
        (, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(2 ether, 42);
        evidence.recordedAt = evidence.requiredBy + 1;

        vm.expectRevert(AccountabilityBondV3Safe.CanonicalSourceMismatch.selector);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
    }

    function test_timelyCanonicalRecordFailsClosedAgainstSlashing() external {
        (, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(2 ether, 43);
        vm.prank(company);
        canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId);
        evidence.recordedAt = evidence.requiredBy;

        vm.expectRevert(AccountabilityBondV3Safe.InvalidEvidence.selector);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
    }

    function test_thresholdSignaturesAreRejectedAsProhibitedOffchainAuthority() external {
        (, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(2 ether, 44);
        address[] memory signers = new address[](1);
        bytes[] memory signatures = new bytes[](1);
        signers[0] = attester1;
        signatures[0] = hex"01";

        vm.expectRevert(AccountabilityBondV3Safe.InvalidEvidence.selector);
        bond.submitObjectiveEvidence(evidence, signers, signatures);
    }

    function test_wrongPinnedCodehashCannotCreateBond() external {
        AccountabilityBondV3Safe.Terms memory terms = _accountabilityTerms(45, 2 ether);
        AccountabilityBondV3Safe.SignerConfig memory signers = _accountabilitySigners();
        AccountabilityBondV3Safe.EnforcementConfig memory config = _accountabilityEnforcement(terms, signers);
        config.canonicalSourceCodehash = bytes32(uint256(1));
        terms.objectiveBreachRulesHash = bond.hashEnforcementConfig(config);
        bytes memory companySig = _signAccountability(bond, terms, COMPANY_KEY);
        bytes memory counterpartySig = _signAccountability(bond, terms, COUNTERPARTY_KEY);

        vm.expectRevert(AccountabilityBondV3Safe.InvalidRule.selector);
        bond.createBond(terms, config, signers, companySig, counterpartySig);
    }

    function test_schemaCompatibleProhibitedInputSourceIsRejectedBySemanticCodeIdentity() external {
        ProhibitedInputCanonicalSourceMock hostile = new ProhibitedInputCanonicalSourceMock();
        AccountabilityBondV3Safe.Terms memory terms = _accountabilityTerms(47, 2 ether);
        AccountabilityBondV3Safe.SignerConfig memory signers = _accountabilitySigners();
        AccountabilityBondV3Safe.EnforcementConfig memory config = _accountabilityEnforcement(terms, signers);
        config.canonicalSource = address(hostile);
        config.canonicalSourceCodehash = address(hostile).codehash;
        terms.objectiveBreachRulesHash = bond.hashEnforcementConfig(config);
        bytes memory companySig = _signAccountability(bond, terms, COMPANY_KEY);
        bytes memory counterpartySig = _signAccountability(bond, terms, COUNTERPARTY_KEY);

        vm.expectRevert(AccountabilityBondV3Safe.InvalidRule.selector);
        bond.createBond(terms, config, signers, companySig, counterpartySig);
    }

    function test_missionBoundaryRejectsOpaqueLegacyInputs() external {
        (uint256 id,) = _activeEvidence(2 ether, 46);
        (bool ok,) = address(bond)
            .call(
                abi.encodeWithSignature(
                    "attestBreach(uint256,uint256,bytes32,uint96)",
                    id,
                    1 ether,
                    keccak256("behavioral-reputation-profile"),
                    99
                )
            );
        assertFalse(ok);
    }

    function testFuzz_maximumSlashIsDeterministicAndConservesPrincipal(uint96 rawStake) external {
        uint256 stake = bound(uint256(rawStake), 2, 10 ether);
        (, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(stake, 80);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        _completeEnforcement(evidence.bondId);

        uint256 slash = stake / 2;
        assertEq(bond.creditOf(address(0), company) + bond.creditOf(address(0), counterparty), stake);
        assertEq(bond.creditOf(address(0), counterparty), slash);
    }

    function _activeEvidence(uint256 stake, uint96 nonce)
        private
        returns (uint256 id, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence)
    {
        AccountabilityBondV3Safe.Terms memory terms = _accountabilityTerms(nonce, stake);
        id = _createAccountability(bond, terms);
        _fundActiveAccountability(bond, id, stake);
        AccountabilityBondV3Safe.BondData memory data = bond.bondOf(id);
        AccountabilityBondV3Safe.EnforcementConfig memory enforcement = bond.enforcementOf(id);
        vm.warp(enforcement.requiredBy);
        evidence = AccountabilityBondV3Safe.ObjectiveEvidence({
            bond: address(bond),
            chainId: block.chainid,
            bondId: id,
            termsHash: data.termsHash,
            ruleHash: data.terms.objectiveBreachRulesHash,
            canonicalSource: enforcement.canonicalSource,
            canonicalSourceId: enforcement.canonicalSourceId,
            obligationId: enforcement.obligationId,
            requiredBy: enforcement.requiredBy,
            recordedAt: 0,
            observedAt: uint64(block.timestamp),
            nonce: nonce,
            signatureDeadline: enforcement.requiredBy + enforcement.responseWindow
        });
    }

    function _completeEnforcement(uint256 bondId) private {
        AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(bondId);
        vm.expectRevert(AccountabilityBondV3Safe.DeadlineNotReached.selector);
        bond.advanceObjectiveEvidence(bondId);
        vm.warp(state.responseDeadline);
        bond.advanceObjectiveEvidence(bondId);
        vm.expectRevert(AccountabilityBondV3Safe.DeadlineNotReached.selector);
        bond.advanceObjectiveEvidence(bondId);
        vm.warp(state.challengeDeadline);
        bond.advanceObjectiveEvidence(bondId);
        vm.expectRevert(AccountabilityBondV3Safe.DeadlineNotReached.selector);
        bond.advanceObjectiveEvidence(bondId);
        vm.warp(state.decisionDeadline);
        bond.advanceObjectiveEvidence(bondId);
    }
}
