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

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "./YTAssetVault.sol";

contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
    
    error InvalidAddress();
    error VaultNotExists();
    error InvalidHardCap();
    
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }
    
    address public vaultImplementation;
    address[] public allVaults;
    mapping(address => bool) public isVault;
    uint256 public defaultHardCap;
    
    event VaultCreated(
        address indexed vault,
        address indexed manager,
        string name,
        string symbol,
        uint256 hardCap,
        uint256 index
    );
    event VaultImplementationUpdated(address indexed newImplementation);
    event DefaultHardCapSet(uint256 newDefaultHardCap);
    event HardCapSet(address indexed vault, uint256 newHardCap);
    event PricesUpdated(address indexed vault, uint256 ytPrice);
    event NextRedemptionTimeSet(address indexed vault, uint256 redemptionTime);
    
    function initialize(
        address _vaultImplementation,
        uint256 _defaultHardCap
    ) external initializer {
        if (_vaultImplementation == address(0)) revert InvalidAddress();
        
        __Ownable_init(msg.sender);
        __UUPSUpgradeable_init();
        
        vaultImplementation = _vaultImplementation;
        defaultHardCap = _defaultHardCap;
    }

    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

    function setVaultImplementation(address _newImplementation) external onlyOwner {
        if (_newImplementation == address(0)) revert InvalidAddress();
        vaultImplementation = _newImplementation;
        emit VaultImplementationUpdated(_newImplementation);
    }
    
    function setDefaultHardCap(uint256 _defaultHardCap) external onlyOwner {
        defaultHardCap = _defaultHardCap;
        emit DefaultHardCapSet(_defaultHardCap);
    }
    
    function createVault(
        string memory _name,
        string memory _symbol,
        address _manager,
        uint256 _hardCap,
        address _usdc,
        uint256 _redemptionTime,
        uint256 _initialYtPrice,
        address _usdcPriceFeed
    ) external onlyOwner returns (address vault) {
        if (_manager == address(0)) revert InvalidAddress();
            
        uint256 actualHardCap = _hardCap == 0 ? defaultHardCap : _hardCap;
        
        bytes memory initData = abi.encodeWithSelector(
            YTAssetVault.initialize.selector,
            _name,
            _symbol,
            _manager,
            actualHardCap,
            _usdc,
            _redemptionTime,
            _initialYtPrice,
            _usdcPriceFeed
        );
        
        vault = address(new ERC1967Proxy(vaultImplementation, initData));
        
        allVaults.push(vault);
        isVault[vault] = true;
        
        emit VaultCreated(
            vault,
            _manager,
            _name,
            _symbol,
            actualHardCap,
            allVaults.length - 1
        );
    }
    
    function createVaultBatch(
        string[] memory _names,
        string[] memory _symbols,
        address[] memory _managers,
        uint256[] memory _hardCaps,
        address _usdc,
        uint256[] memory _redemptionTimes,
        uint256[] memory _initialYtPrices,
        address _usdcPriceFeed
    ) external returns (address[] memory vaults) {
        require(
            _names.length == _symbols.length &&
            _names.length == _managers.length &&
            _names.length == _hardCaps.length &&
            _names.length == _redemptionTimes.length &&
            _names.length == _initialYtPrices.length,
            "Length mismatch"
        );
        
        vaults = new address[](_names.length);
        
        for (uint256 i = 0; i < _names.length; i++) {
            vaults[i] = this.createVault(
                _names[i],
                _symbols[i],
                _managers[i],
                _hardCaps[i],
                _usdc,
                _redemptionTimes[i],
                _initialYtPrices[i],
                _usdcPriceFeed
            );
        }
    }
    
    function setHardCap(address _vault, uint256 _hardCap) external onlyOwner {
        if (!isVault[_vault]) revert VaultNotExists();
        
        YTAssetVault(_vault).setHardCap(_hardCap);
        emit HardCapSet(_vault, _hardCap);
    }
    
    function setHardCapBatch(
        address[] memory _vaults,
        uint256[] memory _hardCaps
    ) external onlyOwner {
        require(_vaults.length == _hardCaps.length, "Length mismatch");
        
        for (uint256 i = 0; i < _vaults.length; i++) {
            if (!isVault[_vaults[i]]) revert VaultNotExists();
            YTAssetVault(_vaults[i]).setHardCap(_hardCaps[i]);
            emit HardCapSet(_vaults[i], _hardCaps[i]);
        }
    }
    
    function setVaultManager(address _vault, address _manager) external onlyOwner {
        if (!isVault[_vault]) revert VaultNotExists();
        if (_manager == address(0)) revert InvalidAddress();
        
        YTAssetVault(_vault).setManager(_manager);
    }
    
    function setVaultNextRedemptionTime(address _vault, uint256 _nextRedemptionTime) external onlyOwner {
        if (!isVault[_vault]) revert VaultNotExists();
        
        YTAssetVault(_vault).setNextRedemptionTime(_nextRedemptionTime);
        emit NextRedemptionTimeSet(_vault, _nextRedemptionTime);
    }
    
    function setVaultNextRedemptionTimeBatch(
        address[] memory _vaults,
        uint256 _nextRedemptionTime
    ) external onlyOwner {
        for (uint256 i = 0; i < _vaults.length; i++) {
            if (!isVault[_vaults[i]]) revert VaultNotExists();
            YTAssetVault(_vaults[i]).setNextRedemptionTime(_nextRedemptionTime);
            emit NextRedemptionTimeSet(_vaults[i], _nextRedemptionTime);
        }
    }
    
    function pauseVault(address _vault) external onlyOwner {
        if (!isVault[_vault]) revert VaultNotExists();
        
        YTAssetVault(_vault).pause();
    }
    
    function unpauseVault(address _vault) external onlyOwner {
        if (!isVault[_vault]) revert VaultNotExists();
        
        YTAssetVault(_vault).unpause();
    }
    
    function pauseVaultBatch(address[] memory _vaults) external onlyOwner {
        for (uint256 i = 0; i < _vaults.length; i++) {
            if (!isVault[_vaults[i]]) revert VaultNotExists();
            YTAssetVault(_vaults[i]).pause();
        }
    }
    
    function unpauseVaultBatch(address[] memory _vaults) external onlyOwner {
        for (uint256 i = 0; i < _vaults.length; i++) {
            if (!isVault[_vaults[i]]) revert VaultNotExists();
            YTAssetVault(_vaults[i]).unpause();
        }
    }
    
    function updateVaultPrices(
        address _vault,
        uint256 _ytPrice
    ) external onlyOwner {
        if (!isVault[_vault]) revert VaultNotExists();
        
        YTAssetVault(_vault).updatePrices(_ytPrice);
        emit PricesUpdated(_vault, _ytPrice);
    }
    
    function updateVaultPricesBatch(
        address[] memory _vaults,
        uint256[] memory _ytPrices
    ) external onlyOwner {
        require(_vaults.length == _ytPrices.length, "Length mismatch");
        
        for (uint256 i = 0; i < _vaults.length; i++) {
            if (!isVault[_vaults[i]]) revert VaultNotExists();
            YTAssetVault(_vaults[i]).updatePrices(_ytPrices[i]);
            emit PricesUpdated(_vaults[i], _ytPrices[i]);
        }
    }
    
    function upgradeVault(address _vault, address _newImplementation) external onlyOwner {
        if (!isVault[_vault]) revert VaultNotExists();
        if (_newImplementation == address(0)) revert InvalidAddress();
        
        YTAssetVault(_vault).upgradeToAndCall(_newImplementation, "");
    }
    
    function upgradeVaultBatch(
        address[] memory _vaults,
        address _newImplementation
    ) external onlyOwner {
        if (_newImplementation == address(0)) revert InvalidAddress();
        
        for (uint256 i = 0; i < _vaults.length; i++) {
            if (!isVault[_vaults[i]]) revert VaultNotExists();
            YTAssetVault(_vaults[i]).upgradeToAndCall(_newImplementation, "");
        }
    }
    
    function getVaultCount() external view returns (uint256) {
        return allVaults.length;
    }
    
    function getVaults(uint256 _start, uint256 _end) 
        external 
        view 
        returns (address[] memory vaults) 
    {
        require(_start < _end && _end <= allVaults.length, "Invalid range");
        
        vaults = new address[](_end - _start);
        for (uint256 i = _start; i < _end; i++) {
            vaults[i - _start] = allVaults[i];
        }
    }
    
    function getAllVaults() external view returns (address[] memory) {
        return allVaults;
    }
    
    function getVaultInfo(address _vault) external view returns (
        bool exists,
        uint256 totalAssets,
        uint256 idleAssets,
        uint256 managedAssets,
        uint256 totalSupply,
        uint256 hardCap,
        uint256 usdcPrice,
        uint256 ytPrice,
        uint256 nextRedemptionTime
    ) {
        exists = isVault[_vault];
        if (!exists) return (false, 0, 0, 0, 0, 0, 0, 0, 0);
        (
            totalAssets,
            idleAssets,
            managedAssets,
            totalSupply,
            hardCap,
            usdcPrice,
            ytPrice,
            nextRedemptionTime
        ) = YTAssetVault(_vault).getVaultInfo();
    }
    
    uint256[50] private __gap;
}