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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./interfaces/IVault.sol";
import "./interfaces/ILedger.sol";
import "./interfaces/IRegulatedEntityRegistry.sol";
import "./interfaces/ICollateralAdapter.sol";
import "./interfaces/IeMoneyJoin.sol";
import "./tokens/DepositToken.sol";
import "./tokens/DebtToken.sol";

/**
 * @title Vault
 * @notice Aave-style vault for deposit, borrow, repay, withdraw operations
 * @dev Each vault is owned by a regulated entity
 */
contract Vault is IVault, AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

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

    address public override owner;
    address public entity; // Regulated entity address

    ILedger public ledger;
    IRegulatedEntityRegistry public entityRegistry;
    ICollateralAdapter public collateralAdapter;
    IeMoneyJoin public eMoneyJoin;

    // Token mappings
    mapping(address => address) public depositTokens; // asset => DepositToken
    mapping(address => address) public debtTokens; // currency => DebtToken

    constructor(
        address owner_,
        address entity_,
        address ledger_,
        address entityRegistry_,
        address collateralAdapter_,
        address eMoneyJoin_
    ) {
        owner = owner_;
        entity = entity_;
        ledger = ILedger(ledger_);
        entityRegistry = IRegulatedEntityRegistry(entityRegistry_);
        collateralAdapter = ICollateralAdapter(collateralAdapter_);
        eMoneyJoin = IeMoneyJoin(eMoneyJoin_);

        _grantRole(DEFAULT_ADMIN_ROLE, owner_);
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); // Factory can set tokens during creation
    }

    /**
     * @notice Set deposit token for an asset
     * @param asset Asset address
     * @param depositToken Deposit token address
     */
    function setDepositToken(address asset, address depositToken) external onlyRole(DEFAULT_ADMIN_ROLE) {
        depositTokens[asset] = depositToken;
    }

    /**
     * @notice Set debt token for a currency
     * @param currency Currency address
     * @param debtToken Debt token address
     */
    function setDebtToken(address currency, address debtToken) external onlyRole(DEFAULT_ADMIN_ROLE) {
        debtTokens[currency] = debtToken;
    }

    /**
     * @notice Deposit M0 collateral into vault
     * @param asset Collateral asset address (address(0) for native ETH)
     * @param amount Amount to deposit
     */
    function deposit(address asset, uint256 amount) external payable override nonReentrant {
        require(amount > 0, "Vault: zero amount");
        require(
            entityRegistry.isEligible(entity) && 
            (entityRegistry.isAuthorized(entity, msg.sender) || 
             entityRegistry.isOperator(entity, msg.sender) ||
             msg.sender == owner),
            "Vault: not authorized"
        );

        if (asset == address(0)) {
            require(msg.value == amount, "Vault: value mismatch");
        } else {
            require(msg.value == 0, "Vault: unexpected ETH");
            IERC20(asset).safeTransferFrom(msg.sender, address(collateralAdapter), amount);
        }

        // Deposit via adapter
        collateralAdapter.deposit{value: asset == address(0) ? amount : 0}(address(this), asset, amount);

        // Mint deposit token
        address depositToken = depositTokens[asset];
        if (depositToken != address(0)) {
            DepositToken(depositToken).mint(msg.sender, amount);
        }

        emit Deposited(asset, amount, msg.sender);
    }

    /**
     * @notice Borrow eMoney against collateral
     * @param currency eMoney currency address
     * @param amount Amount to borrow
     */
    function borrow(address currency, uint256 amount) external override nonReentrant {
        require(amount > 0, "Vault: zero amount");
        require(
            entityRegistry.isEligible(entity) && 
            (entityRegistry.isAuthorized(entity, msg.sender) || 
             entityRegistry.isOperator(entity, msg.sender) ||
             msg.sender == owner),
            "Vault: not authorized"
        );

        // Check if borrow is allowed
        (bool canBorrow, bytes32 reasonCode) = ledger.canBorrow(address(this), currency, amount);
        require(canBorrow, string(abi.encodePacked("Vault: borrow not allowed: ", reasonCode)));

        // Update debt in ledger
        ledger.modifyDebt(address(this), currency, int256(amount));

        // Mint debt token
        address debtToken = debtTokens[currency];
        if (debtToken != address(0)) {
            DebtToken(debtToken).mint(msg.sender, amount);
        }

        // Mint eMoney to borrower
        eMoneyJoin.mint(currency, msg.sender, amount);

        emit Borrowed(currency, amount, msg.sender);
    }

    /**
     * @notice Repay borrowed eMoney
     * @param currency eMoney currency address
     * @param amount Amount to repay
     */
    function repay(address currency, uint256 amount) external override nonReentrant {
        require(amount > 0, "Vault: zero amount");

        uint256 currentDebt = ledger.debt(address(this), currency);
        require(currentDebt > 0, "Vault: no debt");

        // Burn eMoney from repayer
        eMoneyJoin.burn(currency, msg.sender, amount);

        // Update debt in ledger
        uint256 repayAmount = amount > currentDebt ? currentDebt : amount;
        ledger.modifyDebt(address(this), currency, -int256(repayAmount));

        // Burn debt token
        address debtToken = debtTokens[currency];
        if (debtToken != address(0)) {
            uint256 debtTokenBalance = DebtToken(debtToken).balanceOf(msg.sender);
            uint256 burnAmount = repayAmount > debtTokenBalance ? debtTokenBalance : repayAmount;
            DebtToken(debtToken).burn(msg.sender, burnAmount);
        }

        emit Repaid(currency, repayAmount, msg.sender);
    }

    /**
     * @notice Withdraw collateral from vault
     * @param asset Collateral asset address
     * @param amount Amount to withdraw
     */
    function withdraw(address asset, uint256 amount) external override nonReentrant {
        require(amount > 0, "Vault: zero amount");
        require(
            entityRegistry.isEligible(entity) && 
            (entityRegistry.isAuthorized(entity, msg.sender) || 
             entityRegistry.isOperator(entity, msg.sender) ||
             msg.sender == owner),
            "Vault: not authorized"
        );

        // Check vault health after withdrawal
        uint256 collateralBalance = ledger.collateral(address(this), asset);
        require(collateralBalance >= amount, "Vault: insufficient collateral");

        // Check if withdrawal would make vault unsafe
        // Simplified check - in production would calculate health after withdrawal
        (uint256 healthRatio, , ) = ledger.getVaultHealth(address(this));
        require(healthRatio >= 11000, "Vault: withdrawal would make vault unsafe"); // 110% minimum

        // Burn deposit token
        address depositToken = depositTokens[asset];
        if (depositToken != address(0)) {
            uint256 depositTokenBalance = DepositToken(depositToken).balanceOf(msg.sender);
            require(depositTokenBalance >= amount, "Vault: insufficient deposit tokens");
            DepositToken(depositToken).burn(msg.sender, amount);
        }

        // Withdraw via adapter
        collateralAdapter.withdraw(address(this), asset, amount);

        emit Withdrawn(asset, amount, msg.sender);
    }

    /**
     * @notice Get vault health
     * @return healthRatio Collateralization ratio in basis points
     * @return collateralValue Total collateral value in XAU
     * @return debtValue Total debt value in XAU
     */
    function getHealth() external view override returns (
        uint256 healthRatio,
        uint256 collateralValue,
        uint256 debtValue
    ) {
        return ledger.getVaultHealth(address(this));
    }

    /// @notice Accept ETH from CollateralAdapter withdraw
    receive() external payable {}
}
