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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "../reserve/IReserveSystem.sol";

interface ICWNavBridgeL1 {
    function lockedBalance(address token) external view returns (uint256);
    function totalOutstanding(address token) external view returns (uint256);
}

/**
 * @title CWNavOracle
 * @notice Publishes reserve-backed NAV for canonical c* / global cW supply pairs.
 * @dev Global cW supply is aggregated off-chain across mesh chains and pushed by OPERATOR_ROLE.
 *      On-chain locked/outstanding reads come from CWMultiTokenBridgeL1.
 */
contract CWNavOracle is AccessControl {
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
    uint256 public constant BPS = 10_000;
    uint256 public constant NAV_SCALE = 1e18;

    struct TokenConfig {
        bool enabled;
        address reserveAsset;
    }

    struct NavSnapshot {
        uint256 totalLockedAssets;
        uint256 totalReserveAssets;
        uint256 totalCwSupply;
        uint256 backingRatioBps;
        uint256 navPerShare;
        uint256 chain138CollateralBps;
        uint256 treasuryReservesBps;
        uint256 protocolFeesBps;
        bytes32 attestationHash;
        uint256 updatedAt;
    }

    ICWNavBridgeL1 public bridge;
    IReserveSystem public reserveSystem;

    mapping(address => TokenConfig) public tokenConfigs;
    mapping(address => uint256) public globalCwSupply;
    mapping(address => NavSnapshot) public latestSnapshot;

    event BridgeUpdated(address indexed newBridge);
    event ReserveSystemUpdated(address indexed newReserveSystem);
    event TokenConfigured(address indexed canonicalToken, address indexed reserveAsset);
    event TokenDisabled(address indexed canonicalToken);
    event NavPublished(address indexed canonicalToken, uint256 backingRatioBps, uint256 navPerShare, bytes32 attestationHash);

    error ZeroAddress();
    error TokenNotConfigured();

    constructor(address admin, address bridge_, address reserveSystem_) {
        if (admin == address(0) || bridge_ == address(0)) {
            revert ZeroAddress();
        }
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(OPERATOR_ROLE, admin);
        bridge = ICWNavBridgeL1(bridge_);
        reserveSystem = IReserveSystem(reserveSystem_);
    }

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

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

    function configureToken(address canonicalToken, address reserveAsset) external onlyRole(OPERATOR_ROLE) {
        if (canonicalToken == address(0)) {
            revert ZeroAddress();
        }
        tokenConfigs[canonicalToken] = TokenConfig({enabled: true, reserveAsset: reserveAsset});
        emit TokenConfigured(canonicalToken, reserveAsset);
    }

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

    function publishNav(
        address canonicalToken,
        uint256 globalCwSupplyAmount,
        uint256 chain138CollateralBps,
        uint256 treasuryReservesBps,
        uint256 protocolFeesBps,
        bytes32 attestationHash
    ) external onlyRole(OPERATOR_ROLE) {
        TokenConfig memory config = tokenConfigs[canonicalToken];
        if (!config.enabled) {
            revert TokenNotConfigured();
        }
        require(chain138CollateralBps + treasuryReservesBps + protocolFeesBps == BPS, "CWNavOracle: bad decomposition");

        globalCwSupply[canonicalToken] = globalCwSupplyAmount;

        uint256 locked = bridge.lockedBalance(canonicalToken);
        uint256 reserveBalance = address(reserveSystem) == address(0) || config.reserveAsset == address(0)
            ? 0
            : reserveSystem.getReserveBalance(config.reserveAsset);

        uint256 totalAssets = locked + reserveBalance;
        uint256 backingRatioBps = globalCwSupplyAmount == 0 ? 0 : (totalAssets * BPS) / globalCwSupplyAmount;
        uint256 navPerShareValue = globalCwSupplyAmount == 0 ? 0 : (totalAssets * NAV_SCALE) / globalCwSupplyAmount;

        NavSnapshot memory snap = NavSnapshot({
            totalLockedAssets: locked,
            totalReserveAssets: reserveBalance,
            totalCwSupply: globalCwSupplyAmount,
            backingRatioBps: backingRatioBps,
            navPerShare: navPerShareValue,
            chain138CollateralBps: chain138CollateralBps,
            treasuryReservesBps: treasuryReservesBps,
            protocolFeesBps: protocolFeesBps,
            attestationHash: attestationHash,
            updatedAt: block.timestamp
        });

        latestSnapshot[canonicalToken] = snap;
        emit NavPublished(canonicalToken, backingRatioBps, navPerShareValue, attestationHash);
    }

    function totalLockedAssets(address canonicalToken) external view returns (uint256) {
        return bridge.lockedBalance(canonicalToken);
    }

    function totalReserveAssets(address canonicalToken) external view returns (uint256) {
        TokenConfig memory config = tokenConfigs[canonicalToken];
        if (!config.enabled || address(reserveSystem) == address(0) || config.reserveAsset == address(0)) {
            return 0;
        }
        return reserveSystem.getReserveBalance(config.reserveAsset);
    }

    function totalCWUSupply(address canonicalToken) external view returns (uint256) {
        return globalCwSupply[canonicalToken];
    }

    function backingRatio(address canonicalToken) external view returns (uint256 backingRatioBps) {
        return latestSnapshot[canonicalToken].backingRatioBps;
    }

    function navPerShare(address canonicalToken) external view returns (uint256) {
        return latestSnapshot[canonicalToken].navPerShare;
    }

    function getNavSnapshot(address canonicalToken) external view returns (NavSnapshot memory) {
        return latestSnapshot[canonicalToken];
    }
}
