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

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

contract MockUSDC2 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); }
}

contract CouponBackedSecurityTest is Test {
    CouponBackedSecurity cbs;
    Coupon coupon;
    Compliance compliance;
    MockUSDC2 usdc;

    address admin = address(1);
    address cbsIssuer = address(2);
    address investor1 = address(3);
    address investor2 = address(4);
    address settler = address(5);
    address ratingAgency = address(6);

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

        usdc = new MockUSDC2();
        compliance = new Compliance();
        compliance.initialize(admin);

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

        cbs = new CouponBackedSecurity();
        cbs.initialize(address(coupon), address(compliance), address(usdc), admin);
        cbs.grantRole(keccak256("ISSUER_ROLE"), cbsIssuer);
        cbs.grantRole(keccak256("SETTLER_ROLE"), settler);
        cbs.grantRole(keccak256("RATING_AGENCY_ROLE"), ratingAgency);

        // KYC
        compliance.setKycLevel(cbsIssuer, 3);
        compliance.setKycLevel(investor1, 2);
        compliance.setKycLevel(investor2, 2);

        // Fund
        usdc.mint(investor1, 1_000_000e6);
        usdc.mint(investor2, 1_000_000e6);
        usdc.mint(settler, 1_000_000e6);

        vm.stopPrank();

        // Approvals
        vm.prank(investor1);
        usdc.approve(address(cbs), type(uint256).max);
        vm.prank(investor2);
        usdc.approve(address(cbs), type(uint256).max);
        vm.prank(settler);
        usdc.approve(address(cbs), type(uint256).max);
        vm.prank(cbsIssuer);
        coupon.setApprovalForAll(address(cbs), true);
    }

    function test_CreatePool() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(3, 1000e6);

        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 100, block.timestamp + 365 days);

        (
            uint256 totalFaceValue,
            uint256 totalShares,
            uint256 soldShares,
            ,
            ,
            address issuer,
            bool active
        ) = cbs.getPool(poolId);

        assertEq(totalFaceValue, 3000e6);
        assertEq(totalShares, 100);
        assertEq(soldShares, 0);
        assertEq(issuer, cbsIssuer);
        assertTrue(active);
    }

    function test_PurchaseShares() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(2, 500e6);
        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 100, block.timestamp + 365 days);

        vm.prank(investor1);
        cbs.purchaseShares(poolId, 30);

        assertEq(cbs.shares(poolId, investor1), 30);
        (, , uint256 soldShares, , , , ) = cbs.getPool(poolId);
        assertEq(soldShares, 30);
    }

    function test_PurchaseSharesInsufficientReverts() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(1, 100e6);
        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 10, block.timestamp + 365 days);

        vm.prank(investor1);
        vm.expectRevert("CBS: insufficient shares");
        cbs.purchaseShares(poolId, 11);
    }

    function test_PurchaseSharesRequiresKycL2() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(1, 100e6);
        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 10, block.timestamp + 365 days);

        address lowKyc = address(99);
        vm.prank(admin);
        compliance.setKycLevel(lowKyc, 1);

        vm.prank(lowKyc);
        vm.expectRevert("Compliance: insufficient KYC level");
        cbs.purchaseShares(poolId, 1);
    }

    function test_OnlySecurityCouponsAllowed() public {
        // Mint utility coupon
        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: cbsIssuer,
            faceValue: 100e6
        });
        vm.prank(admin);
        uint256 tokenId = coupon.mint(cbsIssuer, 100e6, config);

        uint256[] memory tokenIds = new uint256[](1);
        tokenIds[0] = tokenId;

        vm.prank(cbsIssuer);
        vm.expectRevert("CBS: only Security coupons");
        cbs.createPool(tokenIds, 10, block.timestamp + 365 days);
    }

    function test_SetCreditRating() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(1, 1000e6);
        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 10, block.timestamp + 365 days);

        vm.prank(ratingAgency);
        cbs.setCreditRating(poolId, "AAA");

        (, , , , string memory rating, , ) = cbs.getPool(poolId);
        assertEq(rating, "AAA");
    }

    function test_DepositAndClaimYield() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(2, 500e6);
        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 100, block.timestamp + 365 days);

        // 两个投资者各买 50 份
        vm.prank(investor1);
        cbs.purchaseShares(poolId, 50);
        vm.prank(investor2);
        cbs.purchaseShares(poolId, 50);

        // Settler 存入收益 1000 USDC
        vm.prank(settler);
        cbs.depositYield(poolId, 1000e6);

        // 各自领取 500 USDC
        vm.prank(investor1);
        cbs.claimYield(poolId);
        assertEq(usdc.balanceOf(investor1), 1_000_000e6 - 500e6 + 500e6); // 购买花了500e6, 领回500e6

        vm.prank(investor2);
        cbs.claimYield(poolId);
    }

    function test_ClaimYieldNothingToClaimReverts() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(1, 1000e6);
        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 10, block.timestamp + 365 days);

        vm.prank(investor1);
        cbs.purchaseShares(poolId, 5);

        // 没有收益存入
        vm.prank(investor1);
        vm.expectRevert("CBS: nothing to claim");
        cbs.claimYield(poolId);
    }

    function test_DeactivatePool() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(1, 1000e6);
        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 10, block.timestamp + 365 days);

        vm.prank(admin);
        cbs.deactivatePool(poolId);

        (, , , , , , bool active) = cbs.getPool(poolId);
        assertFalse(active);
    }

    function test_GetClaimableYield() public {
        uint256[] memory tokenIds = _mintSecurityCoupons(1, 1000e6);
        vm.prank(cbsIssuer);
        uint256 poolId = cbs.createPool(tokenIds, 100, block.timestamp + 365 days);

        vm.prank(investor1);
        cbs.purchaseShares(poolId, 25);

        vm.prank(settler);
        cbs.depositYield(poolId, 400e6);

        uint256 claimable = cbs.getClaimableYield(poolId, investor1);
        assertEq(claimable, 100e6); // 25% of 400
    }

    // --- Helpers ---

    function _mintSecurityCoupons(uint256 count, uint256 faceValue) internal returns (uint256[] memory) {
        uint256[] memory tokenIds = new uint256[](count);
        bytes32[] memory stores = new bytes32[](0);

        ICoupon.CouponConfig memory config = ICoupon.CouponConfig({
            couponType: ICoupon.CouponType.Security,
            transferable: true,
            maxResaleCount: 10,
            maxPrice: 0,
            expiryDate: block.timestamp + 730 days,
            minPurchase: 0,
            stackable: false,
            allowedStores: stores,
            issuer: cbsIssuer,
            faceValue: faceValue
        });

        for (uint256 i = 0; i < count; i++) {
            vm.prank(admin);
            tokenIds[i] = coupon.mint(cbsIssuer, faceValue, config);
        }

        return tokenIds;
    }
}
