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

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

contract YTAssetVault is 
    Initializable, 
    ERC20Upgradeable,
    UUPSUpgradeable,
    ReentrancyGuardUpgradeable,
    PausableUpgradeable 
{
    using SafeERC20 for IERC20;

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

    error Forbidden();
    error HardCapExceeded();
    error InvalidAmount();
    error InvalidHardCap();
    error InvalidPrice();
    error InsufficientUSDC();
    error InsufficientYTA();
    error StillInLockPeriod();
    error RequestNotFound();
    error RequestAlreadyProcessed();
    error InvalidBatchSize();
    error InvalidPriceFeed();
    error InvalidChainlinkPrice();
    
    address public factory;
    address public manager;
    
    uint256 public hardCap;
    uint256 public managedAssets;
    
    address public usdcAddress;
    uint8 public usdcDecimals;
    
    uint256 public ytPrice;
    uint256 public constant PRICE_PRECISION = 1e30;
    
    uint256 public constant CHAINLINK_PRICE_PRECISION = 1e8;
    
    uint256 public nextRedemptionTime;
    AggregatorV3Interface internal usdcPriceFeed;
    
    struct WithdrawRequest {
        address user;
        uint256 ytAmount;
        uint256 usdcAmount;
        uint256 requestTime;
        uint256 queueIndex;
        bool processed;
    }
    
    mapping(uint256 => WithdrawRequest) public withdrawRequests;
    mapping(address => uint256[]) private userRequestIds;
    
    uint256 public requestIdCounter;
    
    uint256 public processedUpToIndex;
    
    uint256 public pendingRequestsCount;
    
    event HardCapSet(uint256 newHardCap);
    event ManagerSet(address indexed newManager);
    event AssetsWithdrawn(address indexed to, uint256 amount);
    event AssetsDeposited(uint256 amount);
    event PriceUpdated(uint256 ytPrice, uint256 timestamp);
    event Buy(address indexed user, uint256 usdcAmount, uint256 ytAmount);
    event Sell(address indexed user, uint256 ytAmount, uint256 usdcAmount);
    event NextRedemptionTimeSet(uint256 newRedemptionTime);
    event WithdrawRequestCreated(uint256 indexed requestId, address indexed user, uint256 ytAmount, uint256 usdcAmount, uint256 queueIndex);
    event WithdrawRequestProcessed(uint256 indexed requestId, address indexed user, uint256 usdcAmount);
    event BatchProcessed(uint256 startIndex, uint256 endIndex, uint256 processedCount, uint256 totalUsdcDistributed);
    
    modifier onlyFactory() {
        if (msg.sender != factory) revert Forbidden();
        _;
    }
    
    modifier onlyManager() {
        if (msg.sender != manager) revert Forbidden();
        _;
    }
    
    function initialize(
        string memory _name,
        string memory _symbol,
        address _manager,
        uint256 _hardCap,
        address _usdc,
        uint256 _redemptionTime,
        uint256 _initialYtPrice,
        address _usdcPriceFeed
    ) external initializer {
        __ERC20_init(_name, _symbol);
        __UUPSUpgradeable_init();
        __ReentrancyGuard_init();
        __Pausable_init();

        if (_usdcPriceFeed == address(0)) revert InvalidPriceFeed();
        usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
        usdcAddress = _usdc;
        
        usdcDecimals = IERC20Metadata(usdcAddress).decimals();
        
        factory = msg.sender;
        manager = _manager;
        hardCap = _hardCap;
        
        ytPrice = _initialYtPrice == 0 ? PRICE_PRECISION : _initialYtPrice;
        
        nextRedemptionTime = _redemptionTime;
    }
    
    function _authorizeUpgrade(address newImplementation) internal override onlyFactory {}
    
    function _getUSDCPrice() internal view returns (uint256) {
        (
            /* uint80 roundId */,
            int256 price,
            /* uint256 startedAt */,
            /* uint256 updatedAt */,
            /* uint80 answeredInRound */
        ) = usdcPriceFeed.latestRoundData();
        
        if (price <= 0) revert InvalidChainlinkPrice();
        
        return uint256(price);
    }
    
    function _getPriceConversionFactor() internal view returns (uint256) {
        uint8 ytDecimals = decimals();
        
        uint256 numerator = (10 ** ytDecimals) * PRICE_PRECISION;
        
        uint256 denominator = (10 ** usdcDecimals) * CHAINLINK_PRICE_PRECISION;
        
        return numerator / denominator;
    }
    
    function setHardCap(uint256 _hardCap) external onlyFactory {
        if (_hardCap < totalSupply()) revert InvalidHardCap();
        hardCap = _hardCap;
        emit HardCapSet(_hardCap);
    }
    
    function setManager(address _manager) external onlyFactory {
        manager = _manager;
        emit ManagerSet(_manager);
    }
    
    function pause() external onlyFactory {
        _pause();
    }
    
    function unpause() external onlyFactory {
        _unpause();
    }
    
    function setNextRedemptionTime(uint256 _nextRedemptionTime) external onlyFactory {
        nextRedemptionTime = _nextRedemptionTime;
        emit NextRedemptionTimeSet(_nextRedemptionTime);
    }
    
    function updatePrices(uint256 _ytPrice) external onlyFactory {
        if (_ytPrice == 0) revert InvalidPrice();
        
        ytPrice = _ytPrice;
        
        emit PriceUpdated(_ytPrice, block.timestamp);
    }
    
    function depositYT(uint256 _usdcAmount) 
        external
        nonReentrant 
        whenNotPaused
        returns (uint256 ytAmount) 
    {
        if (_usdcAmount == 0) revert InvalidAmount();

        uint256 usdcPrice = _getUSDCPrice();
        uint256 conversionFactor = _getPriceConversionFactor();
        
        ytAmount = (_usdcAmount * usdcPrice * conversionFactor) / ytPrice;
        
        if (hardCap > 0 && totalSupply() + ytAmount > hardCap) {
            revert HardCapExceeded();
        }
        
        IERC20(usdcAddress).transferFrom(msg.sender, address(this), _usdcAmount);
        
        _mint(msg.sender, ytAmount);
        
        emit Buy(msg.sender, _usdcAmount, ytAmount);
    }
    
    function withdrawYT(uint256 _ytAmount) 
        external 
        nonReentrant 
        whenNotPaused
        returns (uint256 requestId) 
    {
        if (_ytAmount == 0) revert InvalidAmount();
        if (balanceOf(msg.sender) < _ytAmount) revert InsufficientYTA();
        
        if (block.timestamp < nextRedemptionTime) {
            revert StillInLockPeriod();
        }

        uint256 usdcPrice = _getUSDCPrice();
        uint256 conversionFactor = _getPriceConversionFactor();
        
        uint256 usdcAmount = (_ytAmount * ytPrice) / (usdcPrice * conversionFactor);
        
        _burn(msg.sender, _ytAmount);
        
        requestId = requestIdCounter;
        withdrawRequests[requestId] = WithdrawRequest({
            user: msg.sender,
            ytAmount: _ytAmount,
            usdcAmount: usdcAmount,
            requestTime: block.timestamp,
            queueIndex: requestId,
            processed: false
        });
        
        userRequestIds[msg.sender].push(requestId);
        
        requestIdCounter++;
        
        pendingRequestsCount++;
        
        emit WithdrawRequestCreated(requestId, msg.sender, _ytAmount, usdcAmount, requestId);
    }
    
    function processBatchWithdrawals(uint256 _batchSize) 
        external 
        nonReentrant 
        whenNotPaused
        returns (uint256 processedCount, uint256 totalDistributed) 
    {
        if (msg.sender != manager && msg.sender != factory) {
            revert Forbidden();
        }
        
        if (_batchSize == 0) revert InvalidBatchSize();
        
        uint256 availableUSDC = IERC20(usdcAddress).balanceOf(address(this));
        uint256 startIndex = processedUpToIndex;
        
        for (uint256 i = processedUpToIndex; i < requestIdCounter && processedCount < _batchSize; i++) {
            WithdrawRequest storage request = withdrawRequests[i];
            
            if (request.processed) {
                continue;
            }
            
            if (availableUSDC >= request.usdcAmount) {
                IERC20(usdcAddress).safeTransfer(request.user, request.usdcAmount);
                
                request.processed = true;
                
                availableUSDC -= request.usdcAmount;
                totalDistributed += request.usdcAmount;
                processedCount++;
                
                pendingRequestsCount--;
                
                emit WithdrawRequestProcessed(i, request.user, request.usdcAmount);
            } else {
                break;
            }
        }
        
        if (processedCount > 0) {
            for (uint256 i = processedUpToIndex; i < requestIdCounter; i++) {
                if (!withdrawRequests[i].processed) {
                    processedUpToIndex = i;
                    break;
                }
                if (i == requestIdCounter - 1) {
                    processedUpToIndex = requestIdCounter;
                }
            }
        }
        
        emit BatchProcessed(startIndex, processedUpToIndex, processedCount, totalDistributed);
    }
    
    function getUserRequestIds(address _user) external view returns (uint256[] memory) {
        return userRequestIds[_user];
    }
    
    function getRequestDetails(uint256 _requestId) external view returns (WithdrawRequest memory request) {
        if (_requestId >= requestIdCounter) revert RequestNotFound();
        return withdrawRequests[_requestId];
    }
    
    function getPendingRequestsCount() external view returns (uint256) {
        return pendingRequestsCount;
    }
    
    function getUserPendingRequests(address _user) external view returns (WithdrawRequest[] memory pendingRequests) {
        uint256[] memory requestIds = userRequestIds[_user];
        
        uint256 pendingCount = 0;
        for (uint256 i = 0; i < requestIds.length; i++) {
            if (!withdrawRequests[requestIds[i]].processed) {
                pendingCount++;
            }
        }
        
        pendingRequests = new WithdrawRequest[](pendingCount);
        uint256 index = 0;
        for (uint256 i = 0; i < requestIds.length; i++) {
            uint256 requestId = requestIds[i];
            if (!withdrawRequests[requestId].processed) {
                pendingRequests[index] = withdrawRequests[requestId];
                index++;
            }
        }
    }
    
    function getQueueProgress() external view returns (
        uint256 currentIndex,
        uint256 totalRequests,
        uint256 pendingRequests
    ) {
        currentIndex = processedUpToIndex;
        totalRequests = requestIdCounter;
        pendingRequests = pendingRequestsCount;
    }
    
    function getTimeUntilNextRedemption() external view returns (uint256 remainingTime) {
        if (block.timestamp >= nextRedemptionTime) {
            return 0;
        }
        return nextRedemptionTime - block.timestamp;
    }
    
    function canRedeemNow() external view returns (bool) {
        return block.timestamp >= nextRedemptionTime;
    }
    
    function withdrawForManagement(address _to, uint256 _amount) external onlyManager nonReentrant whenNotPaused {
        if (_amount == 0) revert InvalidAmount();
        
        uint256 availableAssets = IERC20(usdcAddress).balanceOf(address(this));
        if (_amount > availableAssets) revert InvalidAmount();
        
        managedAssets += _amount;
        IERC20(usdcAddress).safeTransfer(_to, _amount);
        
        emit AssetsWithdrawn(_to, _amount);
    }
    
    function depositManagedAssets(uint256 _amount) external onlyManager nonReentrant whenNotPaused {
        if (_amount == 0) revert InvalidAmount();
        
        if (_amount >= managedAssets) {
            managedAssets = 0;
        } else {
            managedAssets -= _amount;
        }
        
        IERC20(usdcAddress).transferFrom(msg.sender, address(this), _amount);
        
        emit AssetsDeposited(_amount);
    }

    function totalAssets() public view returns (uint256) {
        return IERC20(usdcAddress).balanceOf(address(this)) + managedAssets;
    }
    
    function idleAssets() public view returns (uint256) {
        return IERC20(usdcAddress).balanceOf(address(this));
    }
    
    function previewBuy(uint256 _usdcAmount) external view returns (uint256 ytAmount) {
        uint256 usdcPrice = _getUSDCPrice();
        uint256 conversionFactor = _getPriceConversionFactor();
        ytAmount = (_usdcAmount * usdcPrice * conversionFactor) / ytPrice;
    }
    
    function previewSell(uint256 _ytAmount) external view returns (uint256 usdcAmount) {
        uint256 usdcPrice = _getUSDCPrice();
        uint256 conversionFactor = _getPriceConversionFactor();
        usdcAmount = (_ytAmount * ytPrice) / (usdcPrice * conversionFactor);
    }

    function getVaultInfo() external view returns (
        uint256 _totalAssets,
        uint256 _idleAssets,
        uint256 _managedAssets,
        uint256 _totalSupply,
        uint256 _hardCap,
        uint256 _usdcPrice,
        uint256 _ytPrice,
        uint256 _nextRedemptionTime
    ) {
        _usdcPrice = _getUSDCPrice();
        _totalAssets = totalAssets();
        _idleAssets = idleAssets();
        _managedAssets = managedAssets;
        _totalSupply = totalSupply();
        _hardCap = hardCap;
        _ytPrice = ytPrice;
        _nextRedemptionTime = nextRedemptionTime;
    }
    
    uint256[50] private __gap;
}