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

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract EasyTokenDocument is ERC721URIStorage, ERC721Burnable, ERC721Pausable, AccessControl {
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
    bytes32 public constant METADATA_ROLE = keccak256("METADATA_ROLE");
    bytes32 public constant APPRAISER_ROLE = keccak256("APPRAISER_ROLE");

    mapping(address => bool) private _blacklist;
    mapping(uint256 => bytes32) private _documentHash;
    mapping(uint256 => uint256) private _appraisalValue;

    uint256 private _tokenIdCounter;

    event BlacklistUpdated(address indexed account, bool isBlacklisted);
    event DocumentMinted(uint256 indexed tokenId, address indexed to, string uri, bytes32 documentHash, uint256 appraisalValue);
    event AppraisalUpdated(uint256 indexed tokenId, uint256 oldValue, uint256 newValue);
    event TokenURIUpdated(uint256 indexed tokenId, string uri);

    constructor(address admin, string memory name_, string memory symbol_) ERC721(name_, symbol_) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(PAUSER_ROLE, admin);
        _grantRole(MINTER_ROLE, admin);
        _grantRole(METADATA_ROLE, admin);
        _grantRole(APPRAISER_ROLE, admin);
        _grantRole(COMPLIANCE_ROLE, admin);
    }

    function valueDecimals() public pure returns (uint8) {
        return 6;
    }

    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }

    function unpause() external onlyRole(PAUSER_ROLE) {
        _unpause();
    }

    function setBlacklist(address account, bool status) external onlyRole(COMPLIANCE_ROLE) {
        _blacklist[account] = status;
        emit BlacklistUpdated(account, status);
    }

    function isBlacklisted(address account) external view returns (bool) {
        return _blacklist[account];
    }

    function documentHashOf(uint256 tokenId) external view returns (bytes32) {
        return _documentHash[tokenId];
    }

    function appraisalOf(uint256 tokenId) external view returns (uint256) {
        return _appraisalValue[tokenId];
    }

    function safeMint(address to, string memory uri, bytes32 documentHash, uint256 appraisalValue) external onlyRole(MINTER_ROLE) returns (uint256) {
        require(!_blacklist[to], "Blacklisted");
        uint256 tokenId = ++_tokenIdCounter;
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
        _documentHash[tokenId] = documentHash;
        _appraisalValue[tokenId] = appraisalValue;
        emit DocumentMinted(tokenId, to, uri, documentHash, appraisalValue);
        return tokenId;
    }

    function setTokenURI(uint256 tokenId, string memory uri) external onlyRole(METADATA_ROLE) {
        require(_ownerOf(tokenId) != address(0), "Invalid token");
        _setTokenURI(tokenId, uri);
        emit TokenURIUpdated(tokenId, uri);
    }

    function setAppraisal(uint256 tokenId, uint256 newValue) external onlyRole(APPRAISER_ROLE) {
        require(_ownerOf(tokenId) != address(0), "Invalid token");
        uint256 old = _appraisalValue[tokenId];
        _appraisalValue[tokenId] = newValue;
        emit AppraisalUpdated(tokenId, old, newValue);
    }

    function approve(address to, uint256 tokenId) public override(ERC721, IERC721) {
        require(!_blacklist[_msgSender()], "Blacklisted");
        if (to != address(0)) {
            require(!_blacklist[to], "Blacklisted");
        }
        super.approve(to, tokenId);
    }

    function setApprovalForAll(address operator, bool approved) public override(ERC721, IERC721) {
        require(!_blacklist[_msgSender()], "Blacklisted");
        if (approved) {
            require(!_blacklist[operator], "Blacklisted");
        }
        super.setApprovalForAll(operator, approved);
    }

    function _update(address to, uint256 tokenId, address auth) internal override(ERC721, ERC721Pausable) returns (address) {
        address from = _ownerOf(tokenId);
        if (from != address(0)) {
            require(!_blacklist[from], "Blacklisted");
        }
        if (to != address(0)) {
            require(!_blacklist[to], "Blacklisted");
        }
        require(!_blacklist[auth], "Blacklisted");
        address previousOwner = super._update(to, tokenId, auth);
        if (to == address(0)) {
            delete _documentHash[tokenId];
            delete _appraisalValue[tokenId];
        }
        return previousOwner;
    }

    function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage, AccessControl) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
        return super.tokenURI(tokenId);
    }

    function grantComplianceRole(address account) external onlyRole(DEFAULT_ADMIN_ROLE) {
        _grantRole(COMPLIANCE_ROLE, account);
    }
}
