// File: contracts/V2/AutoBurnAndClaimV2.sol



// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.20;

import {ERC20} from "../interfaces/ERC20.sol";

// Gelato Dependency
import {OpsProxy} from "../interfaces/OpsProxy.sol";
import {OpsProxyFactory} from "../interfaces/OpsProxyFactory.sol";
import {IAutomate, IProxyModule, Module} from "../interfaces/Gelato.sol";
// Synthetix Dependency
import {AddressResolver} from "../interfaces/AddressResolver.sol";
import {DelegateApprovals} from "../interfaces/DelegateApprovals.sol";
import {FeePool} from "../interfaces/FeePool.sol";
import {Issuer} from "../interfaces/Issuer.sol";
import {Synthetix} from "../interfaces/Synthetix.sol";
import {SystemSettings} from "../interfaces/SystemSettings.sol";

contract AutoBurnAndClaimV2 {
    bytes32 private constant DELEGATE_APPROVALS = "DelegateApprovals";
    bytes32 private constant SYSTEM_SETTINGS = "SystemSettings";
    bytes32 private constant FEE_POOL = "FeePool";
    bytes32 private constant ISSUER = "Issuer";
    bytes32 private constant SUSD_PROXY = "ProxyERC20sUSD";
    bytes32 private constant SYNTHETIX = "Synthetix";

    AddressResolver immutable SNXAddressResolver;
    OpsProxyFactory immutable OPS_PROXY_FACTORY;
    DelegateApprovals private delegateApprovals;
    FeePool private feePool;
    Issuer private issuer;
    ERC20 private sUSD;
    SystemSettings private systemSettings;
    address private SNX;

    mapping(address => uint256) public baseFee;

    error ZeroAddressResolved(bytes32 name);

    constructor(address _SNXAddressResolver, address _automate) {
        SNXAddressResolver = AddressResolver(_SNXAddressResolver);
        _rebuildCaches();
        IAutomate automate = IAutomate(_automate);
        IProxyModule proxyModule = IProxyModule(automate.taskModuleAddresses(Module.PROXY));
        OPS_PROXY_FACTORY = OpsProxyFactory(proxyModule.opsProxyFactory());
    }

    function checker(
        address _account
    ) external view returns (bool, bytes memory execPayload) {
        (address dedicatedMsgSender, ) = OPS_PROXY_FACTORY.getProxyOf(_account);

        // first off, check gas price
        uint256 _gasPrice = baseFee[_account];
        if(_gasPrice != 0 && block.basefee > _gasPrice) {
            return (false, "basefee too high");
        }

        //second, check claim permission
        if(!delegateApprovals.canClaimFor(_account, dedicatedMsgSender) ) {
            return (false, "no claim permission for gelato");
        }

        //third, is reward avaliable to claim?
        (uint256 fee, uint256 SNXRewards) = feePool.feesAvailable(_account);
        if((fee + SNXRewards) == 0 || feePool.totalRewardsAvailable() == 0) {
            return (false, "no reward avaliable");
        }

        // forth, check burn permission and if need to burn
        uint256 issuanceRatio = systemSettings.issuanceRatio();
        uint256 cRatio = issuer.collateralisationRatio(_account);

        address[] memory targets;
        bytes[] memory datas;
        uint256[] memory values;

        if(cRatio > issuanceRatio) {
            uint256 threshold = 1e18 + systemSettings.targetThreshold();
            uint256 issuanceAdjusted = issuanceRatio * threshold / 1e18;
            if(cRatio > issuanceAdjusted) {
                bool burnPerms = delegateApprovals.canBurnFor(_account, dedicatedMsgSender);
                if(!burnPerms) {
                    return (false, "no burn permission and c-ratio too low");
                }
                else {
                    uint256 debtBalance = issuer.debtBalanceOf(_account, "sUSD");
                    uint256 maxIssuable = issuer.maxIssuableSynths(_account);
                    uint256 burnAmount = debtBalance - maxIssuable;
                    uint256 sUSDBalance = sUSD.balanceOf(_account);
                    if(sUSDBalance < burnAmount) {
                        return (false, "not enough sUSD to fix c-ratio");
                    }
                    else {
                        targets = new address[](2);
                        datas = new bytes[](2);
                        values = new uint256[](2);
                        targets[0] = SNX;
                        targets[1] = address(feePool);
                        datas[0] = abi.encodeWithSelector(Synthetix.burnSynthsToTargetOnBehalf.selector, _account);
                        datas[1] = abi.encodeWithSelector(feePool.claimOnBehalf.selector, _account);
                        values[0] = 0;
                        values[1] = 0;
                        return (true, 
                            abi.encodeWithSelector(
                                OpsProxy.batchExecuteCall.selector, 
                                targets,
                                datas,
                                values));
                    }
                }
            }
        }

        targets = new address[](1);
        datas = new bytes[](1);
        values = new uint256[](1);
        targets[0] = address(feePool);
        datas[0] = abi.encodeWithSelector(feePool.claimOnBehalf.selector, _account);
        // values[0] = 0;
        return (true,
                abi.encodeWithSelector(
                    OpsProxy.batchExecuteCall.selector,
                    targets,
                    datas,
                    values
                    )
            );
    }

    function setBaseFee(uint256 _baseFee) external {
        baseFee[msg.sender] = _baseFee;
    }

    function rebuildCaches() external {
        _rebuildCaches();
    }

    function _rebuildCaches() internal {
        feePool = FeePool(getAddress(FEE_POOL));
        delegateApprovals = DelegateApprovals(getAddress(DELEGATE_APPROVALS));
        systemSettings = SystemSettings(getAddress(SYSTEM_SETTINGS));
        issuer = Issuer(getAddress(ISSUER));
        SNX = getAddress(SYNTHETIX);
        sUSD = ERC20(getAddress(SUSD_PROXY));
    }

    function getAddress(bytes32 name) internal view returns (address) {
        address resolved = SNXAddressResolver.getAddress(name);
        if (resolved == address(0)) {
            revert ZeroAddressResolved(name);
        }
        return resolved;
    }
}
// File: contracts/interfaces/ERC20.sol



