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

import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title ApprovalManager
 * @notice Manages and records approval actions on the blockchain
 * @dev Provides immutable audit trail for license approvals
 */
contract ApprovalManager is Ownable {
    enum ApprovalStatus {
        PENDING,
        APPROVED,
        REJECTED,
        CHANGES_REQUESTED,
        INVALIDATED
    }

    struct Approval {
        bytes32 id;
        string requestId;
        address departmentAddress;
        ApprovalStatus status;
        string remarksHash;
        string[] documentHashes;
        uint256 timestamp;
        bool isValid;
    }

    // Mapping from approval ID to Approval struct
    mapping(bytes32 => Approval) private _approvals;

    // Mapping from request ID to approval IDs
    mapping(string => bytes32[]) private _requestApprovals;

    // Counter for generating unique approval IDs
    uint256 private _approvalCounter;

    // Events
    event ApprovalRecorded(
        bytes32 indexed approvalId,
        string indexed requestId,
        address indexed departmentAddress,
        ApprovalStatus status,
        uint256 timestamp
    );

    event ApprovalInvalidated(
        bytes32 indexed approvalId,
        string reason
    );

    constructor() Ownable(msg.sender) {}

    /**
     * @notice Record an approval action
     * @param requestId The license request ID
     * @param departmentAddress The address of the approving department
     * @param status The approval status
     * @param remarksHash Hash of the approval remarks
     * @param documentHashes Array of document hashes that were reviewed
     * @return approvalId The unique ID of the recorded approval
     */
    function recordApproval(
        string calldata requestId,
        address departmentAddress,
        ApprovalStatus status,
        string calldata remarksHash,
        string[] calldata documentHashes
    ) public onlyOwner returns (bytes32) {
        require(bytes(requestId).length > 0, "Request ID required");
        require(departmentAddress != address(0), "Invalid department address");

        _approvalCounter++;
        bytes32 approvalId = keccak256(
            abi.encodePacked(requestId, departmentAddress, block.timestamp, _approvalCounter)
        );

        Approval storage approval = _approvals[approvalId];
        approval.id = approvalId;
        approval.requestId = requestId;
        approval.departmentAddress = departmentAddress;
        approval.status = status;
        approval.remarksHash = remarksHash;
        approval.timestamp = block.timestamp;
        approval.isValid = true;

        // Copy document hashes using loop (calldata to storage)
        for (uint256 i = 0; i < documentHashes.length; i++) {
            approval.documentHashes.push(documentHashes[i]);
        }

        _requestApprovals[requestId].push(approvalId);

        emit ApprovalRecorded(
            approvalId,
            requestId,
            departmentAddress,
            status,
            block.timestamp
        );

        return approvalId;
    }

    /**
     * @notice Get all approvals for a request
     * @param requestId The license request ID
     * @return Array of Approval structs
     */
    function getRequestApprovals(string calldata requestId)
        public
        view
        returns (Approval[] memory)
    {
        bytes32[] memory approvalIds = _requestApprovals[requestId];
        Approval[] memory approvals = new Approval[](approvalIds.length);

        for (uint256 i = 0; i < approvalIds.length; i++) {
            approvals[i] = _approvals[approvalIds[i]];
        }

        return approvals;
    }

    /**
     * @notice Invalidate an existing approval
     * @param approvalId The approval ID to invalidate
     * @param reason The reason for invalidation
     */
    function invalidateApproval(bytes32 approvalId, string calldata reason)
        public
        onlyOwner
    {
        require(_approvals[approvalId].isValid, "Approval not found or already invalid");

        _approvals[approvalId].isValid = false;
        _approvals[approvalId].status = ApprovalStatus.INVALIDATED;

        emit ApprovalInvalidated(approvalId, reason);
    }

    /**
     * @notice Verify if approval remarks match the stored hash
     * @param approvalId The approval ID
     * @param remarksHash The hash to verify
     * @return True if the hashes match
     */
    function verifyApproval(bytes32 approvalId, string calldata remarksHash)
        public
        view
        returns (bool)
    {
        Approval memory approval = _approvals[approvalId];
        return approval.isValid &&
               keccak256(bytes(approval.remarksHash)) == keccak256(bytes(remarksHash));
    }

    /**
     * @notice Get details of a specific approval
     * @param approvalId The approval ID
     * @return The Approval struct
     */
    function getApprovalDetails(bytes32 approvalId)
        public
        view
        returns (Approval memory)
    {
        require(_approvals[approvalId].timestamp > 0, "Approval not found");
        return _approvals[approvalId];
    }

    /**
     * @notice Get the count of approvals for a request
     * @param requestId The license request ID
     * @return The number of approvals
     */
    function getApprovalCount(string calldata requestId) public view returns (uint256) {
        return _requestApprovals[requestId].length;
    }
}
