// 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";

/// @notice Repository regression for the independent exact-active-end harness.
contract IndependentBondBoundaryRegressionV3Test is V3Fixtures {
    AccountabilityBondV3Safe internal bond;

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

    function test_endMinusOneCannotExpireAndLateEvidenceCannotRestartWindows() external {
        (
            uint256 id,
            AccountabilityBondV3Safe.Terms memory terms,
            AccountabilityBondV3Safe.ObjectiveEvidence memory evidence
        ) = _activeEvidence(1, 1 ether);
        vm.warp(terms.activePeriodEnd - 1);
        evidence.observedAt = uint64(block.timestamp);
        evidence.signatureDeadline = uint64(block.timestamp);

        vm.expectRevert(AccountabilityBondV3Safe.DeadlinePassed.selector);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        vm.expectRevert(AccountabilityBondV3Safe.DeadlineNotReached.selector);
        bond.markExpired(id);
    }

    function test_exactEndEvidenceFirstStillGivesExpiryExclusivePrecedence() external {
        (
            uint256 id,
            AccountabilityBondV3Safe.Terms memory terms,
            AccountabilityBondV3Safe.ObjectiveEvidence memory evidence
        ) = _activeEvidence(2, 1 ether);
        vm.warp(terms.activePeriodEnd);
        evidence.observedAt = uint64(block.timestamp);
        evidence.signatureDeadline = uint64(block.timestamp);

        vm.expectRevert(AccountabilityBondV3Safe.DeadlinePassed.selector);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        bond.markExpired(id);
        bond.forceExit(id);
        assertEq(bond.creditOf(address(0), counterparty), 0);
        assertEq(bond.creditOf(address(0), company), 1 ether);
    }

    function test_exactEndExpiryFirstPreventsEvidenceAndGivesSameFullRefund() external {
        (
            uint256 id,
            AccountabilityBondV3Safe.Terms memory terms,
            AccountabilityBondV3Safe.ObjectiveEvidence memory evidence
        ) = _activeEvidence(3, 1 ether);
        vm.warp(terms.activePeriodEnd);
        bond.markExpired(id);
        bond.forceExit(id);

        vm.expectRevert(
            abi.encodeWithSelector(AccountabilityBondV3Safe.BadState.selector, BondStateV3.State.ForceExited)
        );
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        assertEq(bond.creditOf(address(0), counterparty), 0);
        assertEq(bond.creditOf(address(0), company), 1 ether);
    }

    function test_endPlusOneAcceptedEvidenceFinalizesDeterministically() external {
        (
            uint256 id,
            AccountabilityBondV3Safe.Terms memory terms,
            AccountabilityBondV3Safe.ObjectiveEvidence memory evidence
        ) = _activeEvidence(4, 1 ether);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.SettlementRequested));

        vm.warp(terms.activePeriodEnd + 1);
        vm.expectRevert(AccountabilityBondV3Safe.DeadlinePassed.selector);
        bond.advanceObjectiveEvidence(id);
        bond.markExpired(id);
        assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.Settled));
        assertEq(bond.creditOf(address(0), counterparty), 0.5 ether);
        assertEq(bond.creditOf(address(0), company), 0.5 ether);
    }

    function test_requiredByMinusOneActionIsTimelyAndCannotBeSlashed() external {
        (
            uint256 id,
            AccountabilityBondV3Safe.Terms memory terms,
            AccountabilityBondV3Safe.ObjectiveEvidence memory evidence
        ) = _activeEvidence(5, 1 ether);
        vm.warp(evidence.requiredBy - 1);
        vm.prank(company);
        canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId);
        vm.warp(evidence.requiredBy);
        evidence.recordedAt = evidence.requiredBy - 1;
        evidence.observedAt = evidence.requiredBy;

        vm.expectRevert(AccountabilityBondV3Safe.InvalidEvidence.selector);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        vm.warp(terms.activePeriodEnd);
        bond.markExpired(id);
        bond.forceExit(id);
        assertEq(bond.creditOf(address(0), company), 1 ether);
    }

    function test_requiredBySameBlockEvidenceFirstThenTimelyActionDismissesSlash() external {
        (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(6, 1 ether);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        vm.prank(company);
        canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId);

        AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(id);
        vm.warp(state.responseDeadline);
        bond.advanceObjectiveEvidence(id);
        assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.Cancelled));
        assertEq(bond.creditOf(address(0), counterparty), 0);
        assertEq(bond.creditOf(address(0), company), 1 ether);
    }

    function test_requiredBySameBlockActionFirstRejectsAbsenceEvidence() external {
        (
            uint256 id,
            AccountabilityBondV3Safe.Terms memory terms,
            AccountabilityBondV3Safe.ObjectiveEvidence memory evidence
        ) = _activeEvidence(7, 1 ether);
        vm.prank(company);
        canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId);

        vm.expectRevert(AccountabilityBondV3Safe.CanonicalSourceMismatch.selector);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        vm.warp(terms.activePeriodEnd);
        bond.markExpired(id);
        bond.forceExit(id);
        assertEq(bond.creditOf(address(0), company), 1 ether);
    }

    function test_requiredByPlusOneLateActionRemainsSlashableInEitherOrdering() external {
        (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(8, 1 ether);
        vm.warp(evidence.requiredBy + 1);
        vm.prank(company);
        canonicalSource.record(evidence.canonicalSourceId, evidence.obligationId);
        evidence.recordedAt = uint64(block.timestamp);
        evidence.observedAt = uint64(block.timestamp);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        _completeEnforcement(id);
        assertEq(bond.creditOf(address(0), counterparty), 0.5 ether);
        assertEq(bond.creditOf(address(0), company), 0.5 ether);
    }

    function test_pauseAtRequestedStateCannotEraseAcceptedSlashLiability() external {
        (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(9, 1 ether);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        _pauseAndFinalizeAtEnd(id);
    }

    function test_pauseAtFinalizableStateCannotEraseAcceptedSlashLiability() external {
        (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(10, 1 ether);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(id);
        vm.warp(state.responseDeadline);
        bond.advanceObjectiveEvidence(id);
        _pauseAndFinalizeAtEnd(id);
    }

    function test_pauseAtFrozenStateCannotEraseAcceptedSlashLiability() external {
        (uint256 id,, AccountabilityBondV3Safe.ObjectiveEvidence memory evidence) = _activeEvidence(11, 1 ether);
        bond.submitObjectiveEvidence(evidence, new address[](0), new bytes[](0));
        AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(id);
        vm.warp(state.responseDeadline);
        bond.advanceObjectiveEvidence(id);
        vm.warp(state.challengeDeadline);
        bond.advanceObjectiveEvidence(id);
        _pauseAndFinalizeAtEnd(id);
    }

    function _activeEvidence(uint96 nonce, uint256 stake)
        private
        returns (
            uint256 id,
            AccountabilityBondV3Safe.Terms memory terms,
            AccountabilityBondV3Safe.ObjectiveEvidence memory evidence
        )
    {
        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 id) private {
        AccountabilityBondV3Safe.EnforcementState memory state = bond.enforcementStateOf(id);
        vm.warp(state.responseDeadline);
        bond.advanceObjectiveEvidence(id);
        vm.warp(state.challengeDeadline);
        bond.advanceObjectiveEvidence(id);
        vm.warp(state.decisionDeadline);
        bond.advanceObjectiveEvidence(id);
    }

    function _pauseAndFinalizeAtEnd(uint256 id) private {
        AccountabilityBondV3Safe.BondData memory data = bond.bondOf(id);
        AccountabilityBondV3Safe.EnforcementState memory enforcementState = bond.enforcementStateOf(id);
        vm.prank(owner);
        bond.pause();
        if (data.state == BondStateV3.State.SettlementRequested) {
            vm.warp(enforcementState.responseDeadline);
            vm.expectRevert(AccountabilityBondV3Safe.Paused.selector);
            bond.advanceObjectiveEvidence(id);
        } else if (data.state == BondStateV3.State.Finalizable) {
            vm.warp(enforcementState.challengeDeadline);
            vm.expectRevert(AccountabilityBondV3Safe.Paused.selector);
            bond.advanceObjectiveEvidence(id);
        } else {
            vm.warp(enforcementState.decisionDeadline);
            vm.expectRevert(AccountabilityBondV3Safe.Paused.selector);
            bond.advanceObjectiveEvidence(id);
        }
        vm.warp(data.terms.activePeriodEnd);
        bond.markExpired(id);
        assertEq(uint256(bond.bondOf(id).state), uint256(BondStateV3.State.Settled));
        assertEq(bond.creditOf(address(0), counterparty), 0.5 ether);
        assertEq(bond.creditOf(address(0), company), 0.5 ether);
    }
}
