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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../../reserve/IReserveSystem.sol";
import "./ICWReserveVerifier.sol";

interface ICWMultiTokenBridgeL1Accounting {
    function supportedCanonicalToken(address token) external view returns (bool);
    function paused(address token) external view returns (bool);
    function lockedBalance(address token) external view returns (uint256);
    function totalOutstanding(address token) external view returns (uint256);
    function outstandingMinted(address token, uint64 destinationChainSelector) external view returns (uint256);
    function maxOutstanding(address token, uint64 destinationChainSelector) external view returns (uint256);
}

interface IOwnableLike {
    function owner() external view returns (address);
}

interface IStablecoinReserveVaultLike {
    function compliantUSDT() external view returns (address);
    function compliantUSDC() external view returns (address);
    function getBackingRatio(address token) external view returns (
        uint256 reserveBalance,
        uint256 tokenSupply,
        uint256 backingRatio
    );
    function checkReserveAdequacy() external view returns (bool usdtAdequate, bool usdcAdequate);
}

/**
 * @title CWReserveVerifier
 * @notice Verifies bridge escrow and canonical reserve backing before allowing new cW minting.
 * @dev Intended for cUSDC/cUSDT -> cWUSDC/cWUSDT hard-peg flows on Chain 138.
 */
contract CWReserveVerifier is AccessControl, ICWReserveVerifier {
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    uint256 public constant HARD_THRESHOLD_BPS = 10000;

    struct TokenConfig {
        bool enabled;
        address reserveAsset;
        bool requireVaultBacking;
        bool requireReserveSystemBalance;
        bool requireTokenOwnerMatchVault;
    }

    struct VerificationStatus {
        bool supportedCanonicalToken;
        bool bridgePaused;
        bool escrowSufficient;
        bool chainCapSufficient;
        bool reserveSystemAdequate;
        bool vaultAdequate;
        bool tokenOwnerMatchesVault;
        bool passes;
        uint256 lockedBalanceAmount;
        uint256 totalOutstandingAmount;
        uint256 chainOutstandingAmount;
        uint256 maxOutstandingAmount;
        uint256 canonicalTotalSupply;
        uint256 reserveSystemBalance;
        uint256 vaultReserveBalance;
        uint256 vaultBackingRatio;
    }

    ICWMultiTokenBridgeL1Accounting public bridge;
    IStablecoinReserveVaultLike public stablecoinReserveVault;
    IReserveSystem public reserveSystem;

    mapping(address => TokenConfig) public tokenConfigs;

    event BridgeUpdated(address indexed newBridge);
    event StablecoinReserveVaultUpdated(address indexed newVault);
    event ReserveSystemUpdated(address indexed newReserveSystem);
    event TokenConfigured(
        address indexed canonicalToken,
        address indexed reserveAsset,
        bool requireVaultBacking,
        bool requireReserveSystemBalance,
        bool requireTokenOwnerMatchVault
    );
    event TokenDisabled(address indexed canonicalToken);

    error ZeroAddress();
    error TokenNotConfigured();
    error VaultRequired();
    error ReserveSystemRequired();
    error UnsupportedCanonicalToken();
    error BridgeTokenPaused();
    error EscrowInvariantViolation();
    error ChainOutstandingCapExceeded();
    error TokenOwnerMismatch();
    error VaultBackingInsufficient();
    error ReserveSystemBackingInsufficient();

    constructor(
        address admin,
        address bridge_,
        address stablecoinReserveVault_,
        address reserveSystem_
    ) {
        if (admin == address(0) || bridge_ == address(0)) {
            revert ZeroAddress();
        }

        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(OPERATOR_ROLE, admin);

        bridge = ICWMultiTokenBridgeL1Accounting(bridge_);
        stablecoinReserveVault = IStablecoinReserveVaultLike(stablecoinReserveVault_);
        reserveSystem = IReserveSystem(reserveSystem_);
    }

    function setBridge(address bridge_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (bridge_ == address(0)) {
            revert ZeroAddress();
        }
        bridge = ICWMultiTokenBridgeL1Accounting(bridge_);
        emit BridgeUpdated(bridge_);
    }

    function setStablecoinReserveVault(address stablecoinReserveVault_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        stablecoinReserveVault = IStablecoinReserveVaultLike(stablecoinReserveVault_);
        emit StablecoinReserveVaultUpdated(stablecoinReserveVault_);
    }

    function setReserveSystem(address reserveSystem_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        reserveSystem = IReserveSystem(reserveSystem_);
        emit ReserveSystemUpdated(reserveSystem_);
    }

    function configureToken(
        address canonicalToken,
        address reserveAsset,
        bool requireVaultBacking,
        bool requireReserveSystemBalance,
        bool requireTokenOwnerMatchVault
    ) external onlyRole(OPERATOR_ROLE) {
        if (canonicalToken == address(0)) {
            revert ZeroAddress();
        }
        if ((requireVaultBacking || requireTokenOwnerMatchVault) && address(stablecoinReserveVault) == address(0)) {
            revert VaultRequired();
        }
        if (requireReserveSystemBalance && address(reserveSystem) == address(0)) {
            revert ReserveSystemRequired();
        }
        if (requireReserveSystemBalance && reserveAsset == address(0)) {
            revert ZeroAddress();
        }

        tokenConfigs[canonicalToken] = TokenConfig({
            enabled: true,
            reserveAsset: reserveAsset,
            requireVaultBacking: requireVaultBacking,
            requireReserveSystemBalance: requireReserveSystemBalance,
            requireTokenOwnerMatchVault: requireTokenOwnerMatchVault
        });

        emit TokenConfigured(
            canonicalToken,
            reserveAsset,
            requireVaultBacking,
            requireReserveSystemBalance,
            requireTokenOwnerMatchVault
        );
    }

    function disableToken(address canonicalToken) external onlyRole(OPERATOR_ROLE) {
        delete tokenConfigs[canonicalToken];
        emit TokenDisabled(canonicalToken);
    }

    function verifyLock(
        address canonicalToken,
        uint64 destinationChainSelector,
        uint256
    ) external view override returns (bool verified) {
        TokenConfig memory config = tokenConfigs[canonicalToken];
        if (!config.enabled) {
            revert TokenNotConfigured();
        }

        VerificationStatus memory status = _buildStatus(canonicalToken, destinationChainSelector, config);

        if (!status.supportedCanonicalToken) {
            revert UnsupportedCanonicalToken();
        }
        if (status.bridgePaused) {
            revert BridgeTokenPaused();
        }
        if (!status.escrowSufficient) {
            revert EscrowInvariantViolation();
        }
        if (!status.chainCapSufficient) {
            revert ChainOutstandingCapExceeded();
        }
        if (config.requireTokenOwnerMatchVault && !status.tokenOwnerMatchesVault) {
            revert TokenOwnerMismatch();
        }
        if (config.requireVaultBacking && !status.vaultAdequate) {
            revert VaultBackingInsufficient();
        }
        if (config.requireReserveSystemBalance && !status.reserveSystemAdequate) {
            revert ReserveSystemBackingInsufficient();
        }

        return true;
    }

    function getVerificationStatus(
        address canonicalToken,
        uint64 destinationChainSelector
    ) external view returns (VerificationStatus memory status) {
        TokenConfig memory config = tokenConfigs[canonicalToken];
        if (!config.enabled) {
            revert TokenNotConfigured();
        }
        return _buildStatus(canonicalToken, destinationChainSelector, config);
    }

    function _buildStatus(
        address canonicalToken,
        uint64 destinationChainSelector,
        TokenConfig memory config
    ) internal view returns (VerificationStatus memory status) {
        status.supportedCanonicalToken = bridge.supportedCanonicalToken(canonicalToken);
        status.bridgePaused = bridge.paused(canonicalToken);
        status.lockedBalanceAmount = bridge.lockedBalance(canonicalToken);
        status.totalOutstandingAmount = bridge.totalOutstanding(canonicalToken);
        status.chainOutstandingAmount = bridge.outstandingMinted(canonicalToken, destinationChainSelector);
        status.maxOutstandingAmount = bridge.maxOutstanding(canonicalToken, destinationChainSelector);
        status.escrowSufficient = status.lockedBalanceAmount >= status.totalOutstandingAmount;
        status.chainCapSufficient = status.maxOutstandingAmount == 0
            || status.chainOutstandingAmount <= status.maxOutstandingAmount;

        status.canonicalTotalSupply = IERC20(canonicalToken).totalSupply();

        status.reserveSystemAdequate = !config.requireReserveSystemBalance;
        if (config.requireReserveSystemBalance) {
            status.reserveSystemBalance = reserveSystem.getReserveBalance(config.reserveAsset);
            status.reserveSystemAdequate = status.reserveSystemBalance >= status.canonicalTotalSupply;
        }

        status.vaultAdequate = !config.requireVaultBacking;
        status.tokenOwnerMatchesVault = !config.requireTokenOwnerMatchVault;
        if (config.requireVaultBacking || config.requireTokenOwnerMatchVault) {
            address vaultAddress = address(stablecoinReserveVault);

            if (config.requireTokenOwnerMatchVault) {
                try IOwnableLike(canonicalToken).owner() returns (address tokenOwner) {
                    status.tokenOwnerMatchesVault = tokenOwner == vaultAddress;
                } catch {
                    status.tokenOwnerMatchesVault = false;
                }
            }

            if (config.requireVaultBacking) {
                bool tokenTrackedByVault;
                bool tokenAdequate;

                try stablecoinReserveVault.compliantUSDT() returns (address compliantUSDT) {
                    if (canonicalToken == compliantUSDT) {
                        tokenTrackedByVault = true;
                        try stablecoinReserveVault.checkReserveAdequacy() returns (bool usdtAdequate, bool) {
                            tokenAdequate = usdtAdequate;
                        } catch {}
                    }
                } catch {}

                if (!tokenTrackedByVault) {
                    try stablecoinReserveVault.compliantUSDC() returns (address compliantUSDC) {
                        if (canonicalToken == compliantUSDC) {
                            tokenTrackedByVault = true;
                            try stablecoinReserveVault.checkReserveAdequacy() returns (bool, bool usdcAdequate) {
                                tokenAdequate = usdcAdequate;
                            } catch {}
                        }
                    } catch {}
                }

                if (tokenTrackedByVault) {
                    try stablecoinReserveVault.getBackingRatio(canonicalToken) returns (
                        uint256 reserveBalance,
                        uint256,
                        uint256 backingRatio
                    ) {
                        status.vaultReserveBalance = reserveBalance;
                        status.vaultBackingRatio = backingRatio;
                        status.vaultAdequate = tokenAdequate
                            && backingRatio >= HARD_THRESHOLD_BPS
                            && reserveBalance >= status.canonicalTotalSupply;
                    } catch {
                        status.vaultAdequate = false;
                    }
                } else {
                    status.vaultAdequate = false;
                }
            }
        }

        status.passes =
            status.supportedCanonicalToken
            && !status.bridgePaused
            && status.escrowSufficient
            && status.chainCapSufficient
            && status.tokenOwnerMatchesVault
            && status.vaultAdequate
            && status.reserveSystemAdequate;
    }
}
