// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Test} from "forge-std/Test.sol";
import {DBISEngineXIndexedLiquidityVault} from "../../contracts/flash/DBISEngineXIndexedLiquidityVault.sol";
import {MockMintableToken} from "../dbis/MockMintableToken.sol";

contract MockEngineXUniswapV3Pool {
    address public immutable token0;
    address public immutable token1;
    uint24 public immutable fee;
    uint160 public sqrtPriceX96;
    int24 public tick;
    uint128 public liquidity;

    constructor(address token0_, address token1_, uint24 fee_) {
        token0 = token0_;
        token1 = token1_;
        fee = fee_;
    }

    function setSlot0(uint160 sqrtPriceX96_, int24 tick_) external {
        sqrtPriceX96 = sqrtPriceX96_;
        tick = tick_;
    }

    function setLiquidity(uint128 liquidity_) external {
        liquidity = liquidity_;
    }

    function slot0() external view returns (uint160, int24, uint16, uint16, uint16, uint8, bool) {
        return (sqrtPriceX96, tick, 0, 0, 0, 0, true);
    }
}

contract DBISEngineXIndexedLiquidityVaultTest is Test {
    MockMintableToken internal cwusdc;
    MockMintableToken internal usdc;
    MockEngineXUniswapV3Pool internal pool;
    DBISEngineXIndexedLiquidityVault internal vault;

    address internal constant RECIPIENT = address(0xD00D);
    uint160 internal constant ONE_TO_ONE_SQRT_PRICE_X96 = 79_228_162_514_264_337_593_543_950_336;
    bytes32 internal constant PROOF_ID = bytes32("indexed-proof");
    bytes32 internal constant SWAP_TX = bytes32(uint256(0xA1));
    bytes32 internal constant LIQUIDITY_TX = bytes32(uint256(0xB1));
    bytes32 internal constant ISO_HASH = bytes32(uint256(0x1001));
    bytes32 internal constant AUDIT_HASH = bytes32(uint256(0x1002));
    bytes32 internal constant PEG_HASH = bytes32(uint256(0x1003));

    function setUp() public {
        cwusdc = new MockMintableToken("Wrapped cWUSDC", "cWUSDC", 6, address(this));
        usdc = new MockMintableToken("USD Coin", "USDC", 6, address(this));
        pool = new MockEngineXUniswapV3Pool(address(cwusdc), address(usdc), 100);
        pool.setSlot0(ONE_TO_ONE_SQRT_PRICE_X96, 0);
        pool.setLiquidity(1_000_000);
        cwusdc.mint(address(pool), 100_000_000);
        usdc.mint(address(pool), 100_000_000);

        vault = new DBISEngineXIndexedLiquidityVault(
            address(cwusdc), address(usdc), address(pool), address(this), 100, 1_000, 1_000_000
        );
    }

    function testRecordIndexedProofAnchorsPublicPoolState() public {
        DBISEngineXIndexedLiquidityVault.IndexedProof memory proof = _proof(PROOF_ID);

        (uint160 sqrtPriceX96, int24 tick, uint128 liquidity) = vault.recordIndexedProof(proof);

        assertEq(sqrtPriceX96, ONE_TO_ONE_SQRT_PRICE_X96, "sqrt price");
        assertEq(tick, 0, "tick");
        assertEq(liquidity, 1_000_000, "liquidity");
        assertTrue(vault.usedProofIds(PROOF_ID), "proof consumed");
    }

    function testRejectsDuplicateProofId() public {
        vault.recordIndexedProof(_proof(PROOF_ID));

        vm.expectRevert(bytes("proof used"));
        vault.recordIndexedProof(_proof(PROOF_ID));
    }

    function testRejectsTickDrift() public {
        pool.setSlot0(ONE_TO_ONE_SQRT_PRICE_X96, 101);

        vm.expectRevert(bytes("tick drift too high"));
        vault.recordIndexedProof(_proof(PROOF_ID));
    }

    function testRejectsInsufficientLiquidity() public {
        pool.setLiquidity(999);

        vm.expectRevert(bytes("insufficient liquidity"));
        vault.recordIndexedProof(_proof(PROOF_ID));
    }

    function testRejectsOversizedProofAmount() public {
        DBISEngineXIndexedLiquidityVault.IndexedProof memory proof = _proof(PROOF_ID);
        proof.exactOutputAmount = 1_000_001;

        vm.expectRevert(bytes("proof amount too high"));
        vault.recordIndexedProof(proof);
    }

    function testOperatorAllowlist() public {
        vault.setOperatorAllowlistEnabled(true);

        vm.expectRevert(bytes("operator not approved"));
        vm.prank(address(0xBEEF));
        vault.recordIndexedProof(_proof(PROOF_ID));

        vault.setOperatorApproved(address(0xBEEF), true);
        vm.prank(address(0xBEEF));
        vault.recordIndexedProof(_proof(PROOF_ID));
        assertTrue(vault.usedProofIds(PROOF_ID), "proof consumed");
    }

    function testPauseBlocksProofs() public {
        vault.pause();

        vm.expectRevert(bytes("paused"));
        vault.recordIndexedProof(_proof(PROOF_ID));
    }

    function _proof(bytes32 proofId) internal pure returns (DBISEngineXIndexedLiquidityVault.IndexedProof memory) {
        return DBISEngineXIndexedLiquidityVault.IndexedProof({
            proofId: proofId,
            publicSwapTxHash: SWAP_TX,
            liquidityTxHash: LIQUIDITY_TX,
            outputRecipient: RECIPIENT,
            exactOutputAmount: 100_000,
            iso20022DocumentHash: ISO_HASH,
            auditEnvelopeHash: AUDIT_HASH,
            pegProofHash: PEG_HASH
        });
    }
}