pragma solidity ^0.8.20;

interface ERC20 {
    function balanceOf(address _who) external view returns (uint256);
}
// File: contracts/interfaces/OpsProxy.sol



pragma solidity ^0.8.20;

interface OpsProxy {
    function batchExecuteCall(
        address[] calldata _targets,
        bytes[] calldata _datas,
        uint256[] calldata _values
    ) external payable;

    function owner() external returns (address);
}
// File: contracts/interfaces/OpsProxyFactory.sol



pragma solidity ^0.8.20;

interface OpsProxyFactory {
    function getProxyOf(address account) external view returns (address, bool);

    function deployFor(address owner) external returns (address payable proxy);

    function ops() external returns (address);
}
// File: contracts/interfaces/Gelato.sol



pragma solidity ^0.8.20;

enum Module {
    RESOLVER,
    DEPRECATED_TIME,
    PROXY,
    SINGLE_EXEC,
    WEB3_FUNCTION,
    TRIGGER
}

enum TriggerType {
    TIME,
    CRON,
    EVENT,
    BLOCK
}

struct ModuleData {
    Module[] modules;
    bytes[] args;
}

interface IAutomate {
    function createTask(
        address execAddress,
        bytes calldata execDataOrSelector,
        ModuleData calldata moduleData,
        address feeToken
    ) external returns (bytes32 taskId);

    function cancelTask(bytes32 taskId) external;

    function getFeeDetails() external view returns (uint256, address);

    function gelato() external view returns (address payable);

    function taskModuleAddresses(Module) external view returns (address);
}

interface IProxyModule {
    function opsProxyFactory() external view returns (address);
}

// File: contracts/interfaces/AddressResolver.sol



pragma solidity ^0.8.20;

interface AddressResolver {
    function getAddress(bytes32 name) external view returns (address);
}
// File: contracts/interfaces/DelegateApprovals.sol



pragma solidity ^0.8.20;

interface DelegateApprovals {
    function canIssueFor(
        address authoriser,
        address delegate
    ) external view returns (bool);
    function approveIssueOnBehalf(address delegate) external;

    function canClaimFor(address authoriser, address delegate) external view returns (bool);

    function approveClaimOnBehalf(address delegate) external;

    function canBurnFor(address authoriser, address delegate) external view returns (bool);

    function approveBurnOnBehalf(address delegate) external;
}
// File: contracts/interfaces/FeePool.sol



pragma solidity ^0.8.20;

interface FeePool {
    function feesAvailable(
        address account
    ) external view returns (uint256, uint256);

    function totalRewardsAvailable() external view returns (uint);

    function claimOnBehalf(address claimingForAddress) external returns (bool);

    function claimFees() external returns (bool);
}
// File: contracts/interfaces/Issuer.sol



pragma solidity ^0.8.20;

interface Issuer {
    function collateralisationRatio(address issuer) external view returns (uint);

    function debtBalanceOf(address _issuer, bytes32 currencyKey) external view returns (uint256);

    function maxIssuableSynths(address _issuer) external view returns (uint);
}
// File: contracts/interfaces/Synthetix.sol



pragma solidity ^0.8.20;

interface Synthetix {
    function remainingIssuableSynths(address issuer)
        external
        view
        returns (
            uint maxIssuable,
            uint alreadyIssued,
            uint totalSystemDebt
        );
    
    function collateralisationRatio(address issuer) external view returns (uint);

    function issueMaxSynthsOnBehalf(address issueForAddress) external;

    function burnSynthsToTargetOnBehalf(address burnForAddress) external;
    function burnSynthsToTarget() external;
}
// File: contracts/interfaces/SystemSettings.sol



pragma solidity ^0.8.20;

interface SystemSettings {
    function issuanceRatio() external view returns (uint);

    function targetThreshold() external view returns (uint);
}