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

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

contract DBISEngineXXautUsdcBorrowVaultTest is Test {
    MockMintableToken internal xaut;
    MockMintableToken internal usdc;
    MockMintableToken internal cwusdc;
    DBISEngineXXautUsdcBorrowVault internal vault;

    address internal constant BORROWER = address(0xB0B);
    address internal constant LENDER = address(0x1EAD);
    address internal constant LIQUIDATOR = address(0xA11CE);
    bytes32 internal constant PRICE_SOURCE_HASH = bytes32(uint256(0x5052494345));
    bytes32 internal constant SWAP_TX = bytes32(uint256(0x51574150));
    bytes32 internal constant ISO_HASH = bytes32(uint256(0x150));
    bytes32 internal constant AUDIT_HASH = bytes32(uint256(0xA0017));
    bytes32 internal constant PEG_HASH = bytes32(uint256(0x9E6));

    uint256 internal constant XAUT_PRICE6 = 3_226_640_000;
    uint256 internal constant LTV_BPS = 7_500;
    uint256 internal constant LIQUIDATION_THRESHOLD_BPS = 8_000;
    uint256 internal constant MIN_HEALTH_FACTOR_BPS = 11_000;
    uint256 internal constant LIQUIDATION_BONUS_BPS = 500;
    uint256 internal constant LENDER_USDC = 5_000_000_000;

    event CwusdcSourcedRepay(
        address indexed account,
        address indexed payer,
        uint256 amount,
        bytes32 indexed publicSwapTxHash,
        bytes32 iso20022DocumentHash,
        bytes32 auditEnvelopeHash,
        bytes32 pegProofHash
    );

    function setUp() public {
        xaut = new MockMintableToken("Tether Gold", "XAUt", 6, address(this));
        usdc = new MockMintableToken("USD Coin", "USDC", 6, address(this));
        cwusdc = new MockMintableToken("Wrapped cWUSDC", "cWUSDC", 6, address(this));

        vault = new DBISEngineXXautUsdcBorrowVault(
            address(xaut),
            address(usdc),
            address(cwusdc),
            address(this),
            XAUT_PRICE6,
            LTV_BPS,
            LIQUIDATION_THRESHOLD_BPS,
            MIN_HEALTH_FACTOR_BPS,
            LIQUIDATION_BONUS_BPS,
            0,
            PRICE_SOURCE_HASH
        );

        usdc.mint(LENDER, LENDER_USDC);
        vm.startPrank(LENDER);
        usdc.approve(address(vault), type(uint256).max);
        vault.fundLender(LENDER_USDC);
        vm.stopPrank();

        xaut.mint(BORROWER, 1_000_000);
        usdc.mint(BORROWER, 1_000_000_000);
        vm.startPrank(BORROWER);
        xaut.approve(address(vault), type(uint256).max);
        usdc.approve(address(vault), type(uint256).max);
        vm.stopPrank();

        usdc.mint(LIQUIDATOR, 1_000_000_000);
        vm.prank(LIQUIDATOR);
        usdc.approve(address(vault), type(uint256).max);
    }

    function testBorrowRepayAndWithdrawCollateral() public {
        vm.startPrank(BORROWER);
        vault.supplyCollateral(1_000_000);
        assertEq(vault.collateralValueUsd6(BORROWER), XAUT_PRICE6, "1 XAUt value");

        vault.borrowUsdc(2_000_000_000, BORROWER);
        assertEq(usdc.balanceOf(BORROWER), 3_000_000_000, "borrowed USDC");
        assertEq(vault.lenderUsdcAvailable(), 3_000_000_000, "lender bucket lent out");
        assertEq(vault.healthFactorBps(BORROWER), 12_906, "health factor");

        vault.repayUsdc(2_000_000_000);
        vault.withdrawCollateral(1_000_000, BORROWER);
        vm.stopPrank();

        (uint256 collateral, uint256 debt) = vault.positions(BORROWER);
        assertEq(collateral, 0, "collateral closed");
        assertEq(debt, 0, "debt closed");
        assertEq(xaut.balanceOf(BORROWER), 1_000_000, "xaut returned");
        assertEq(vault.lenderUsdcAvailable(), LENDER_USDC, "lender restored");
    }

    function testBorrowRejectsDebtAboveEffectiveCollateralLimit() public {
        vm.startPrank(BORROWER);
        vault.supplyCollateral(1_000_000);

        vm.expectRevert(bytes("exceeds collateral"));
        vault.borrowUsdc(2_400_000_000, BORROWER);
        vm.stopPrank();
    }

    function testBorrowRejectsGlobalBorrowCap() public {
        vault.setRiskParams(LTV_BPS, LIQUIDATION_THRESHOLD_BPS, MIN_HEALTH_FACTOR_BPS, LIQUIDATION_BONUS_BPS, 1_000_000_000);

        vm.startPrank(BORROWER);
        vault.supplyCollateral(1_000_000);

        vm.expectRevert(bytes("max borrow exceeded"));
        vault.borrowUsdc(1_000_000_001, BORROWER);
        vm.stopPrank();
    }

    function testRepayFromCwusdcProofStillSettlesInUsdcAndAnchorsHashes() public {
        vm.startPrank(BORROWER);
        vault.supplyCollateral(1_000_000);
        vault.borrowUsdc(1_000_000_000, BORROWER);

        vm.expectEmit(true, true, true, true, address(vault));
        emit CwusdcSourcedRepay(BORROWER, BORROWER, 250_000_000, SWAP_TX, ISO_HASH, AUDIT_HASH, PEG_HASH);
        vault.repayUsdcFromCwusdcProof(250_000_000, SWAP_TX, ISO_HASH, AUDIT_HASH, PEG_HASH);
        vm.stopPrank();

        (, uint256 debt) = vault.positions(BORROWER);
        assertEq(debt, 750_000_000, "debt reduced");
        assertEq(vault.totalCwusdcProofRepayUsdc(), 250_000_000, "proof-sourced repay counter");
    }

    function testLiquidationAfterPriceDrop() public {
        vm.startPrank(BORROWER);
        vault.supplyCollateral(1_000_000);
        vault.borrowUsdc(2_000_000_000, BORROWER);
        vm.stopPrank();

        vault.setXautUsdPrice6(2_000_000_000, bytes32(uint256(0x44524f50)));
        assertEq(vault.healthFactorBps(BORROWER), 8_000, "unhealthy after price drop");

        vm.prank(LIQUIDATOR);
        uint256 seized = vault.liquidate(BORROWER, 100_000_000);

        assertEq(seized, 52_500, "5 percent bonus on 0.05 XAUt");
        assertEq(xaut.balanceOf(LIQUIDATOR), 52_500, "liquidator receives XAUt");
        (, uint256 debt) = vault.positions(BORROWER);
        assertEq(debt, 1_900_000_000, "debt after partial liquidation");
    }

    function testOwnerCanWithdrawOnlyUnborrowedLenderUsdc() public {
        vm.prank(BORROWER);
        vault.supplyCollateral(1_000_000);
        vm.prank(BORROWER);
        vault.borrowUsdc(500_000_000, BORROWER);

        vault.withdrawLenderUsdc(address(this), 4_500_000_000);
        assertEq(vault.lenderUsdcAvailable(), 0, "available bucket withdrawn");

        vm.expectRevert(bytes("insufficient lender usdc"));
        vault.withdrawLenderUsdc(address(this), 1);
    }

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

        vm.expectRevert(bytes("paused"));
        vm.prank(BORROWER);
        vault.supplyCollateral(1);

        vault.unpause();
        vm.prank(BORROWER);
        vault.supplyCollateral(1);
    }
}
