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

import {Test} from "forge-std/Test.sol";
import "../../contracts/dex/DODOPMMIntegration.sol";
import "../../contracts/liquidity/providers/DODOPMMProvider.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockProviderERC20 is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        _mint(msg.sender, 1_000_000 ether);
    }
}

contract MockQuotePool {
    address public immutable baseToken;
    address public immutable quoteToken;
    uint256 public immutable midPrice;
    uint256 public immutable baseToQuoteOut;
    uint256 public immutable quoteToBaseOut;

    constructor(
        address baseToken_,
        address quoteToken_,
        uint256 midPrice_,
        uint256 baseToQuoteOut_,
        uint256 quoteToBaseOut_
    ) {
        baseToken = baseToken_;
        quoteToken = quoteToken_;
        midPrice = midPrice_;
        baseToQuoteOut = baseToQuoteOut_;
        quoteToBaseOut = quoteToBaseOut_;
    }

    function _BASE_TOKEN_() external view returns (address) {
        return baseToken;
    }

    function _QUOTE_TOKEN_() external view returns (address) {
        return quoteToken;
    }

    function querySellBase(address, uint256) external view returns (uint256, uint256) {
        return (baseToQuoteOut, 0);
    }

    function querySellQuote(address, uint256) external view returns (uint256, uint256) {
        return (quoteToBaseOut, 0);
    }

    function sellBase(address) external pure returns (uint256) {
        return 0;
    }

    function sellQuote(address) external pure returns (uint256) {
        return 0;
    }

    function buyShares(address) external pure returns (uint256, uint256, uint256) {
        return (0, 0, 0);
    }

    function getVaultReserve() external pure returns (uint256, uint256) {
        return (1_000_000, 1_000_000);
    }

    function getMidPrice() external view returns (uint256) {
        return midPrice;
    }

    function _QUOTE_RESERVE_() external pure returns (uint256) {
        return 1_000_000;
    }

    function _BASE_RESERVE_() external pure returns (uint256) {
        return 1_000_000;
    }
}

contract MockFallbackQuotePool {
    address public immutable baseToken;
    address public immutable quoteToken;
    uint256 public immutable baseReserve;
    uint256 public immutable quoteReserve;

    constructor(
        address baseToken_,
        address quoteToken_,
        uint256 baseReserve_,
        uint256 quoteReserve_
    ) {
        baseToken = baseToken_;
        quoteToken = quoteToken_;
        baseReserve = baseReserve_;
        quoteReserve = quoteReserve_;
    }

    function _BASE_TOKEN_() external view returns (address) {
        return baseToken;
    }

    function _QUOTE_TOKEN_() external view returns (address) {
        return quoteToken;
    }

    function querySellBase(address, uint256) external pure returns (uint256, uint256) {
        revert("base quote disabled");
    }

    function querySellQuote(address, uint256) external pure returns (uint256, uint256) {
        revert("quote quote disabled");
    }

    function sellBase(address) external pure returns (uint256) {
        return 0;
    }

    function sellQuote(address) external pure returns (uint256) {
        return 0;
    }

    function buyShares(address) external pure returns (uint256, uint256, uint256) {
        return (0, 0, 0);
    }

    function getVaultReserve() external view returns (uint256, uint256) {
        return (baseReserve, quoteReserve);
    }

    function getMidPrice() external pure returns (uint256) {
        return 1e18;
    }

    function _QUOTE_RESERVE_() external view returns (uint256) {
        return quoteReserve;
    }

    function _BASE_RESERVE_() external view returns (uint256) {
        return baseReserve;
    }
}

