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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {AaveQuotePushFlashReceiver} from "./AaveQuotePushFlashReceiver.sol";

interface IQuotePushSweepableReceiver {
    function owner() external view returns (address);
    function quoteSurplusBalance(address quoteToken, uint256 reserveRetained) external view returns (uint256 surplus);
    function sweepQuoteSurplus(address quoteToken, address to, uint256 reserveRetained)
        external
        returns (uint256 amount);
    function transferOwnership(address newOwner) external;
}

/**
 * @title QuotePushTreasuryManager
 * @notice Minimal treasury manager for Aave quote-push retained surplus.
 *         Intended flow:
 *           1. Receiver retains quote surplus after flash repayment.
 *           2. This manager, as receiver owner, harvests that surplus into itself.
 *           3. Owner/operator distributes quote to gas and recycle recipients.
 *
 *         Gas replenishment is still an operator policy in quote units; converting
 *         quote into native gas token remains off-chain / external.
 */
contract QuotePushTreasuryManager is Ownable {
    using SafeERC20 for IERC20;

    IQuotePushSweepableReceiver public immutable receiver;
    IERC20 public immutable quoteToken;

    address public operator;
    address public gasRecipient;
    address public recycleRecipient;
    uint256 public receiverReserveRetained;
    uint256 public managerReserveRetained;

    error BadConfig();
    error Unauthorized();
    error NothingToHarvest();
    error InsufficientAvailable(uint256 available, uint256 requested);
    error ReceiverNotOwnedByManager();

    event OperatorUpdated(address indexed previousOperator, address indexed newOperator);
    event RecipientsUpdated(address indexed gasRecipient, address indexed recycleRecipient);
    event ReservesUpdated(uint256 receiverReserveRetained, uint256 managerReserveRetained);
    event ReceiverSurplusHarvested(address indexed token, uint256 amount, uint256 receiverReserveRetained);
    event QuoteDistributed(address indexed token, address indexed to, uint256 amount, bytes32 purpose);
    event TokenRescued(address indexed token, address indexed to, uint256 amount);
    event ManagedCycleExecuted(
        address indexed flashAsset,
        uint256 flashAmount,
        uint256 harvested,
        uint256 gasAmount,
        uint256 recycleAmount
    );
    event ManagedReceiverOwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    modifier onlyOwnerOrOperator() {
        if (msg.sender != owner() && msg.sender != operator) revert Unauthorized();
        _;
    }

    constructor(
        address initialOwner,
        address receiver_,
        address quoteToken_,
        address operator_,
        address gasRecipient_,
        address recycleRecipient_,
        uint256 receiverReserveRetained_,
        uint256 managerReserveRetained_
    ) Ownable(initialOwner) {
        if (
            initialOwner == address(0) || receiver_ == address(0) || quoteToken_ == address(0)
                || gasRecipient_ == address(0) || recycleRecipient_ == address(0)
        ) revert BadConfig();

        receiver = IQuotePushSweepableReceiver(receiver_);
        quoteToken = IERC20(quoteToken_);
        operator = operator_;
        gasRecipient = gasRecipient_;
        recycleRecipient = recycleRecipient_;
        receiverReserveRetained = receiverReserveRetained_;
        managerReserveRetained = managerReserveRetained_;
    }

    function receiverOwner() public view returns (address) {
        return receiver.owner();
    }

    function isReceiverOwnedByManager() public view returns (bool) {
        return receiver.owner() == address(this);
    }

    function quoteBalance() public view returns (uint256) {
        return quoteToken.balanceOf(address(this));
    }

    function availableQuote() public view returns (uint256 available) {
        uint256 balance = quoteBalance();
        if (balance > managerReserveRetained) {
            available = balance - managerReserveRetained;
        }
    }

    function receiverSweepableQuote() public view returns (uint256) {
        return receiver.quoteSurplusBalance(address(quoteToken), receiverReserveRetained);
    }

    function setOperator(address newOperator) external onlyOwner {
        emit OperatorUpdated(operator, newOperator);
        operator = newOperator;
    }

    function setRecipients(address gasRecipient_, address recycleRecipient_) external onlyOwner {
        if (gasRecipient_ == address(0) || recycleRecipient_ == address(0)) revert BadConfig();
        gasRecipient = gasRecipient_;
        recycleRecipient = recycleRecipient_;
        emit RecipientsUpdated(gasRecipient_, recycleRecipient_);
    }

    function setReserves(uint256 receiverReserveRetained_, uint256 managerReserveRetained_) external onlyOwner {
        receiverReserveRetained = receiverReserveRetained_;
        managerReserveRetained = managerReserveRetained_;
        emit ReservesUpdated(receiverReserveRetained_, managerReserveRetained_);
    }

    function harvestReceiverSurplus() public onlyOwnerOrOperator returns (uint256 amount) {
        amount = receiver.sweepQuoteSurplus(address(quoteToken), address(this), receiverReserveRetained);
        if (amount == 0) revert NothingToHarvest();
        emit ReceiverSurplusHarvested(address(quoteToken), amount, receiverReserveRetained);
    }

    function runManagedCycle(
        address flashAsset,
        uint256 flashAmount,
        AaveQuotePushFlashReceiver.QuotePushParams calldata params,
        bool harvestSurplus,
        uint256 gasHoldbackTargetRaw
    ) external onlyOwnerOrOperator returns (uint256 harvested, uint256 gasAmount, uint256 recycleAmount) {
        if (!isReceiverOwnedByManager()) revert ReceiverNotOwnedByManager();

        AaveQuotePushFlashReceiver(address(receiver)).flashQuotePush(flashAsset, flashAmount, params);

        if (harvestSurplus) {
            uint256 sweepable = receiverSweepableQuote();
            if (sweepable > 0) {
                harvested = harvestReceiverSurplus();
            }
        }

        uint256 available = availableQuote();
        gasAmount = _min(available, gasHoldbackTargetRaw);
        recycleAmount = available - gasAmount;

        if (gasAmount > 0 || recycleAmount > 0) {
            _distributeConfigured(gasAmount, recycleAmount);
        }

        emit ManagedCycleExecuted(flashAsset, flashAmount, harvested, gasAmount, recycleAmount);
    }

    function distributeQuote(address to, uint256 amount, bytes32 purpose) external onlyOwnerOrOperator {
        _distributeQuote(to, amount, purpose);
    }

    function distributeToConfiguredRecipients(uint256 gasAmount, uint256 recycleAmount) external onlyOwnerOrOperator {
        _distributeConfigured(gasAmount, recycleAmount);
    }

    function transferManagedReceiverOwnership(address newOwner) external onlyOwner {
        if (newOwner == address(0)) revert BadConfig();
        if (!isReceiverOwnedByManager()) revert ReceiverNotOwnedByManager();
        address previousOwner = receiver.owner();
        receiver.transferOwnership(newOwner);
        emit ManagedReceiverOwnershipTransferred(previousOwner, newOwner);
    }

    function rescueToken(address token, address to, uint256 amount) external onlyOwner {
        if (token == address(0) || to == address(0) || amount == 0) revert BadConfig();
        if (token == address(quoteToken)) {
            _requireAvailable(amount);
        }
        IERC20(token).safeTransfer(to, amount);
        emit TokenRescued(token, to, amount);
    }

    function _distributeQuote(address to, uint256 amount, bytes32 purpose) internal {
        if (to == address(0) || amount == 0) revert BadConfig();
        _requireAvailable(amount);
        quoteToken.safeTransfer(to, amount);
        emit QuoteDistributed(address(quoteToken), to, amount, purpose);
    }

    function _distributeConfigured(uint256 gasAmount, uint256 recycleAmount) internal {
        uint256 requested = gasAmount + recycleAmount;
        _requireAvailable(requested);

        if (gasAmount > 0) {
            quoteToken.safeTransfer(gasRecipient, gasAmount);
            emit QuoteDistributed(address(quoteToken), gasRecipient, gasAmount, bytes32("gas"));
        }
        if (recycleAmount > 0) {
            quoteToken.safeTransfer(recycleRecipient, recycleAmount);
            emit QuoteDistributed(address(quoteToken), recycleRecipient, recycleAmount, bytes32("recycle"));
        }
    }

    function _requireAvailable(uint256 requested) internal view {
        uint256 available = availableQuote();
        if (requested > available) revert InsufficientAvailable(available, requested);
    }

    function _min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }
}
