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

import "forge-std/Test.sol";
import "../src/CouponBatch.sol";
import "../src/interfaces/ICouponBatch.sol";

contract CouponBatchTest is Test {
    CouponBatch batch;

    address admin = address(1);
    address factory = address(2);
    address burner = address(3);
    address issuer = address(4);
    address user1 = address(5);
    address user2 = address(6);

    bytes32 storeA = keccak256("STORE_A");
    bytes32 storeB = keccak256("STORE_B");

    function setUp() public {
        vm.startPrank(admin);

        batch = new CouponBatch();
        batch.initialize("https://genex.io/api/coupon-batch/{id}.json", admin);

        batch.grantRole(keccak256("FACTORY_ROLE"), factory);
        batch.grantRole(keccak256("BURNER_ROLE"), burner);

        vm.stopPrank();
    }

    // ==========================================
    // 铸造测试
    // ==========================================

    function test_MintCreatesNewType() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 100e6, 1000, config);

        assertEq(typeId, 1);
        assertEq(batch.balanceOf(issuer, typeId), 1000);
        assertEq(batch.totalSupply(typeId), 1000);
        assertEq(batch.getMintedQuantity(typeId), 1000);
    }

    function test_MintMultipleTypes() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.startPrank(factory);
        uint256 typeId1 = batch.mint(issuer, 100e6, 500, config);
        uint256 typeId2 = batch.mint(issuer, 50e6, 2000, config);
        vm.stopPrank();

        assertEq(typeId1, 1);
        assertEq(typeId2, 2);
        assertEq(batch.balanceOf(issuer, typeId1), 500);
        assertEq(batch.balanceOf(issuer, typeId2), 2000);
    }

    function test_MintStoresConfig() public {
        bytes32[] memory stores = new bytes32[](2);
        stores[0] = storeA;
        stores[1] = storeB;

        ICouponBatch.BatchCouponConfig memory config = ICouponBatch.BatchCouponConfig({
            transferable: false,
            maxPrice: 50e6,
            expiryDate: block.timestamp + 90 days,
            minPurchase: 10,
            stackable: true,
            allowedStores: stores,
            issuer: issuer,
            faceValue: 50e6
        });

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 50e6, 100, config);

        ICouponBatch.BatchCouponConfig memory stored = batch.getConfig(typeId);
        assertEq(stored.transferable, false);
        assertEq(stored.maxPrice, 50e6);
        assertEq(stored.minPurchase, 10);
        assertEq(stored.stackable, true);
        assertEq(stored.allowedStores.length, 2);
        assertEq(stored.issuer, issuer);
        assertEq(stored.faceValue, 50e6);
    }

    function test_MintZeroAddressReverts() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        vm.expectRevert("CouponBatch: zero address");
        batch.mint(address(0), 100e6, 1000, config);
    }

    function test_MintZeroFaceValueReverts() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        vm.expectRevert("CouponBatch: zero face value");
        batch.mint(issuer, 0, 1000, config);
    }

    function test_MintZeroQuantityReverts() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        vm.expectRevert("CouponBatch: zero quantity");
        batch.mint(issuer, 100e6, 0, config);
    }

    function test_MintOnlyFactoryRole() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(user1);
        vm.expectRevert();
        batch.mint(issuer, 100e6, 1000, config);
    }

    function test_GetFaceValue() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 250e6, 100, config);

        assertEq(batch.getFaceValue(typeId), 250e6);
    }

    function test_GetConfigNonexistentReverts() public {
        vm.expectRevert("CouponBatch: nonexistent type");
        batch.getConfig(999);
    }

    // ==========================================
    // 销毁测试
    // ==========================================

    function test_BurnByOwner() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 100e6, 1000, config);

        vm.prank(issuer);
        batch.burn(issuer, typeId, 300);

        assertEq(batch.balanceOf(issuer, typeId), 700);
        assertEq(batch.totalSupply(typeId), 700);
    }

    function test_BurnByBurnerRole() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 100e6, 1000, config);

        vm.prank(burner);
        batch.burn(issuer, typeId, 500);

        assertEq(batch.balanceOf(issuer, typeId), 500);
    }

    function test_BurnByApproved() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 100e6, 1000, config);

        vm.prank(issuer);
        batch.setApprovalForAll(user1, true);

        vm.prank(user1);
        batch.burn(issuer, typeId, 200);

        assertEq(batch.balanceOf(issuer, typeId), 800);
    }

    function test_BurnUnauthorizedReverts() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 100e6, 1000, config);

        vm.prank(user1);
        vm.expectRevert("CouponBatch: not authorized to burn");
        batch.burn(issuer, typeId, 100);
    }

    // ==========================================
    // 转让限制测试
    // ==========================================

    function test_TransferableTokenCanTransfer() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
        config.transferable = true;

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 100e6, 100, config);

        vm.prank(issuer);
        batch.safeTransferFrom(issuer, user1, typeId, 50, "");

        assertEq(batch.balanceOf(issuer, typeId), 50);
        assertEq(batch.balanceOf(user1, typeId), 50);
    }

    function test_NonTransferableTokenReverts() public {
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();
        config.transferable = false;

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 100e6, 100, config);

        vm.prank(issuer);
        vm.expectRevert("CouponBatch: non-transferable");
        batch.safeTransferFrom(issuer, user1, typeId, 10, "");
    }

    // ==========================================
    // Fuzz 测试
    // ==========================================

    function testFuzz_MintQuantity(uint256 quantity) public {
        quantity = bound(quantity, 1, 100_000);
        ICouponBatch.BatchCouponConfig memory config = _defaultConfig();

        vm.prank(factory);
        uint256 typeId = batch.mint(issuer, 100e6, quantity, config);

        assertEq(batch.balanceOf(issuer, typeId), quantity);
        assertEq(batch.totalSupply(typeId), quantity);
        assertEq(batch.getMintedQuantity(typeId), quantity);
    }

    // ==========================================
    // Helpers
    // ==========================================

    function _defaultConfig() internal view returns (ICouponBatch.BatchCouponConfig memory) {
        bytes32[] memory stores = new bytes32[](0);
        return ICouponBatch.BatchCouponConfig({
            transferable: true,
            maxPrice: 100e6,
            expiryDate: block.timestamp + 180 days,
            minPurchase: 0,
            stackable: false,
            allowedStores: stores,
            issuer: issuer,
            faceValue: 100e6
        });
    }
}
