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

import "forge-std/Test.sol";
import "../src/CouponFactory.sol";
import "../src/Coupon.sol";
import "../src/Settlement.sol";
import "../src/Redemption.sol";
import "../src/Compliance.sol";
import "../src/Treasury.sol";
import "../src/interfaces/ICoupon.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockUSDCIntegration is ERC20 {
    constructor() ERC20("USD Coin", "USDC") {}
    function decimals() public pure override returns (uint8) { return 6; }
    function mint(address to, uint256 amount) external { _mint(to, amount); }
}

/// @title 端到端集成测试
/// @notice 完整流程: 发行 → 一级市场购买 → 二级市场交易 → 兑付核销
contract IntegrationTest is Test {
    CouponFactory factory;
    Coupon coupon;
    Settlement settlement;
    Redemption redemption;
    Compliance compliance;
    Treasury treasury;
    MockUSDCIntegration usdc;

    address admin = address(1);
    address issuer = address(2);        // 发行方
    address buyer1 = address(3);        // 一级市场买方
    address buyer2 = address(4);        // 二级市场买方
    address feeCollector = address(5);  // 平台手续费

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

        usdc = new MockUSDCIntegration();

        // 部署所有合约
        compliance = new Compliance();
        compliance.initialize(admin);

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

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

        settlement = new Settlement();
        settlement.initialize(address(coupon), address(compliance), address(usdc), admin);

        redemption = new Redemption();
        redemption.initialize(address(coupon), address(compliance), admin);

        treasury = new Treasury();
        treasury.initialize(address(usdc), feeCollector, admin);

        // 角色授权
        coupon.grantRole(keccak256("FACTORY_ROLE"), address(factory));
        coupon.grantRole(keccak256("SETTLER_ROLE"), address(settlement));
        coupon.grantRole(keccak256("SETTLER_ROLE"), address(redemption));

        // KYC 设置
        compliance.setKycLevel(issuer, 3);   // L3 发行方
        compliance.setKycLevel(buyer1, 1);   // L1 普通用户
        compliance.setKycLevel(buyer2, 1);   // L1 普通用户

        // 资金准备
        usdc.mint(buyer1, 500_000e6);
        usdc.mint(buyer2, 500_000e6);
        usdc.mint(issuer, 100_000e6);

        vm.stopPrank();

        // 全局 approve
        vm.prank(buyer1);
        usdc.approve(address(settlement), type(uint256).max);
        vm.prank(buyer2);
        usdc.approve(address(settlement), type(uint256).max);
        vm.prank(buyer1);
        coupon.setApprovalForAll(address(settlement), true);
        vm.prank(buyer2);
        coupon.setApprovalForAll(address(settlement), true);
        vm.prank(issuer);
        coupon.setApprovalForAll(address(settlement), true);
    }

    /// @notice 完整 Utility 券生命周期: 铸造 → 一级购买 → 二级交易 → 兑付
    function test_FullUtilityLifecycle() public {
        // ========== Step 1: 发行方铸造券 ==========
        bytes32[] memory stores = new bytes32[](0);
        ICoupon.CouponConfig memory config = ICoupon.CouponConfig({
            couponType: ICoupon.CouponType.Utility,
            transferable: true,
            maxResaleCount: 3,
            maxPrice: 100e6,     // 面值上限 $100
            expiryDate: block.timestamp + 180 days,
            minPurchase: 0,
            stackable: false,
            allowedStores: stores,
            issuer: issuer,
            faceValue: 100e6
        });

        vm.prank(admin);
        uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 5, config);
        assertEq(tokenIds.length, 5);
        assertEq(coupon.ownerOf(tokenIds[0]), issuer);

        // ========== Step 2: 一级市场 — 发行方卖给 buyer1 ==========
        vm.prank(admin);
        settlement.executeSwap(tokenIds[0], buyer1, issuer, 100e6, address(usdc));

        assertEq(coupon.ownerOf(tokenIds[0]), buyer1);
        assertEq(usdc.balanceOf(issuer), 100_000e6 + 100e6);
        assertEq(coupon.getResaleCount(tokenIds[0]), 1);

        // ========== Step 3: 二级市场 — buyer1 以 $85 卖给 buyer2 ==========
        vm.prank(admin);
        settlement.executeSwap(tokenIds[0], buyer2, buyer1, 85e6, address(usdc));

        assertEq(coupon.ownerOf(tokenIds[0]), buyer2);
        assertEq(coupon.getResaleCount(tokenIds[0]), 2);

        // ========== Step 4: 兑付 — buyer2 去门店使用 ==========
        vm.prank(buyer2);
        redemption.redeem(tokenIds[0], keccak256("ANY_STORE"));

        // 券已销毁
        vm.expectRevert();
        coupon.ownerOf(tokenIds[0]);

        // 验证兑付记录
        Redemption.RedemptionRecord memory record = redemption.getRedemption(0);
        assertEq(record.consumer, buyer2);
        assertEq(record.issuer, issuer);
    }

    /// @notice Utility 券价格上限保护
    function test_UtilityPriceCap() public {
        bytes32[] memory stores = new bytes32[](0);
        ICoupon.CouponConfig memory config = 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
        });

        vm.prank(admin);
        uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 1, config);

        // 尝试以超过面值的价格交易
        vm.prank(admin);
        vm.expectRevert("Utility: price exceeds max price");
        settlement.executeSwap(tokenIds[0], buyer1, issuer, 120e6, address(usdc));

        // 以面值或更低价格交易成功
        vm.prank(admin);
        settlement.executeSwap(tokenIds[0], buyer1, issuer, 95e6, address(usdc));
    }

    /// @notice 不可转让券测试
    function test_NonTransferableCoupon() public {
        bytes32[] memory stores = new bytes32[](0);
        ICoupon.CouponConfig memory config = ICoupon.CouponConfig({
            couponType: ICoupon.CouponType.Utility,
            transferable: false,
            maxResaleCount: 0,
            maxPrice: 50e6,
            expiryDate: block.timestamp + 90 days,
            minPurchase: 0,
            stackable: false,
            allowedStores: stores,
            issuer: issuer,
            faceValue: 50e6
        });

        vm.prank(admin);
        uint256[] memory tokenIds = factory.mintBatch(issuer, 50e6, 1, config);

        // 不可转让券不能在二级市场交易
        // With maxResaleCount=0, Settlement's own resale count check (0 < 0 == false)
        // fires before the Coupon's _beforeTokenTransfer non-transferable check
        vm.prank(admin);
        vm.expectRevert("Settlement: max resale count exceeded");
        settlement.executeSwap(tokenIds[0], buyer1, issuer, 50e6, address(usdc));
    }

    /// @notice 合规检查集成
    function test_ComplianceBlocksBlacklistedTrader() public {
        bytes32[] memory stores = new bytes32[](0);
        ICoupon.CouponConfig memory config = 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
        });

        vm.prank(admin);
        uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 1, config);

        // 将 buyer1 加入 OFAC 黑名单
        vm.prank(admin);
        compliance.addToBlacklist(buyer1, "OFAC SDN Match");

        // 交易被拒绝
        vm.prank(admin);
        vm.expectRevert("Compliance: buyer blacklisted");
        settlement.executeSwap(tokenIds[0], buyer1, issuer, 100e6, address(usdc));
    }

    /// @notice 过期券不能交易也不能兑付
    function test_ExpiredCouponBlocked() public {
        bytes32[] memory stores = new bytes32[](0);
        ICoupon.CouponConfig memory config = ICoupon.CouponConfig({
            couponType: ICoupon.CouponType.Utility,
            transferable: true,
            maxResaleCount: 3,
            maxPrice: 100e6,
            expiryDate: block.timestamp + 30 days,
            minPurchase: 0,
            stackable: false,
            allowedStores: stores,
            issuer: issuer,
            faceValue: 100e6
        });

        vm.prank(admin);
        uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 1, config);

        // 快进到过期
        vm.warp(block.timestamp + 31 days);

        // 交易被拒绝
        vm.prank(admin);
        vm.expectRevert("Settlement: coupon expired");
        settlement.executeSwap(tokenIds[0], buyer1, issuer, 100e6, address(usdc));

        // 兑付也被拒绝
        vm.prank(issuer);
        vm.expectRevert("Redemption: coupon expired");
        redemption.redeem(tokenIds[0], keccak256("STORE"));
    }

    /// @notice 转售次数用尽后不能再交易
    function test_MaxResaleCountBlocksFurtherTrades() public {
        bytes32[] memory stores = new bytes32[](0);
        ICoupon.CouponConfig memory config = ICoupon.CouponConfig({
            couponType: ICoupon.CouponType.Utility,
            transferable: true,
            maxResaleCount: 2,
            maxPrice: 100e6,
            expiryDate: block.timestamp + 180 days,
            minPurchase: 0,
            stackable: false,
            allowedStores: stores,
            issuer: issuer,
            faceValue: 100e6
        });

        vm.prank(admin);
        uint256[] memory tokenIds = factory.mintBatch(issuer, 100e6, 1, config);

        // 第1次: issuer → buyer1
        vm.prank(admin);
        settlement.executeSwap(tokenIds[0], buyer1, issuer, 100e6, address(usdc));

        // 第2次: buyer1 → buyer2
        vm.prank(admin);
        settlement.executeSwap(tokenIds[0], buyer2, buyer1, 90e6, address(usdc));

        // 第3次: 应被拒绝（已转售2次，达到上限）
        vm.prank(buyer2);
        coupon.setApprovalForAll(address(settlement), true);

        vm.prank(admin);
        usdc.mint(buyer1, 100e6);

        vm.prank(admin);
        vm.expectRevert("Settlement: max resale count exceeded");
        settlement.executeSwap(tokenIds[0], buyer1, buyer2, 80e6, address(usdc));

        // 但兑付仍然可以
        vm.prank(buyer2);
        redemption.redeem(tokenIds[0], keccak256("STORE"));
    }
}