contract DODOPMMProviderTest is Test {
    DODOPMMIntegration internal integration;
    DODOPMMProvider internal provider;
    MockProviderERC20 internal officialUSDT;
    MockProviderERC20 internal officialUSDC;
    MockProviderERC20 internal compliantUSDT;
    MockProviderERC20 internal compliantUSDC;

    address internal constant ADMIN = address(0xAD);
    address internal constant DVM = address(0xD0D0);
    address internal constant DODO_APPROVE = address(0xA11CE);

    function setUp() public {
        officialUSDT = new MockProviderERC20("USDT", "USDT");
        officialUSDC = new MockProviderERC20("USDC", "USDC");
        compliantUSDT = new MockProviderERC20("cUSDT", "cUSDT");
        compliantUSDC = new MockProviderERC20("cUSDC", "cUSDC");

        integration = new DODOPMMIntegration(
            ADMIN,
            DVM,
            DODO_APPROVE,
            address(officialUSDT),
            address(officialUSDC),
            address(compliantUSDT),
            address(compliantUSDC)
        );

        provider = new DODOPMMProvider(address(integration), ADMIN);
    }

    function testGetQuoteUsesPoolQueryForBaseSell() public {
        MockQuotePool pool = new MockQuotePool(
            address(compliantUSDT),
            address(compliantUSDC),
            2e18,
            990_000,
            995_000
        );

        vm.prank(ADMIN);
        integration.importExistingPool(address(pool), address(compliantUSDT), address(compliantUSDC), 3, 1e18, 0.5e18, false);

        vm.startPrank(ADMIN);
        provider.registerPool(address(compliantUSDT), address(compliantUSDC), address(pool));
        provider.registerPool(address(compliantUSDC), address(compliantUSDT), address(pool));
        vm.stopPrank();

        (uint256 amountOut, uint256 slippageBps) = provider.getQuote(
            address(compliantUSDT),
            address(compliantUSDC),
            1_000_000
        );

        assertEq(amountOut, 990_000);
        assertEq(slippageBps, 30);
    }

    function testGetQuoteUsesPoolQueryForQuoteSell() public {
        MockQuotePool pool = new MockQuotePool(
            address(compliantUSDT),
            address(compliantUSDC),
            2e18,
            990_000,
            995_000
        );

        vm.prank(ADMIN);
        integration.importExistingPool(address(pool), address(compliantUSDT), address(compliantUSDC), 3, 1e18, 0.5e18, false);

        vm.startPrank(ADMIN);
        provider.registerPool(address(compliantUSDT), address(compliantUSDC), address(pool));
        provider.registerPool(address(compliantUSDC), address(compliantUSDT), address(pool));
        vm.stopPrank();

        (uint256 amountOut, uint256 slippageBps) = provider.getQuote(
            address(compliantUSDC),
            address(compliantUSDT),
            1_000_000
        );

        assertEq(amountOut, 995_000);
        assertEq(slippageBps, 30);
    }

    function testGetQuoteReturnsZeroForUnsupportedPair() public view {
        (uint256 amountOut, uint256 slippageBps) = provider.getQuote(
            address(compliantUSDT),
            address(officialUSDC),
            1_000_000
        );

        assertEq(amountOut, 0);
        assertEq(slippageBps, 10000);
    }

    function testGetQuoteFallsBackToReservesForBaseSell() public {
        uint256 amountIn = 100_000;
        uint256 lpFeeRate = 30;
        MockFallbackQuotePool pool = new MockFallbackQuotePool(
            address(compliantUSDT),
            address(compliantUSDC),
            1_000_000,
            2_000_000
        );

        vm.prank(ADMIN);
        integration.importExistingPool(address(pool), address(compliantUSDT), address(compliantUSDC), lpFeeRate, 1e18, 0.5e18, false);

        vm.startPrank(ADMIN);
        provider.registerPool(address(compliantUSDT), address(compliantUSDC), address(pool));
        provider.registerPool(address(compliantUSDC), address(compliantUSDT), address(pool));
        vm.stopPrank();

        uint256 netAmountIn = (amountIn * (10_000 - lpFeeRate)) / 10_000;
        uint256 expectedOut = (netAmountIn * 2_000_000) / (1_000_000 + netAmountIn);

        (uint256 amountOut, uint256 slippageBps) = provider.getQuote(
            address(compliantUSDT),
            address(compliantUSDC),
            amountIn
        );

        assertEq(amountOut, expectedOut);
        assertEq(slippageBps, 100);
    }

    function testGetQuoteFallsBackToReservesForQuoteSell() public {
        uint256 amountIn = 100_000;
        uint256 lpFeeRate = 25;
        MockFallbackQuotePool pool = new MockFallbackQuotePool(
            address(compliantUSDT),
            address(compliantUSDC),
            2_500_000,
            1_500_000
        );

        vm.prank(ADMIN);
        integration.importExistingPool(address(pool), address(compliantUSDT), address(compliantUSDC), lpFeeRate, 1e18, 0.5e18, false);

        vm.startPrank(ADMIN);
        provider.registerPool(address(compliantUSDT), address(compliantUSDC), address(pool));
        provider.registerPool(address(compliantUSDC), address(compliantUSDT), address(pool));
        vm.stopPrank();

        uint256 netAmountIn = (amountIn * (10_000 - lpFeeRate)) / 10_000;
        uint256 expectedOut = (netAmountIn * 2_500_000) / (1_500_000 + netAmountIn);

        (uint256 amountOut, uint256 slippageBps) = provider.getQuote(
            address(compliantUSDC),
            address(compliantUSDT),
            amountIn
        );

        assertEq(amountOut, expectedOut);
        assertEq(slippageBps, 100);
    }
}
