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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/ILiquidityProvider.sol";
import "../../dex/DODOPMMIntegration.sol";

/**
 * @title DODOPMMProvider
 * @notice Wrapper for DODO PMM with extended functionality
 * @dev Implements ILiquidityProvider for multi-provider liquidity system
 */
contract DODOPMMProvider is ILiquidityProvider, AccessControl {
    using SafeERC20 for IERC20;

    bytes32 public constant POOL_MANAGER_ROLE = keccak256("POOL_MANAGER_ROLE");

    DODOPMMIntegration public dodoIntegration;
    
    // Pool tracking
    mapping(address => mapping(address => address)) public pools; // tokenIn => tokenOut => pool
    mapping(address => bool) public isKnownPool;

    event PoolCreated(address indexed tokenIn, address indexed tokenOut, address pool);
    event PoolOptimized(address indexed pool, uint256 newK, uint256 newI);

    constructor(address _dodoIntegration, address admin) {
        require(_dodoIntegration != address(0), "Zero address");
        
        dodoIntegration = DODOPMMIntegration(_dodoIntegration);
        
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(POOL_MANAGER_ROLE, admin);
    }

    /**
     * @notice Get quote from DODO PMM
     */
    function getQuote(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external view override returns (uint256 amountOut, uint256 slippageBps) {
        address pool = pools[tokenIn][tokenOut];

        if (pool == address(0)) {
            return (0, 10000); // No pool, 100% slippage
        }

        try dodoIntegration.getPoolConfig(pool) returns (DODOPMMIntegration.PoolConfig memory config) {
            if (tokenIn == config.baseToken && tokenOut == config.quoteToken) {
                try IDODOPMMPool(pool).querySellBase(address(this), amountIn) returns (uint256 quoteAmount, uint256) {
                    return (quoteAmount, 30);
                } catch {
                    return _fallbackReserveQuote(pool, config, true, amountIn);
                }
            }

            if (tokenIn == config.quoteToken && tokenOut == config.baseToken) {
                try IDODOPMMPool(pool).querySellQuote(address(this), amountIn) returns (uint256 baseAmount, uint256) {
                    return (baseAmount, 30);
                } catch {
                    return _fallbackReserveQuote(pool, config, false, amountIn);
                }
            }

            return (0, 10000);
        } catch {
            return (0, 10000);
        }
    }

    function _fallbackReserveQuote(
        address pool,
        DODOPMMIntegration.PoolConfig memory config,
        bool sellBase,
        uint256 amountIn
    ) internal view returns (uint256 amountOut, uint256 slippageBps) {
        try IDODOPMMPool(pool).getVaultReserve() returns (uint256 baseReserve, uint256 quoteReserve) {
            if (baseReserve == 0 || quoteReserve == 0 || amountIn == 0) {
                return (0, 10000);
            }

            uint256 netAmountIn = amountIn;
            if (config.lpFeeRate < 10_000) {
                netAmountIn = (amountIn * (10_000 - config.lpFeeRate)) / 10_000;
            }

            if (netAmountIn == 0) {
                return (0, 10000);
            }

            if (sellBase) {
                amountOut = (netAmountIn * quoteReserve) / (baseReserve + netAmountIn);
            } else {
                amountOut = (netAmountIn * baseReserve) / (quoteReserve + netAmountIn);
            }

            // Treat fallback quotes as conservative / read-only approximations.
            slippageBps = 100;
            return (amountOut, slippageBps);
        } catch {
            return (0, 10000);
        }
    }

    /**
     * @notice Execute swap via DODO PMM
     */
    function executeSwap(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minAmountOut
    ) external override returns (uint256 amountOut) {
        // Get pool for token pair
        address pool = pools[tokenIn][tokenOut];
        require(pool != address(0), "Pool not found");
        
        // Transfer tokens from caller
        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
        
        // Route to appropriate swap method: use dedicated methods for the 6 legacy pairs,
        // otherwise use generic swapExactIn for full-mesh routing (any registered pool).
        if (tokenIn == dodoIntegration.compliantUSDT() && tokenOut == dodoIntegration.officialUSDT()) {
            IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
            amountOut = dodoIntegration.swapCUSDTForUSDT(pool, amountIn, minAmountOut);
        } else if (tokenIn == dodoIntegration.officialUSDT() && tokenOut == dodoIntegration.compliantUSDT()) {
            IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
            amountOut = dodoIntegration.swapUSDTForCUSDT(pool, amountIn, minAmountOut);
        } else if (tokenIn == dodoIntegration.compliantUSDC() && tokenOut == dodoIntegration.officialUSDC()) {
            IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
            amountOut = dodoIntegration.swapCUSDCForUSDC(pool, amountIn, minAmountOut);
        } else if (tokenIn == dodoIntegration.officialUSDC() && tokenOut == dodoIntegration.compliantUSDC()) {
            IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
            amountOut = dodoIntegration.swapUSDCForCUSDC(pool, amountIn, minAmountOut);
        } else if (tokenIn == dodoIntegration.compliantUSDT() && tokenOut == dodoIntegration.compliantUSDC()) {
            IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
            amountOut = dodoIntegration.swapCUSDTForUSDC(pool, amountIn, minAmountOut);
        } else if (tokenIn == dodoIntegration.compliantUSDC() && tokenOut == dodoIntegration.compliantUSDT()) {
            IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
            amountOut = dodoIntegration.swapUSDCForCUSDT(pool, amountIn, minAmountOut);
        } else {
            // Full mesh: any registered pool (c* vs c*, c* vs official, etc.)
            IERC20(tokenIn).approve(address(dodoIntegration), amountIn);
            amountOut = dodoIntegration.swapExactIn(pool, tokenIn, amountIn, minAmountOut);
        }
        
        // Transfer output tokens to caller
        IERC20(tokenOut).safeTransfer(msg.sender, amountOut);
        
        return amountOut;
    }

    /**
     * @notice Check if provider supports token pair
     */
    function supportsTokenPair(
        address tokenIn,
        address tokenOut
    ) external view override returns (bool) {
        return pools[tokenIn][tokenOut] != address(0);
    }

    /**
     * @notice Get provider name
     */
    function providerName() external pure override returns (string memory) {
        return "DODO PMM";
    }

    /**
     * @notice Estimate gas for swap
     */
    function estimateGas(
        address,
        address,
        uint256
    ) external pure override returns (uint256) {
        return 150000; // Typical gas for DODO swap
    }

    /**
     * @notice Ensure pool exists for token pair
     */
    function ensurePoolExists(address tokenIn, address tokenOut) external onlyRole(POOL_MANAGER_ROLE) {
        if (pools[tokenIn][tokenOut] == address(0)) {
            _createOptimalPool(tokenIn, tokenOut);
        }
    }

    /// @dev Default params for stablecoin pairs: 0.03% fee, 1:1 price, k=0.5e18
    uint256 private constant DEFAULT_LP_FEE = 3;
    uint256 private constant DEFAULT_I = 1e18;
    uint256 private constant DEFAULT_K = 0.5e18;

    /**
     * @notice Create optimal DODO pool via DODOPMMIntegration
     * @dev Requires DODOPMMIntegration to grant POOL_MANAGER_ROLE to this contract
     */
    function _createOptimalPool(address tokenIn, address tokenOut) internal {
        address pool = dodoIntegration.createPool(
            tokenIn,
            tokenOut,
            DEFAULT_LP_FEE,
            DEFAULT_I,
            DEFAULT_K,
            false // isOpenTWAP - disable for stablecoins
        );
        pools[tokenIn][tokenOut] = pool;
        pools[tokenOut][tokenIn] = pool;
        isKnownPool[pool] = true;
        emit PoolCreated(tokenIn, tokenOut, pool);
    }

    /**
     * @notice Register existing pool
     */
    function registerPool(
        address tokenIn,
        address tokenOut,
        address pool
    ) external onlyRole(POOL_MANAGER_ROLE) {
        require(pool != address(0), "Zero address");
        
        pools[tokenIn][tokenOut] = pool;
        isKnownPool[pool] = true;
        
        emit PoolCreated(tokenIn, tokenOut, pool);
    }

    /**
     * @notice Optimize pool parameters (K, I)
     * @dev DODO PMM parameters are typically oracle-driven; DVM pools may expose
     *      setK/setI. Emits for off-chain monitoring. If the pool supports
     *      parameter updates, extend this to call the pool's update functions.
     */
    function optimizePoolParameters(
        address pool,
        uint256 newK,
        uint256 newI
    ) external onlyRole(POOL_MANAGER_ROLE) {
        require(isKnownPool[pool], "Unknown pool");
        // DODO DVM/PMM: parameters set at creation; oracle-driven rebalancing.
        // Emit for observability; integrate pool.setK/setI when supported.
        emit PoolOptimized(pool, newK, newI);
    }
}
