// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../src/Counter.sol";
import "./MintableERC20.sol";
import { ERC1155Holder } from "@openzeppelin/token/ERC1155/utils/ERC1155Holder.sol";
import { IERC20 } from '@openzeppelin/token/ERC20/IERC20.sol';

contract CounterTest is Test, ERC1155Holder {
    event ClaimedRewards(uint256 creatorId, address receiver, uint256 amount);
    event CollectionCreated(
        address collectionOwner,
        uint32 collectionIndex
    );
    event CollectionEntryUpdated(
        address collectionOwner,
        uint32 collectionIndex,
        uint256 creatorId,
        uint32 feeBips
    );
    event UserSubscribed(
        address subscriber,
        address collectionOwner,
        uint32 collectionIndex,
        uint32 subscriptionStartTimestamp,
        uint32 subscriptionDuration,
        uint256 subscriptionFee
    );

    Counter public counter;
    MintableERC20 public mockUsdc;
    uint256 public SUBSCRIPTION_FEE_USDC = 10_000; // $0.01 in USDC.
    uint32 public SUBSCRIPTION_LENGTH_SECONDS = 604_800; // 1 week in seconds.
    uint256 public ORIGINAL_BALANCE = SUBSCRIPTION_FEE_USDC * 10_000;
    string public MOCK_URI = "MOCK URI";

    constructor(
    ) ERC1155Holder() { }

    function setUp() public {
        mockUsdc = new MintableERC20("Mock USDC", "MOCK");
        counter = new Counter(
            IERC20(mockUsdc),
            SUBSCRIPTION_FEE_USDC,
            SUBSCRIPTION_LENGTH_SECONDS,
            MOCK_URI
         );
        counter.setNumber(0);
        mockUsdc.mint(address(this), ORIGINAL_BALANCE);
    }

    function testIncrement() public {
        counter.increment();
        assertEq(counter.number(), 1);
    }

    function testSetNumber(uint256 x) public {
        counter.setNumber(x);
        assertEq(counter.number(), x);
    }

    function testCreateCollection() public {
        // Verify a `CollectionCreated` event is emitted.
        vm.expectEmit(false, false, false, true, address(counter));
        emit CollectionCreated(address(this), 0);

        counter.createCollection();

        assertEq(counter.getCollectionNumTotalSubscribers(address(this), 0), 0);
        assertEq(counter.getCollectionTotalFeeBips(address(this), 0), 0);
    }

    function testCreateCollectionEntry() public {
        counter.createCollection();
        counter.registerCreator();
        uint256 creatorId = counter.numCreators() - 1;

        uint32 collectionIndex = 0;
        uint32 feeBips = 77;
        // Verify a `CollectionEntryUpdated` event is emitted.
        vm.expectEmit(false, false, false, true, address(counter));
        emit CollectionEntryUpdated(address(this), collectionIndex, creatorId, feeBips);

        counter.updateCollectionEntry(collectionIndex, creatorId, feeBips); 

        Counter.CollectionEntry memory entry = counter.getCollectionEntry(
            address(this),
            collectionIndex,
            creatorId
        );

        assertEq(entry.lastNumSubscribersPayout, 0);
        assertEq(entry.feeBips, feeBips);

        uint32 totalFeeBips = counter.getCollectionTotalFeeBips(address(this), collectionIndex);
        assertEq(totalFeeBips, feeBips);
    }

    function testSubscribeToCollectionAndClaimRewards() public {
        counter.createCollection();
        counter.registerCreator();
        uint256 creatorId = counter.numCreators() - 1;

        uint32 collectionIndex = 0;
        uint32 feeBips = 10_000;
        counter.updateCollectionEntry(collectionIndex, creatorId, feeBips);

        uint256 expectedFee = SUBSCRIPTION_FEE_USDC;
        mockUsdc.approve(address(counter), expectedFee);
        // TODO: Add event emission assertions.
        counter.subscribeToCollection(address(this), collectionIndex, address(this), address(this));

        uint32 numTotalSubscribers = counter.getCollectionNumTotalSubscribers(address(this), collectionIndex); 
        assertEq(numTotalSubscribers, 1);

        uint32 totalFeeBips = counter.getCollectionTotalFeeBips(address(this), collectionIndex); 
        assertEq(totalFeeBips, feeBips);

        assertEq(mockUsdc.balanceOf(address(counter)), expectedFee);
        assertEq(mockUsdc.balanceOf(address(this)), ORIGINAL_BALANCE - expectedFee);

        counter.updateCollectionEntry(collectionIndex, creatorId, feeBips);
        assertEq(mockUsdc.balanceOf(address(counter)), expectedFee);
        assertEq(mockUsdc.balanceOf(address(this)), ORIGINAL_BALANCE - expectedFee);

        // Verify a `ClaimedRewards` event is emitted.
        vm.expectEmit(false, false, false, true, address(counter));
        emit ClaimedRewards(creatorId, address(this), expectedFee);

        counter.claimRewards(creatorId);
        assertEq(mockUsdc.balanceOf(address(counter)), 0);
        assertEq(mockUsdc.balanceOf(address(this)), ORIGINAL_BALANCE);
    }
}
