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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title LicenseNFT
 * @notice ERC721 token representing government-issued licenses
 * @dev Each license is minted as an NFT with associated metadata
 */
contract LicenseNFT is ERC721, ERC721URIStorage, Ownable {
    uint256 private _nextTokenId;

    // Mapping from request ID to token ID
    mapping(string => uint256) private _requestToToken;

    // Mapping from token ID to request ID
    mapping(uint256 => string) private _tokenToRequest;

    // Mapping to track revoked licenses
    mapping(uint256 => bool) private _revokedTokens;

    // Mapping to store revocation reasons
    mapping(uint256 => string) private _revocationReasons;

    // Mapping to store license metadata URI
    mapping(uint256 => string) private _metadataUris;

    // Events
    event LicenseMinted(
        uint256 indexed tokenId,
        address indexed to,
        string requestId,
        string metadataUri
    );

    event LicenseRevoked(
        uint256 indexed tokenId,
        string reason
    );

    constructor() ERC721("Goa Government License", "GOA-LIC") Ownable(msg.sender) {}

    /**
     * @notice Mint a new license NFT
     * @param to The address to mint the token to
     * @param requestId The associated license request ID
     * @param metadataUri The URI containing license metadata
     * @return tokenId The ID of the newly minted token
     */
    function mint(
        address to,
        string calldata requestId,
        string calldata metadataUri
    ) public onlyOwner returns (uint256) {
        require(bytes(requestId).length > 0, "Request ID required");
        require(_requestToToken[requestId] == 0, "License already minted for this request");

        uint256 tokenId = ++_nextTokenId;

        _safeMint(to, tokenId);
        _setTokenURI(tokenId, metadataUri);

        _requestToToken[requestId] = tokenId;
        _tokenToRequest[tokenId] = requestId;
        _metadataUris[tokenId] = metadataUri;

        emit LicenseMinted(tokenId, to, requestId, metadataUri);

        return tokenId;
    }

    /**
     * @notice Get the token ID for a request
     * @param requestId The license request ID
     * @return The token ID (0 if not found)
     */
    function tokenOfRequest(string calldata requestId) public view returns (uint256) {
        return _requestToToken[requestId];
    }

    /**
     * @notice Get the request ID for a token
     * @param tokenId The token ID
     * @return The request ID
     */
    function requestOfToken(uint256 tokenId) public view returns (string memory) {
        require(_ownerOf(tokenId) != address(0), "Token does not exist");
        return _tokenToRequest[tokenId];
    }

    /**
     * @notice Check if a token exists
     * @param tokenId The token ID to check
     * @return True if the token exists
     */
    function exists(uint256 tokenId) public view returns (bool) {
        return _ownerOf(tokenId) != address(0);
    }

    /**
     * @notice Revoke a license
     * @param tokenId The token ID to revoke
     * @param reason The reason for revocation
     */
    function revoke(uint256 tokenId, string calldata reason) public onlyOwner {
        require(_ownerOf(tokenId) != address(0), "Token does not exist");
        require(!_revokedTokens[tokenId], "License already revoked");

        _revokedTokens[tokenId] = true;
        _revocationReasons[tokenId] = reason;

        emit LicenseRevoked(tokenId, reason);
    }

    /**
     * @notice Check if a license is revoked
     * @param tokenId The token ID to check
     * @return True if the license is revoked
     */
    function isRevoked(uint256 tokenId) public view returns (bool) {
        return _revokedTokens[tokenId];
    }

    /**
     * @notice Get the revocation reason for a license
     * @param tokenId The token ID
     * @return The revocation reason
     */
    function getRevocationReason(uint256 tokenId) public view returns (string memory) {
        return _revocationReasons[tokenId];
    }

    /**
     * @notice Get the metadata URI for a token
     * @param tokenId The token ID
     * @return The metadata URI
     */
    function getMetadata(uint256 tokenId) public view returns (string memory) {
        require(_ownerOf(tokenId) != address(0), "Token does not exist");
        return _metadataUris[tokenId];
    }

    /**
     * @notice Get the total number of minted licenses
     * @return The total count
     */
    function totalSupply() public view returns (uint256) {
        return _nextTokenId;
    }

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

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