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

import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../vendor/openzeppelin/ReentrancyGuardUpgradeable.sol";
import "./interfaces/ILiquidityProvider.sol";

/**
 * @title LiquidityManager
 * @notice Orchestrates liquidity across multiple sources (DODO, Uniswap, Curve)
 * @dev Implements per-asset configuration for PMM usage
 */
contract LiquidityManager is 
    Initializable,
    AccessControlUpgradeable,
    ReentrancyGuardUpgradeable,
    UUPSUpgradeable
{
    using SafeERC20 for IERC20;

    bytes32 public constant LIQUIDITY_ADMIN_ROLE = keccak256("LIQUIDITY_ADMIN_ROLE");
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");

    struct LiquidityConfig {
        uint256 minAmountForPMM;    // Below this, don't use PMM
        uint256 maxSlippageBps;     // Max acceptable slippage
        uint256 timeout;            // Max wait time for liquidity
        bool autoCreate;            // Auto-create pools if needed
        bool enabled;               // PMM enabled for this asset
    }

    struct ProviderInfo {
        address providerContract;
        bool active;
        uint256 priority;           // Higher priority = checked first
        uint256 totalVolume;
        uint256 successfulSwaps;
        uint256 failedSwaps;
    }

    struct LiquidityReservation {
        bytes32 messageId;
        address token;
        uint256 amount;
        uint256 reservedAt;
        bool released;
    }

    // Storage
    mapping(address => LiquidityConfig) public assetConfigs;
    ILiquidityProvider[] public providers;
    mapping(address => ProviderInfo) public providerInfo;
    mapping(bytes32 => LiquidityReservation) public reservations;

    event LiquidityProvided(
        bytes32 indexed messageId,
        address indexed token,
        uint256 amount,
        address provider
    );
    
    event ProviderAdded(address indexed provider, uint256 priority);
    event ProviderRemoved(address indexed provider);
    event AssetConfigured(address indexed token, LiquidityConfig config);
    event LiquidityReserved(bytes32 indexed messageId, address token, uint256 amount);
    event LiquidityReleased(bytes32 indexed messageId);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(address admin) external initializer {
        __AccessControl_init();
        __ReentrancyGuard_init();
        __UUPSUpgradeable_init();
        
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(LIQUIDITY_ADMIN_ROLE, admin);
        _grantRole(UPGRADER_ROLE, admin);
    }

    function _authorizeUpgrade(address newImplementation)
        internal override onlyRole(UPGRADER_ROLE) {}

    /**
     * @notice Get best liquidity quote across all providers
     */
    function getBestQuote(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external view returns (
        address bestProvider,
        uint256 bestAmountOut,
        uint256 bestSlippageBps
    ) {
        uint256 bestAmount = 0;
        uint256 bestSlippage = type(uint256).max;
        
        for (uint256 i = 0; i < providers.length; i++) {
            if (!providerInfo[address(providers[i])].active) continue;
            
            try providers[i].supportsTokenPair(tokenIn, tokenOut) returns (bool supported) {
                if (!supported) continue;
                
                try providers[i].getQuote(tokenIn, tokenOut, amountIn) returns (
                    uint256 amountOut,
                    uint256 slippageBps
                ) {
                    // Select provider with best output and lowest slippage
                    if (amountOut > bestAmount || 
                        (amountOut == bestAmount && slippageBps < bestSlippage)) {
                        bestProvider = address(providers[i]);
                        bestAmount = amountOut;
                        bestSlippage = slippageBps;
                    }
                } catch {
                    // Provider failed, skip
                    continue;
                }
            } catch {
                continue;
            }
        }
        
        return (bestProvider, bestAmount, bestSlippage);
    }

    /**
     * @notice Provide liquidity for bridge operation
     */
    function provideLiquidity(
        address token,
        uint256 amount,
        bytes calldata strategyParams
    ) external nonReentrant returns (uint256 liquidityProvided) {
        LiquidityConfig memory config = assetConfigs[token];
        
        // Check if PMM enabled for this asset
        if (!config.enabled || amount < config.minAmountForPMM) {
            return 0;
        }
        
        // Find best provider
        // In production, this would execute actual liquidity provision
        // For now, return the amount as-is
        
        liquidityProvided = amount;
        
        return liquidityProvided;
    }

    /**
     * @notice Reserve liquidity for pending bridge
     */
    function reserveLiquidity(
        bytes32 messageId,
        address token,
        uint256 amount
    ) external onlyRole(LIQUIDITY_ADMIN_ROLE) {
        require(messageId != bytes32(0), "Invalid message ID");
        require(reservations[messageId].reservedAt == 0, "Already reserved");
        
        reservations[messageId] = LiquidityReservation({
            messageId: messageId,
            token: token,
            amount: amount,
            reservedAt: block.timestamp,
            released: false
        });
        
        emit LiquidityReserved(messageId, token, amount);
    }

    /**
     * @notice Release reserved liquidity
     */
    function releaseLiquidity(bytes32 messageId) external onlyRole(LIQUIDITY_ADMIN_ROLE) {
        LiquidityReservation storage reservation = reservations[messageId];
        require(reservation.reservedAt > 0, "Not reserved");
        require(!reservation.released, "Already released");
        
        reservation.released = true;
        
        emit LiquidityReleased(messageId);
    }

    /**
     * @notice Add liquidity provider
     */
    function addProvider(
        address provider,
        uint256 priority
    ) external onlyRole(LIQUIDITY_ADMIN_ROLE) {
        require(provider != address(0), "Zero address");
        require(!providerInfo[provider].active, "Already added");
        
        providers.push(ILiquidityProvider(provider));
        providerInfo[provider] = ProviderInfo({
            providerContract: provider,
            active: true,
            priority: priority,
            totalVolume: 0,
            successfulSwaps: 0,
            failedSwaps: 0
        });
        
        emit ProviderAdded(provider, priority);
    }

    /**
     * @notice Remove liquidity provider
     */
    function removeProvider(address provider) external onlyRole(LIQUIDITY_ADMIN_ROLE) {
        providerInfo[provider].active = false;
        
        emit ProviderRemoved(provider);
    }

    /**
     * @notice Configure asset liquidity settings
     */
    function configureAsset(
        address token,
        uint256 minAmountForPMM,
        uint256 maxSlippageBps,
        uint256 timeout,
        bool autoCreate,
        bool enabled
    ) external onlyRole(LIQUIDITY_ADMIN_ROLE) {
        assetConfigs[token] = LiquidityConfig({
            minAmountForPMM: minAmountForPMM,
            maxSlippageBps: maxSlippageBps,
            timeout: timeout,
            autoCreate: autoCreate,
            enabled: enabled
        });
        
        emit AssetConfigured(token, assetConfigs[token]);
    }

    // View functions
    
    function getAssetConfig(address token) external view returns (LiquidityConfig memory) {
        return assetConfigs[token];
    }

    function getProviderCount() external view returns (uint256) {
        return providers.length;
    }

    function getProvider(uint256 index) external view returns (address) {
        return address(providers[index]);
    }

    function getReservation(bytes32 messageId) external view returns (LiquidityReservation memory) {
        return reservations[messageId];
    }
}
