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

import {Test} from "forge-std/Test.sol";
import {RailTriggerRegistry} from "@emoney/RailTriggerRegistry.sol";
import {IRailTriggerRegistry} from "@emoney/interfaces/IRailTriggerRegistry.sol";
import {RailTypes} from "@emoney/libraries/RailTypes.sol";

contract RailTriggerRegistryTest is Test {
    RailTriggerRegistry public registry;
    address public admin;
    address public railOperator;
    address public railAdapter;
    address public token;

    function setUp() public {
        admin = address(0x1);
        railOperator = address(0x2);
        railAdapter = address(0x3);
        token = address(0x100);

        registry = new RailTriggerRegistry(admin);

        vm.startPrank(admin);
        registry.grantRole(registry.RAIL_OPERATOR_ROLE(), railOperator);
        registry.grantRole(registry.RAIL_ADAPTER_ROLE(), railAdapter);
        vm.stopPrank();
    }

    function test_createTrigger() public {
        IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
            id: 0,
            rail: RailTypes.Rail.SWIFT,
            msgType: keccak256("pacs.008"),
            accountRefId: keccak256("account1"),
            walletRefId: bytes32(0),
            token: token,
            amount: 1000,
            currencyCode: keccak256("USD"),
            instructionId: keccak256("instruction1"),
            state: RailTypes.State.CREATED,
            createdAt: 0,
            updatedAt: 0
        });

        vm.expectEmit(true, true, false, true);
        emit TriggerCreated(
            0,
            uint8(RailTypes.Rail.SWIFT),
            keccak256("pacs.008"),
            keccak256("instruction1"),
            keccak256("account1"),
            token,
            1000
        );

        vm.prank(railOperator);
        uint256 id = registry.createTrigger(t);

        assertEq(id, 0);
        IRailTriggerRegistry.Trigger memory retrieved = registry.getTrigger(id);
        assertEq(uint8(retrieved.rail), uint8(RailTypes.Rail.SWIFT));
        assertEq(retrieved.msgType, keccak256("pacs.008"));
        assertEq(retrieved.amount, 1000);
        assertEq(uint8(retrieved.state), uint8(RailTypes.State.CREATED));
    }

    function test_createTrigger_duplicateInstructionId() public {
        IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
            id: 0,
            rail: RailTypes.Rail.SWIFT,
            msgType: keccak256("pacs.008"),
            accountRefId: keccak256("account1"),
            walletRefId: bytes32(0),
            token: token,
            amount: 1000,
            currencyCode: keccak256("USD"),
            instructionId: keccak256("instruction1"),
            state: RailTypes.State.CREATED,
            createdAt: 0,
            updatedAt: 0
        });

        vm.prank(railOperator);
        registry.createTrigger(t);

        vm.prank(railOperator);
        vm.expectRevert("RailTriggerRegistry: duplicate instructionId");
        registry.createTrigger(t);
    }

    function test_updateState() public {
        IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
            id: 0,
            rail: RailTypes.Rail.SWIFT,
            msgType: keccak256("pacs.008"),
            accountRefId: keccak256("account1"),
            walletRefId: bytes32(0),
            token: token,
            amount: 1000,
            currencyCode: keccak256("USD"),
            instructionId: keccak256("instruction1"),
            state: RailTypes.State.CREATED,
            createdAt: 0,
            updatedAt: 0
        });

        vm.prank(railOperator);
        uint256 id = registry.createTrigger(t);

        vm.expectEmit(true, false, false, true);
        emit TriggerStateUpdated(id, uint8(RailTypes.State.CREATED), uint8(RailTypes.State.VALIDATED), bytes32(0));

        vm.prank(railAdapter);
        registry.updateState(id, RailTypes.State.VALIDATED, bytes32(0));

        IRailTriggerRegistry.Trigger memory retrieved = registry.getTrigger(id);
        assertEq(uint8(retrieved.state), uint8(RailTypes.State.VALIDATED));
    }

    function test_updateState_invalidTransition() public {
        IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
            id: 0,
            rail: RailTypes.Rail.SWIFT,
            msgType: keccak256("pacs.008"),
            accountRefId: keccak256("account1"),
            walletRefId: bytes32(0),
            token: token,
            amount: 1000,
            currencyCode: keccak256("USD"),
            instructionId: keccak256("instruction1"),
            state: RailTypes.State.CREATED,
            createdAt: 0,
            updatedAt: 0
        });

        vm.prank(railOperator);
        uint256 id = registry.createTrigger(t);

        vm.prank(railAdapter);
        vm.expectRevert("RailTriggerRegistry: invalid state transition");
        registry.updateState(id, RailTypes.State.SETTLED, bytes32(0));
    }

    function test_instructionIdExists() public {
        IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
            id: 0,
            rail: RailTypes.Rail.SWIFT,
            msgType: keccak256("pacs.008"),
            accountRefId: keccak256("account1"),
            walletRefId: bytes32(0),
            token: token,
            amount: 1000,
            currencyCode: keccak256("USD"),
            instructionId: keccak256("instruction1"),
            state: RailTypes.State.CREATED,
            createdAt: 0,
            updatedAt: 0
        });

        assertFalse(registry.instructionIdExists(keccak256("instruction1")));

        vm.prank(railOperator);
        registry.createTrigger(t);

        assertTrue(registry.instructionIdExists(keccak256("instruction1")));
    }
    
    // Helper events for testing (match IRailTriggerRegistry events)
    event TriggerCreated(
        uint256 indexed id,
        uint8 rail,
        bytes32 msgType,
        bytes32 instructionId,
        bytes32 accountRefId,
        address token,
        uint256 amount
    );
    
    event TriggerStateUpdated(uint256 indexed id, uint8 oldState, uint8 newState, bytes32 reason);
}

