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

import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../registry/UniversalAssetRegistry.sol";

/**
 * @title TokenlistGovernanceSync
 * @notice Automatically syncs tokenlist.json changes to on-chain governance
 * @dev Monitors tokenlist versions and creates proposals for changes
 */
contract TokenlistGovernanceSync is 
    Initializable,
    AccessControlUpgradeable,
    UUPSUpgradeable
{
    bytes32 public constant TOKENLIST_MANAGER_ROLE = keccak256("TOKENLIST_MANAGER_ROLE");
    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");

    struct TokenlistVersion {
        uint256 major;
        uint256 minor;
        uint256 patch;
        string ipfsHash;
        uint256 timestamp;
        bool synced;
    }

    struct AssetMetadata {
        address tokenAddress;
        UniversalAssetRegistry.AssetType assetType;
        UniversalAssetRegistry.ComplianceLevel complianceLevel;
        string name;
        string symbol;
        uint8 decimals;
        string jurisdiction;
        uint8 volatilityScore;
        uint256 minBridgeAmount;
        uint256 maxBridgeAmount;
    }

    struct TokenChange {
        address tokenAddress;
        ChangeType changeType;
        AssetMetadata metadata;
    }

    enum ChangeType {
        Added,
        Removed,
        Modified
    }

    // Storage
    UniversalAssetRegistry public assetRegistry;
    mapping(bytes32 => TokenlistVersion) public versions;
    bytes32 public currentVersion;
    bytes32[] public versionHistory;

    // Events
    event TokenlistUpdated(
        bytes32 indexed versionHash,
        uint256 major,
        uint256 minor,
        uint256 patch,
        string ipfsHash
    );
    
    event AutoProposalCreated(
        bytes32 indexed proposalId,
        address indexed token,
        ChangeType changeType
    );
    
    event VersionSynced(bytes32 indexed versionHash);

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

    function initialize(
        address _assetRegistry,
        address admin
    ) external initializer {
        __AccessControl_init();
        __UUPSUpgradeable_init();
        
        require(_assetRegistry != address(0), "Zero registry");
        
        assetRegistry = UniversalAssetRegistry(_assetRegistry);
        
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(TOKENLIST_MANAGER_ROLE, admin);
        _grantRole(UPGRADER_ROLE, admin);
    }

    function _authorizeUpgrade(address newImplementation)
        internal override onlyRole(UPGRADER_ROLE) {}

    /**
     * @notice Submit new tokenlist version and auto-create proposals
     */
    function submitTokenlistVersion(
        uint256 major,
        uint256 minor,
        uint256 patch,
        string calldata ipfsHash,
        address[] calldata newTokens,
        AssetMetadata[] calldata metadata
    ) external onlyRole(TOKENLIST_MANAGER_ROLE) returns (bytes32[] memory proposalIds) {
        require(newTokens.length == metadata.length, "Length mismatch");
        
        bytes32 versionHash = keccak256(abi.encode(major, minor, patch));
        
        // Store version
        TokenlistVersion storage version = versions[versionHash];
        version.major = major;
        version.minor = minor;
        version.patch = patch;
        version.ipfsHash = ipfsHash;
        version.timestamp = block.timestamp;
        version.synced = false;
        
        versionHistory.push(versionHash);
        currentVersion = versionHash;
        
        emit TokenlistUpdated(versionHash, major, minor, patch, ipfsHash);
        
        // Auto-create proposals for new tokens
        proposalIds = new bytes32[](newTokens.length);
        
        for (uint256 i = 0; i < newTokens.length; i++) {
            proposalIds[i] = _createAssetProposal(newTokens[i], metadata[i]);
            
            emit AutoProposalCreated(
                proposalIds[i],
                newTokens[i],
                ChangeType.Added
            );
        }
        
        return proposalIds;
    }

    /**
     * @notice Create asset proposal in registry
     */
    function _createAssetProposal(
        address token,
        AssetMetadata memory metadata
    ) internal returns (bytes32) {
        return assetRegistry.proposeAsset(
            token,
            metadata.assetType,
            metadata.complianceLevel,
            metadata.name,
            metadata.symbol,
            metadata.decimals,
            metadata.jurisdiction,
            metadata.volatilityScore,
            metadata.minBridgeAmount,
            metadata.maxBridgeAmount
        );
    }

    /**
     * @notice Detect changes between versions
     */
    function detectChanges(
        bytes32 oldVersionHash,
        bytes32 newVersionHash,
        address[] calldata oldTokens,
        address[] calldata newTokens
    ) external pure returns (TokenChange[] memory changes) {
        // Simple diff: tokens in new but not old = added
        // tokens in old but not new = removed
        
        uint256 maxChanges = oldTokens.length + newTokens.length;
        TokenChange[] memory tempChanges = new TokenChange[](maxChanges);
        uint256 changeCount = 0;
        
        // Find added tokens
        for (uint256 i = 0; i < newTokens.length; i++) {
            bool found = false;
            for (uint256 j = 0; j < oldTokens.length; j++) {
                if (newTokens[i] == oldTokens[j]) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                tempChanges[changeCount].tokenAddress = newTokens[i];
                tempChanges[changeCount].changeType = ChangeType.Added;
                changeCount++;
            }
        }
        
        // Find removed tokens
        for (uint256 i = 0; i < oldTokens.length; i++) {
            bool found = false;
            for (uint256 j = 0; j < newTokens.length; j++) {
                if (oldTokens[i] == newTokens[j]) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                tempChanges[changeCount].tokenAddress = oldTokens[i];
                tempChanges[changeCount].changeType = ChangeType.Removed;
                changeCount++;
            }
        }
        
        // Resize array to actual size
        changes = new TokenChange[](changeCount);
        for (uint256 i = 0; i < changeCount; i++) {
            changes[i] = tempChanges[i];
        }
        
        return changes;
    }

    /**
     * @notice Mark version as synced
     */
    function markVersionSynced(bytes32 versionHash) external onlyRole(TOKENLIST_MANAGER_ROLE) {
        require(versions[versionHash].timestamp > 0, "Version not found");
        
        versions[versionHash].synced = true;
        
        emit VersionSynced(versionHash);
    }

    /**
     * @notice Batch create proposals for multiple tokens
     */
    function batchCreateProposals(
        address[] calldata tokens,
        AssetMetadata[] calldata metadata
    ) external onlyRole(TOKENLIST_MANAGER_ROLE) returns (bytes32[] memory proposalIds) {
        require(tokens.length == metadata.length, "Length mismatch");
        
        proposalIds = new bytes32[](tokens.length);
        
        for (uint256 i = 0; i < tokens.length; i++) {
            proposalIds[i] = _createAssetProposal(tokens[i], metadata[i]);
            
            emit AutoProposalCreated(
                proposalIds[i],
                tokens[i],
                ChangeType.Added
            );
        }
        
        return proposalIds;
    }

    // View functions
    
    function getVersion(bytes32 versionHash) external view returns (TokenlistVersion memory) {
        return versions[versionHash];
    }

    function getCurrentVersion() external view returns (TokenlistVersion memory) {
        return versions[currentVersion];
    }

    function getVersionHistory() external view returns (bytes32[] memory) {
        return versionHistory;
    }

    function isVersionSynced(bytes32 versionHash) external view returns (bool) {
        return versions[versionHash].synced;
    }

    function getVersionCount() external view returns (uint256) {
        return versionHistory.length;
    }
}
