// SPDX-License-Identifier: PROPRIETARY AND MIT
// Copyright (c) 2024-2026 Тарабанов Александр Викторович
// All rights reserved.
//
// This software is proprietary and confidential.
// Unauthorized copying, modification, or distribution is prohibited.
//
// For licensing inquiries: info@hb3-accelerator.com
// Website: https://hb3-accelerator.com
// GitHub: https://github.com/VC-HB3-Accelerator

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/**
 * @title HierarchicalVotingModule
 * @dev Модуль для иерархического голосования между DLE
 * 
 * ОСНОВНЫЕ ФУНКЦИИ:
 * - Владение токенами других DLE
 * - Создание предложений для голосования в других DLE
 * - Внутреннее голосование для внешнего голосования
 * - Выполнение внешнего голосования после достижения кворума
 * 
 * БЕЗОПАСНОСТЬ:
 * - Только DLE контракт может выполнять операции
 * - Защита от реентерабельности
 * - Валидация всех входных параметров
 * - Проверка прав через governance
 */
contract HierarchicalVotingModule is ReentrancyGuard {
    using SafeERC20 for IERC20;
    using Address for address;

    // Структура для внешнего голосования
    struct ExternalVotingProposal {
        address targetDLE;           // Адрес целевого DLE
        uint256 targetProposalId;    // ID предложения в целевом DLE
        bool support;               // Поддержка предложения
        string reason;              // Причина голосования
        bool executed;              // Выполнено ли внешнее голосование
        uint256 internalProposalId; // ID внутреннего предложения в DLE
        uint256 votingPower;        // Сила голоса (количество токенов)
        uint256 createdAt;          // Время создания
    }

    // Структура для информации о внешнем DLE
    struct ExternalDLEInfo {
        address dleAddress;         // Адрес DLE
        string name;               // Название DLE
        string symbol;             // Символ токена DLE
        uint256 tokenBalance;      // Количество токенов на балансе
        bool isActive;            // Активен ли DLE
        uint256 addedAt;          // Время добавления
    }

    // Основные переменные
    address public immutable dleContract;  // Адрес основного DLE контракта
    address public treasuryModule; // Адрес TreasuryModule (может быть установлен позже)
    
    // Хранение внешних DLE
    mapping(address => ExternalDLEInfo) public externalDLEs;
    address[] public externalDLEList;
    mapping(address => uint256) public externalDLEIndex;
    
    // Внешние предложения
    mapping(uint256 => ExternalVotingProposal) public externalVotingProposals;
    uint256 public externalProposalCounter;
    
    // Статистика
    uint256 public totalExternalDLEs;
    uint256 public totalExternalProposals;
    uint256 public totalExternalVotes;

    // События
    event TreasuryModuleSet(address indexed treasuryModule, uint256 timestamp);
    event ExternalDLEAdded(
        address indexed dleAddress,
        string name,
        string symbol,
        uint256 tokenBalance,
        uint256 timestamp
    );
    event ExternalDLERemoved(address indexed dleAddress, uint256 timestamp);
    event ExternalVotingProposalCreated(
        uint256 indexed proposalId,
        address indexed targetDLE,
        uint256 targetProposalId,
        bool support,
        string reason,
        uint256 internalProposalId
    );
    event ExternalVoteExecuted(
        uint256 indexed proposalId,
        address indexed targetDLE,
        uint256 targetProposalId,
        bool support,
        uint256 votingPower
    );
    event ExternalDLEBalanceUpdated(
        address indexed dleAddress,
        uint256 oldBalance,
        uint256 newBalance
    );

    // Модификаторы
    modifier onlyDLE() {
        require(msg.sender == dleContract, "Only DLE contract can call this");
        _;
    }

    modifier validExternalDLE(address dleAddress) {
        require(externalDLEs[dleAddress].isActive, "External DLE not active");
        _;
    }

    constructor(address _dleContract) {
        require(_dleContract != address(0), "DLE contract cannot be zero");
        
        dleContract = _dleContract;
        treasuryModule = address(0); // Будет установлен позже через governance
    }

    /**
     * @dev Установить адрес TreasuryModule (только через DLE governance)
     * @param _treasuryModule Адрес TreasuryModule
     */
    function setTreasuryModule(address _treasuryModule) external onlyDLE {
        require(_treasuryModule != address(0), "Treasury module cannot be zero");
        require(_treasuryModule.code.length > 0, "Treasury module contract does not exist");
        
        treasuryModule = _treasuryModule;
        
        emit TreasuryModuleSet(_treasuryModule, block.timestamp);
    }

    /**
     * @dev Добавить внешний DLE (только через DLE governance)
     * @param dleAddress Адрес внешнего DLE
     * @param name Название DLE
     * @param symbol Символ токена DLE
     */
    function addExternalDLE(
        address dleAddress,
        string memory name,
        string memory symbol
    ) external onlyDLE {
        require(dleAddress != address(0), "DLE address cannot be zero");
        require(!externalDLEs[dleAddress].isActive, "External DLE already added");
        require(bytes(name).length > 0, "Name cannot be empty");
        require(bytes(symbol).length > 0, "Symbol cannot be empty");
        require(treasuryModule != address(0), "Treasury module not set");

        // Проверяем, что DLE контракт существует
        require(dleAddress.code.length > 0, "DLE contract does not exist");

        // Получаем баланс токенов этого DLE в TreasuryModule
        uint256 tokenBalance = IERC20(dleAddress).balanceOf(treasuryModule);

        externalDLEs[dleAddress] = ExternalDLEInfo({
            dleAddress: dleAddress,
            name: name,
            symbol: symbol,
            tokenBalance: tokenBalance,
            isActive: true,
            addedAt: block.timestamp
        });

        externalDLEList.push(dleAddress);
        externalDLEIndex[dleAddress] = externalDLEList.length - 1;
        totalExternalDLEs++;

        emit ExternalDLEAdded(dleAddress, name, symbol, tokenBalance, block.timestamp);
    }

    /**
     * @dev Удалить внешний DLE (только через DLE governance)
     * @param dleAddress Адрес внешнего DLE
     */
    function removeExternalDLE(address dleAddress) external onlyDLE validExternalDLE(dleAddress) {
        require(externalDLEs[dleAddress].tokenBalance == 0, "Token balance must be zero");

        // Удаляем из массива
        uint256 index = externalDLEIndex[dleAddress];
        uint256 lastIndex = externalDLEList.length - 1;
        
        if (index != lastIndex) {
            address lastDLE = externalDLEList[lastIndex];
            externalDLEList[index] = lastDLE;
            externalDLEIndex[lastDLE] = index;
        }
        
        externalDLEList.pop();
        delete externalDLEIndex[dleAddress];
        delete externalDLEs[dleAddress];
        totalExternalDLEs--;

        emit ExternalDLERemoved(dleAddress, block.timestamp);
    }

    /**
     * @dev Создать предложение для внешнего голосования
     * @param targetDLE Адрес целевого DLE
     * @param targetProposalId ID предложения в целевом DLE
     * @param support Поддержка предложения
     * @param reason Причина голосования
     * @return proposalId ID созданного предложения
     */
    function createExternalVotingProposal(
        address targetDLE,
        uint256 targetProposalId,
        bool support,
        string memory reason
    ) external onlyDLE validExternalDLE(targetDLE) returns (uint256) {
        require(targetProposalId > 0, "Target proposal ID must be positive");
        require(bytes(reason).length > 0, "Reason cannot be empty");

        ExternalDLEInfo memory dleInfo = externalDLEs[targetDLE];
        require(dleInfo.tokenBalance > 0, "No tokens in target DLE");

        // Создаем описание для внутреннего предложения
        string memory description = string(abi.encodePacked(
            "Vote in DLE ", dleInfo.name, " (", dleInfo.symbol, ") on proposal #", 
            _toString(targetProposalId), ": ", reason
        ));

        // Создаем внутреннее предложение через DLE
        // Это требует интеграции с DLE контрактом
        uint256 internalProposalId = _createInternalProposal(description);

        uint256 proposalId = externalProposalCounter++;
        externalVotingProposals[proposalId] = ExternalVotingProposal({
            targetDLE: targetDLE,
            targetProposalId: targetProposalId,
            support: support,
            reason: reason,
            executed: false,
            internalProposalId: internalProposalId,
            votingPower: dleInfo.tokenBalance,
            createdAt: block.timestamp
        });

        totalExternalProposals++;

        emit ExternalVotingProposalCreated(
            proposalId,
            targetDLE,
            targetProposalId,
            support,
            reason,
            internalProposalId
        );

        return proposalId;
    }

    /**
     * @dev Выполнить внешнее голосование (после прохождения внутреннего предложения)
     * @param proposalId ID внешнего предложения
     */
    function executeExternalVote(uint256 proposalId) external onlyDLE nonReentrant {
        ExternalVotingProposal storage proposal = externalVotingProposals[proposalId];
        require(proposal.targetDLE != address(0), "Proposal not found");
        require(!proposal.executed, "External vote already executed");

        // Проверяем, что внутреннее предложение прошло
        require(_isInternalProposalPassed(proposal.internalProposalId), "Internal proposal not passed");

        // Выполняем голосование в целевом DLE
        _executeVoteInTargetDLE(proposal.targetDLE, proposal.targetProposalId, proposal.support);

        proposal.executed = true;
        totalExternalVotes++;

        emit ExternalVoteExecuted(
            proposalId,
            proposal.targetDLE,
            proposal.targetProposalId,
            proposal.support,
            proposal.votingPower
        );
    }

    /**
     * @dev Обновить баланс токенов внешнего DLE
     * @param dleAddress Адрес внешнего DLE
     */
    function updateExternalDLEBalance(address dleAddress) external onlyDLE validExternalDLE(dleAddress) {
        uint256 oldBalance = externalDLEs[dleAddress].tokenBalance;
        uint256 newBalance = IERC20(dleAddress).balanceOf(treasuryModule);
        
        externalDLEs[dleAddress].tokenBalance = newBalance;
        
        emit ExternalDLEBalanceUpdated(dleAddress, oldBalance, newBalance);
    }

    /**
     * @dev Обновить балансы всех внешних DLE
     */
    function updateAllExternalDLEBalances() external onlyDLE {
        for (uint256 i = 0; i < externalDLEList.length; i++) {
            address dleAddress = externalDLEList[i];
            if (externalDLEs[dleAddress].isActive) {
                uint256 oldBalance = externalDLEs[dleAddress].tokenBalance;
                uint256 newBalance = IERC20(dleAddress).balanceOf(treasuryModule);
                
                externalDLEs[dleAddress].tokenBalance = newBalance;
                
                emit ExternalDLEBalanceUpdated(dleAddress, oldBalance, newBalance);
            }
        }
    }

    // ===== VIEW ФУНКЦИИ =====

    /**
     * @dev Получить информацию о внешнем DLE
     */
    function getExternalDLEInfo(address dleAddress) external view returns (ExternalDLEInfo memory) {
        return externalDLEs[dleAddress];
    }

    /**
     * @dev Получить список всех внешних DLE
     */
    function getAllExternalDLEs() external view returns (address[] memory) {
        return externalDLEList;
    }

    /**
     * @dev Получить активные внешние DLE
     */
    function getActiveExternalDLEs() external view returns (address[] memory) {
        uint256 activeCount = 0;
        
        for (uint256 i = 0; i < externalDLEList.length; i++) {
            if (externalDLEs[externalDLEList[i]].isActive) {
                activeCount++;
            }
        }

        address[] memory activeDLEs = new address[](activeCount);
        uint256 index = 0;
        
        for (uint256 i = 0; i < externalDLEList.length; i++) {
            if (externalDLEs[externalDLEList[i]].isActive) {
                activeDLEs[index] = externalDLEList[i];
                index++;
            }
        }

        return activeDLEs;
    }

    /**
     * @dev Получить информацию о внешнем предложении
     */
    function getExternalVotingProposal(uint256 proposalId) external view returns (ExternalVotingProposal memory) {
        return externalVotingProposals[proposalId];
    }

    /**
     * @dev Получить статистику модуля
     */
    function getModuleStats() external view returns (
        uint256 totalDLEs,
        uint256 totalProposals,
        uint256 totalVotes,
        uint256 activeDLEs
    ) {
        uint256 activeCount = 0;
        for (uint256 i = 0; i < externalDLEList.length; i++) {
            if (externalDLEs[externalDLEList[i]].isActive) {
                activeCount++;
            }
        }

        return (
            totalExternalDLEs,
            totalExternalProposals,
            totalExternalVotes,
            activeCount
        );
    }

    // ===== ВНУТРЕННИЕ ФУНКЦИИ =====

    /**
     * @dev Создать внутреннее предложение в DLE
     * @param description Описание предложения
     * @return proposalId ID созданного предложения
     */
    function _createInternalProposal(string memory description) internal returns (uint256) {
        // Создаем предложение через стандартный интерфейс DLE
        (bool success, bytes memory data) = dleContract.call(
            abi.encodeWithSignature(
                "createProposal(string,uint256,bytes,uint256,uint256[],uint256)",
                description,
                7 days, // 7 дней голосования
                "", // Пустая операция
                block.chainid, // Текущая сеть
                new uint256[](0), // Пустой массив целевых цепочек
                0 // Без timelock
            )
        );
        
        require(success, "Failed to create internal proposal");
        return abi.decode(data, (uint256));
    }

    /**
     * @dev Проверить, прошло ли внутреннее предложение
     * @param proposalId ID внутреннего предложения
     * @return passed Прошло ли предложение
     */
    function _isInternalProposalPassed(uint256 proposalId) internal view returns (bool) {
        (bool success, bytes memory data) = dleContract.staticcall(
            abi.encodeWithSignature("checkProposalResult(uint256)", proposalId)
        );
        
        if (!success) return false;
        (bool passed, bool quorumReached) = abi.decode(data, (bool, bool));
        return passed && quorumReached;
    }

    /**
     * @dev Выполнить голосование в целевом DLE
     * @param targetDLE Адрес целевого DLE
     * @param proposalId ID предложения
     * @param support Поддержка предложения
     */
    function _executeVoteInTargetDLE(
        address targetDLE,
        uint256 proposalId,
        bool support
    ) internal {
        // Выполняем голосование напрямую в целевом DLE
        // Это требует, чтобы целевой DLE имел функцию vote
        (bool success, ) = targetDLE.call(
            abi.encodeWithSignature("vote(uint256,bool)", proposalId, support)
        );
        
        require(success, "Failed to execute vote in target DLE");
    }

    /**
     * @dev Конвертировать uint256 в string
     */
    function _toString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}
