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

import {Test} from "forge-std/Test.sol";
import {ReserveSystem} from "../../contracts/reserve/ReserveSystem.sol";
import {IReserveSystem} from "../../contracts/reserve/IReserveSystem.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
}

contract ReserveSystemTest is Test {
    ReserveSystem public reserveSystem;
    MockERC20 public asset1;
    MockERC20 public asset2;

    address public admin;
    address public reserveManager;
    address public priceFeedOperator;
    address public conversionOperator;

    function setUp() public {
        admin = address(0x1);
        reserveManager = address(0x2);
        priceFeedOperator = address(0x3);
        conversionOperator = address(0x4);

        reserveSystem = new ReserveSystem(admin);

        asset1 = new MockERC20("Asset1", "A1");
        asset2 = new MockERC20("Asset2", "A2");

        // Grant roles and add assets (admin has all roles from constructor)
        vm.startPrank(admin);
        reserveSystem.grantRole(reserveSystem.RESERVE_MANAGER_ROLE(), reserveManager);
        reserveSystem.grantRole(reserveSystem.PRICE_FEED_ROLE(), priceFeedOperator);
        reserveSystem.grantRole(reserveSystem.CONVERSION_OPERATOR_ROLE(), conversionOperator);
        reserveSystem.addSupportedAsset(address(asset1), true);
        reserveSystem.addSupportedAsset(address(asset2), true);
        vm.stopPrank();

        // Set up price feeds
        vm.prank(priceFeedOperator);
        reserveSystem.updatePriceFeed(address(asset1), 1000 * 1e18, block.timestamp);

        vm.prank(priceFeedOperator);
        reserveSystem.updatePriceFeed(address(asset2), 2000 * 1e18, block.timestamp);
    }

    function test_depositReserve() public {
        asset1.mint(reserveManager, 1000 * 1e18);

        vm.prank(reserveManager);
        asset1.approve(address(reserveSystem), 1000 * 1e18);

        vm.prank(reserveManager);
        bytes32 reserveId = reserveSystem.depositReserve(address(asset1), 1000 * 1e18);

        assertEq(reserveSystem.getReserveBalance(address(asset1)), 1000 * 1e18);
        assertTrue(reserveId != bytes32(0));
    }

    function test_withdrawReserve() public {
        // First deposit
        asset1.mint(reserveManager, 1000 * 1e18);
        vm.prank(reserveManager);
        asset1.approve(address(reserveSystem), 1000 * 1e18);
        vm.prank(reserveManager);
        reserveSystem.depositReserve(address(asset1), 1000 * 1e18);

        // Then withdraw
        vm.prank(reserveManager);
        bytes32 withdrawalId = reserveSystem.withdrawReserve(address(asset1), 500 * 1e18, reserveManager);

        assertEq(reserveSystem.getReserveBalance(address(asset1)), 500 * 1e18);
        assertEq(asset1.balanceOf(reserveManager), 500 * 1e18);
        assertTrue(withdrawalId != bytes32(0));
    }

    function test_convertAssets() public {
        // Set up reserves
        asset1.mint(reserveManager, 1000 * 1e18);
        asset2.mint(reserveManager, 1000 * 1e18);

        vm.startPrank(reserveManager);
        asset1.approve(address(reserveSystem), 1000 * 1e18);
        asset2.approve(address(reserveSystem), 1000 * 1e18);
        reserveSystem.depositReserve(address(asset1), 1000 * 1e18);
        reserveSystem.depositReserve(address(asset2), 1000 * 1e18);
        vm.stopPrank();

        // User has asset1 and wants to convert to asset2
        asset1.mint(conversionOperator, 100 * 1e18);
        vm.prank(conversionOperator);
        asset1.approve(address(reserveSystem), 100 * 1e18);

        uint256 asset2BalanceBefore = asset2.balanceOf(conversionOperator);

        vm.prank(conversionOperator);
        (bytes32 conversionId, uint256 targetAmount, uint256 fees) = reserveSystem.convertAssets(
            address(asset1),
            address(asset2),
            100 * 1e18
        );

        assertTrue(conversionId != bytes32(0));
        assertGt(targetAmount, 0);
        assertGt(fees, 0);
        assertEq(asset2.balanceOf(conversionOperator), asset2BalanceBefore + targetAmount);
    }

    function test_calculateConversion() public {
        (uint256 targetAmount, uint256 fees, address[] memory path) = reserveSystem.calculateConversion(
            address(asset1),
            address(asset2),
            100 * 1e18
        );

        // asset1 price: 1000, asset2 price: 2000
        // 100 * 1e18 * 2000 / 1000 = 200 * 1e18
        assertEq(targetAmount, 200 * 1e18);
        assertGt(fees, 0);
        assertEq(path.length, 2);
        assertEq(path[0], address(asset1));
        assertEq(path[1], address(asset2));
    }

    function test_updatePriceFeed() public {
        vm.prank(priceFeedOperator);
        reserveSystem.updatePriceFeed(address(asset1), 1500 * 1e18, block.timestamp);

        (uint256 price, uint256 timestamp) = reserveSystem.getPrice(address(asset1));
        assertEq(price, 1500 * 1e18);
        assertEq(timestamp, block.timestamp);
    }

    function test_redeem() public {
        // Set up reserves
        asset1.mint(reserveManager, 1000 * 1e18);
        vm.prank(reserveManager);
        asset1.approve(address(reserveSystem), 1000 * 1e18);
        vm.prank(reserveManager);
        reserveSystem.depositReserve(address(asset1), 1000 * 1e18);

        address recipient = address(0x5);
        uint256 balanceBefore = asset1.balanceOf(recipient);

        vm.prank(reserveManager);
        bytes32 redemptionId = reserveSystem.redeem(address(asset1), 300 * 1e18, recipient);

        assertEq(reserveSystem.getReserveBalance(address(asset1)), 700 * 1e18);
        assertEq(asset1.balanceOf(recipient), balanceBefore + 300 * 1e18);
        assertTrue(redemptionId != bytes32(0));
    }
}

