pragma solidity 0.8.20;

import {Test} from "forge-std/Test.sol";

import {Fatso} from "../../src/FatsoERC20.sol";
import {GhostAuthority} from "../../src/GhostAuthority.sol";
import {GhostTreasury} from "../../src/Treasury.sol";
import {GhostBondingCalculator} from "../../src/StandardBondingCalculator.sol";
import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol";

import {ITreasury} from "../../src/interfaces/ITreasury.sol";
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";

contract GhostTreasuryCoefficientLesserTest is Test {
    using SafeERC20 for IERC20;

    address public constant OWNER    = 0x0000000000000000000000000000000000000001;
    address public constant GOVERNOR = 0x0000000000000000000000000000000000000002;
    address public constant GUARDIAN = 0x0000000000000000000000000000000000000003;
    address public constant ALICE    = 0x0000000000000000000000000000000000000004;

    uint256 public constant AMOUNT = 69 * 1e18;

    Fatso ftso;
    ERC20Mock reserve;
    ERC20Mock liquidity;
    GhostTreasury treasury;
    GhostAuthority authority;
    GhostBondingCalculator calculator;

    function setUp() public {
        vm.startPrank(OWNER);
        authority = new GhostAuthority(
            GOVERNOR,
            GUARDIAN,
            OWNER,
            OWNER
        );
        reserve = new ERC20Mock("Reserve Token", "RET");
        liquidity = new ERC20Mock("Liquidity Token", "LDT");
        ftso = new Fatso(address(authority), "Fatso", "FTSO");
        treasury = new GhostTreasury(address(ftso), 69, address(authority));
        calculator = new GhostBondingCalculator(address(ftso), 1, 20);
        vm.stopPrank();

        vm.prank(GOVERNOR);
        authority.pushVault(address(treasury));

        vm.startPrank(ALICE);
        reserve.mint(ALICE, AMOUNT);
        reserve.approve(address(treasury), type(uint256).max);
        ftso.approve(address(treasury), type(uint256).max);
        vm.stopPrank();
    }

    function test_deposit_onlyIfApprovedTokenAndApprovedAddress() public {
        vm.expectRevert();
        vm.prank(ALICE);
        treasury.deposit(address(reserve), AMOUNT, 0);

        vm.prank(GOVERNOR);
        treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0));

        vm.expectRevert();
        vm.prank(ALICE);
        treasury.deposit(address(reserve), AMOUNT, 0);

        vm.prank(GOVERNOR);
        treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator));
        vm.prank(ALICE);
        uint256 send = treasury.deposit(address(reserve), AMOUNT, 0);

        assertEq(ftso.balanceOf(ALICE), send);
        assertEq(reserve.balanceOf(address(treasury)), AMOUNT);

        vm.startPrank(GOVERNOR);
        treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0));
        treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator));
        vm.stopPrank();
    }

    function test_auditTreasuryReservesWithCoefficient() public {
        vm.startPrank(GOVERNOR);
        treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0));
        treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator));
        vm.stopPrank();

        assertEq(treasury.totalReserves(), 0);

        uint256 tokenValue = treasury.tokenValue(address(reserve), AMOUNT);
        vm.prank(ALICE);
        IERC20(reserve).safeTransfer(address(treasury), AMOUNT);

        assertEq(treasury.totalReserves(), 0);

        vm.prank(GOVERNOR);
        treasury.auditReserves();
        assertEq(treasury.totalReserves(), tokenValue);

        uint256 coefficient = treasury.originalCoefficient();
        uint256 decimals = IERC20Metadata(address(reserve)).decimals() + 9;
        uint256 convertedReserves = treasury.totalReserves() * 10**decimals / coefficient;
        uint256 convertedEps = convertedReserves * 1e5 / 1e12; // .000001%

        assertEq(convertedReserves + convertedEps >= convertedReserves, true);
        assertEq(convertedReserves - convertedEps <= convertedReserves, true);

        assertEq(treasury.excessReserves(), tokenValue);
    }

    function test_originalCoefficientCorrect() public {
        vm.startPrank(GOVERNOR);
        treasury.enable(ITreasury.STATUS.RESERVEDEPOSITOR, ALICE, address(0));
        treasury.enable(ITreasury.STATUS.RESERVETOKEN, address(reserve), address(calculator));
        vm.stopPrank();

        uint256 estimation = 1e18 / 20;
        uint256 estimationEps = estimation * 1e5 / 1e12; // .000001%

        assertEq(estimation + estimationEps >= treasury.originalCoefficient(), true);
        assertEq(estimation - estimationEps <= treasury.originalCoefficient(), true);
    }
}
