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

import "forge-std/Test.sol";
import "../src/CouponFactory.sol";
import "../src/Coupon.sol";
import "../src/Compliance.sol";
import "../src/interfaces/ICoupon.sol";
import "../src/CouponBatch.sol";
import "../src/interfaces/ICouponBatch.sol";

contract CouponFactoryTest is Test {
    CouponFactory factory;
    Coupon coupon;
    Compliance compliance;
    CouponBatch couponBatch;

    address admin = address(1);
    address issuer = address(2);
    address minter = address(3);

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

        // 部署 Compliance
        compliance = new Compliance();
        compliance.initialize(admin);

        // 部署 Coupon
        coupon = new Coupon();
        coupon.initialize("Genex Coupon", "GXC", admin);

        // 部署 CouponFactory
        factory = new CouponFactory();
        factory.initialize(address(coupon), address(compliance), admin);

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

        // 授权
        coupon.grantRole(keccak256("FACTORY_ROLE"), address(factory));
        factory.grantRole(keccak256("MINTER_ROLE"), minter);
        couponBatch.grantRole(keccak256("FACTORY_ROLE"), address(factory));

        // 关联 CouponBatch
        factory.setCouponBatchContract(address(couponBatch));

        // 设置发行方 KYC
        compliance.setKycLevel(issuer, 3);

        vm.stopPrank();
    }

    function test_MintBatchUtility() public {
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _utilityConfig();
        uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 5, config);

        assertEq(tokenIds.length, 5);
        for (uint256 i = 0; i < 5; i++) {
            assertEq(coupon.ownerOf(tokenIds[i]), issuer);
            assertEq(coupon.getFaceValue(tokenIds[i]), 100e6);
        }
    }

    function test_MintBatchSecurity() public {
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _securityConfig();
        uint256[] memory tokenIds = factory.mintBatch(issuer, 1000e6, 3, config);

        assertEq(tokenIds.length, 3);
    }

    function test_UtilityMaxPriceExceedsFaceValue() public {
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _utilityConfig();
        config.maxPrice = 200e6; // 超过面值 100e6

        vm.expectRevert("Utility: maxPrice <= faceValue");
        factory.mintBatch(issuer, 100e6, 1, config);
    }

    function test_UtilityMaxExpiry12Months() public {
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _utilityConfig();
        config.expiryDate = block.timestamp + 400 days;

        vm.expectRevert("Utility: max 12 months");
        factory.mintBatch(issuer, 100e6, 1, config);
    }

    function test_SecurityRequiresExpiry() public {
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _securityConfig();
        config.expiryDate = 0;

        vm.expectRevert("Security: expiry required");
        factory.mintBatch(issuer, 1000e6, 1, config);
    }

    function test_SecurityRequiresKycL3() public {
        address lowKycIssuer = address(10);
        vm.prank(admin);
        compliance.setKycLevel(lowKycIssuer, 1);

        vm.prank(minter);
        ICoupon.CouponConfig memory config = _securityConfig();

        vm.expectRevert("Compliance: insufficient KYC level");
        factory.mintBatch(lowKycIssuer, 1000e6, 1, config);
    }

    function test_OnlyMinterCanMint() public {
        vm.prank(issuer);
        ICoupon.CouponConfig memory config = _utilityConfig();

        vm.expectRevert();
        factory.mintBatch(issuer, 100e6, 1, config);
    }

    function test_ZeroQuantityReverts() public {
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _utilityConfig();

        vm.expectRevert("CouponFactory: invalid quantity");
        factory.mintBatch(issuer, 100e6, 0, config);
    }

    function test_ExcessiveQuantityReverts() public {
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _utilityConfig();

        vm.expectRevert("CouponFactory: invalid quantity");
        factory.mintBatch(issuer, 100e6, 10001, config);
    }

    function test_BatchInfoRecorded() public {
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _utilityConfig();
        factory.mintBatch(issuer, 100e6, 3, config);

        CouponFactory.BatchInfo memory batch = factory.getBatchInfo(0);
        assertEq(batch.issuer, issuer);
        assertEq(batch.faceValue, 100e6);
        assertEq(batch.quantity, 3);
        assertEq(batch.tokenIds.length, 3);
    }

    function testFuzz_MintBatchQuantity(uint256 quantity) public {
        quantity = bound(quantity, 1, 100);
        vm.prank(minter);
        ICoupon.CouponConfig memory config = _utilityConfig();
        uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, quantity, config);
        assertEq(tokenIds.length, quantity);
    }

    // --- Helpers ---

    function _utilityConfig() internal view returns (ICoupon.CouponConfig memory) {
        bytes32[] memory stores = new bytes32[](0);
        return ICoupon.CouponConfig({
            couponType: ICoupon.CouponType.Utility,
            transferable: true,
            maxResaleCount: 3,
            maxPrice: 100e6,
            expiryDate: block.timestamp + 180 days,
            minPurchase: 0,
            stackable: false,
            allowedStores: stores,
            issuer: issuer,
            faceValue: 100e6
        });
    }

    function _securityConfig() internal view returns (ICoupon.CouponConfig memory) {
        bytes32[] memory stores = new bytes32[](0);
        return ICoupon.CouponConfig({
            couponType: ICoupon.CouponType.Security,
            transferable: true,
            maxResaleCount: 10,
            maxPrice: 0,
            expiryDate: block.timestamp + 365 days,
            minPurchase: 0,
            stackable: false,
            allowedStores: stores,
            issuer: issuer,
            faceValue: 1000e6
        });
    }

    function _batchConfig() 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
        });
    }

    // ==========================================
    // ERC-1155 (mintBatch1155) 测试
    // ==========================================

    function test_MintBatch1155Success() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        uint256 typeId = factory.mintBatch1155(issuer, 100e6, 10000, config);

        assertEq(typeId, 1);
        assertEq(couponBatch.balanceOf(issuer, typeId), 10000);
    }

    function test_MintBatch1155LargeQuantity() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        uint256 typeId = factory.mintBatch1155(issuer, 100e6, 100000, config);

        assertEq(couponBatch.balanceOf(issuer, typeId), 100000);
    }

    function test_MintBatch1155RecordsBatchInfo() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        factory.mintBatch1155(issuer, 100e6, 5000, config);

        CouponFactory.BatchInfo memory info = factory.getBatchInfo(0);
        assertEq(info.issuer, issuer);
        assertEq(info.faceValue, 100e6);
        assertEq(info.quantity, 5000);
        assertEq(info.tokenIds.length, 0); // ERC-1155 uses typeId, not tokenIds
    }

    function test_MintBatch1155ZeroIssuerReverts() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        vm.expectRevert("CouponFactory: zero address issuer");
        factory.mintBatch1155(address(0), 100e6, 1000, config);
    }

    function test_MintBatch1155ZeroFaceValueReverts() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        vm.expectRevert("CouponFactory: zero face value");
        factory.mintBatch1155(issuer, 0, 1000, config);
    }

    function test_MintBatch1155ExcessiveQuantityReverts() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        vm.expectRevert("CouponFactory: invalid quantity");
        factory.mintBatch1155(issuer, 100e6, 100001, config);
    }

    function test_MintBatch1155MaxPriceExceedsFaceValueReverts() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        config.maxPrice = 200e6; // 超过面值

        vm.expectRevert("Utility: maxPrice <= faceValue");
        factory.mintBatch1155(issuer, 100e6, 1000, config);
    }

    function test_MintBatch1155ExpiryTooFarReverts() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        config.expiryDate = block.timestamp + 400 days;

        vm.expectRevert("Utility: max 12 months");
        factory.mintBatch1155(issuer, 100e6, 1000, config);
    }

    function test_MintBatch1155ExpiredReverts() public {
        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        config.expiryDate = block.timestamp - 1;

        vm.expectRevert("Utility: expiry must be future");
        factory.mintBatch1155(issuer, 100e6, 1000, config);
    }

    function test_MintBatch1155BatchContractNotSetReverts() public {
        // 部署新工厂，不设置 CouponBatch
        vm.startPrank(admin);
        CouponFactory factory2 = new CouponFactory();
        factory2.initialize(address(coupon), address(compliance), admin);
        factory2.grantRole(keccak256("MINTER_ROLE"), minter);
        vm.stopPrank();

        vm.prank(minter);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        vm.expectRevert("CouponFactory: batch contract not set");
        factory2.mintBatch1155(issuer, 100e6, 1000, config);
    }

    function test_MintBatch1155OnlyMinterRole() public {
        vm.prank(issuer);
        ICouponBatch.BatchCouponConfig memory config = _batchConfig();
        vm.expectRevert();
        factory.mintBatch1155(issuer, 100e6, 1000, config);
    }
}
