// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IAereLendingMarketForFund {
    function repayOnBehalfOf(address account, uint256 amount) external;
    function debtBalance(address user) external view returns (uint256);
    function DEBT_TOKEN() external view returns (address);
}

/**
 * @title AereInsuranceFund — sweeps BadDebt from AereLendingMarket
 *                            insolvent liquidations
 * @notice Foundation-discretion fund that absorbs residual debt left on
 *         insolvent borrowers after the lending market's liquidation clamp.
 *         The fund:
 *           1. Accepts permissionless donations (any ERC-20).
 *           2. Per-market registration with an immutable lifetime coverage
 *              cap — once the cap is hit, no more coverage for that market
 *              (prevents a single misbehaving market from draining the
 *              entire fund).
 *           3. Per-market cooldown between coverages — gives governance
 *              time to react if a pattern of insolvent positions emerges
 *              (e.g. oracle attack).
 *           4. Foundation-only `coverBadDebt` — Foundation evaluates each
 *              BadDebt event off-chain (was it a legit market move, an
 *              oracle exploit, or an attack?) then chooses whether to cover.
 *           5. NO admin withdrawal. Funds are sticky. The only path out is
 *              `coverBadDebt` calling the market's `repayOnBehalfOf`.
 *
 *         FOUNDATION CANNOT DIRECT-WITHDRAW. The only way tokens leave this
 *         contract is `coverBadDebt` paying down debt on a REGISTERED market.
 *         However, market registration IS a Foundation-trust operation: a
 *         compromised Foundation multisig could register a malicious
 *         "market" whose `repayOnBehalfOf` impl drains the approved
 *         tokens. We mitigate this with a 7-day REGISTRATION_DELAY between
 *         `proposeMarket` and `registerMarket` — long enough for community
 *         observers + bots to call `cancelMarket` if a registration looks
 *         malicious. Cancellation is permissionless.
 *
 *         So the real trust model is:
 *           - Foundation key compromise + 7-day undetected delay
 *               = potential drip drain
 *           - Foundation key compromise alone
 *               = NO loss (community cancels the pending registration)
 *         This is materially weaker than "Foundation cannot rug" but
 *         materially stronger than "Foundation can rug anytime".
 *
 *         FUNDING STREAMS:
 *           - Direct donations (anyone)
 *           - AereSink optional 5% slice (Foundation can route a portion
 *             of buyback-and-burn to this fund instead, via a separate
 *             route — out of scope for this contract)
 *           - Protocol revenue dedications (Bank28 fees, etc.)
 */
contract AereInsuranceFund is ReentrancyGuard {

    /* ------------------------------- immutable ------------------------------- */

    address public immutable FOUNDATION;
    uint64  public immutable COOLDOWN_SECONDS;   // min seconds between coverages per market
    uint64  public constant REGISTRATION_DELAY = 7 days;

    /* --------------------------------- state -------------------------------- */

    struct Market {
        bool registered;
        uint256 lifetimeCap;       // max total coverage for this market over its lifetime
        uint256 covered;            // cumulative coverage paid for this market
        uint64 lastCoverageAt;
    }

    /// @notice market address → config + accounting
    mapping(address => Market) public markets;
    /// @notice token → balance held by the fund
    mapping(address => uint256) public balances;

    struct PendingRegistration {
        bool exists;
        uint256 lifetimeCap;
        uint64 readyAt;
    }
    /// @notice market → pending registration (proposed but not yet executed)
    mapping(address => PendingRegistration) public pending;

    /* --------------------------------- events ------------------------------- */

    event MarketProposed(address indexed market, uint256 lifetimeCap, uint64 readyAt);
    event MarketRegistered(address indexed market, uint256 lifetimeCap);
    event MarketCancelled(address indexed market, address indexed canceller, string reason);
    event Donated(address indexed donor, address indexed token, uint256 amount);
    event BadDebtCovered(address indexed market, address indexed borrower, uint256 amount);

    /* --------------------------------- errors ------------------------------- */

    error NotFoundation();
    error AlreadyRegistered();
    error NoPendingRegistration();
    error TimelockNotElapsed(uint64 readyAt);
    error UnknownMarket();
    error CapExceeded(uint256 wouldBe, uint256 cap);
    error CooldownActive(uint256 readyAt);
    error InsufficientBalance(uint256 have, uint256 want);
    error ZeroAddress();
    error ZeroAmount();
    error TransferFailed();
    error NoDebtToCover();

    /* ------------------------------ modifiers ------------------------------- */

    modifier onlyFoundation() {
        if (msg.sender != FOUNDATION) revert NotFoundation();
        _;
    }

    /* ----------------------------- constructor ------------------------------ */

    constructor(address foundation, uint64 cooldownSeconds) {
        if (foundation == address(0)) revert ZeroAddress();
        FOUNDATION = foundation;
        COOLDOWN_SECONDS = cooldownSeconds;
    }

    /* ---------------------------- market registry --------------------------- */

    /// @notice H4 fix: 2-step registration with 7-day delay.
    function proposeMarket(address market, uint256 lifetimeCap) external onlyFoundation {
        if (market == address(0)) revert ZeroAddress();
        if (markets[market].registered) revert AlreadyRegistered();
        uint64 readyAt = uint64(block.timestamp + REGISTRATION_DELAY);
        pending[market] = PendingRegistration({ exists: true, lifetimeCap: lifetimeCap, readyAt: readyAt });
        emit MarketProposed(market, lifetimeCap, readyAt);
    }

    /// @notice Execute a pending registration after timelock elapsed.
    ///         Foundation OR anyone may call (Foundation will normally
    ///         do it; permissionless lets anyone unblock if Foundation
    ///         stalls).
    function registerMarket(address market) external {
        PendingRegistration memory p = pending[market];
        if (!p.exists) revert NoPendingRegistration();
        if (block.timestamp < uint256(p.readyAt)) revert TimelockNotElapsed(p.readyAt);
        if (markets[market].registered) revert AlreadyRegistered();
        markets[market] = Market({
            registered: true,
            lifetimeCap: p.lifetimeCap,
            covered: 0,
            lastCoverageAt: 0
        });
        delete pending[market];
        emit MarketRegistered(market, p.lifetimeCap);
    }

    /// @notice Permissionless: cancel a pending registration. Use case:
    ///         community observers spot a malicious / wrong-address
    ///         registration during the 7-day delay and abort it before
    ///         it can be executed. Foundation can re-propose if it was
    ///         a false-positive cancellation.
    function cancelPendingMarket(address market, string calldata reason) external {
        if (!pending[market].exists) revert NoPendingRegistration();
        delete pending[market];
        emit MarketCancelled(market, msg.sender, reason);
    }

    /* -------------------------------- funding ------------------------------- */

    function donate(address token, uint256 amount) external nonReentrant {
        if (amount == 0) revert ZeroAmount();
        if (token == address(0)) revert ZeroAddress();
        if (!IERC20(token).transferFrom(msg.sender, address(this), amount)) revert TransferFailed();
        balances[token] += amount;
        emit Donated(msg.sender, token, amount);
    }

    /* ------------------------------ coverage -------------------------------- */

    /// @notice Foundation-only: clear `amount` of `borrower`'s debt on
    ///         `market` by calling `market.repayOnBehalfOf`. Enforces
    ///         lifetime cap + cooldown. Reverts if borrower has no debt.
    function coverBadDebt(address market, address borrower, uint256 amount)
        external onlyFoundation nonReentrant
    {
        Market storage m = markets[market];
        if (!m.registered) revert UnknownMarket();
        if (amount == 0) revert ZeroAmount();
        if (borrower == address(0)) revert ZeroAddress();
        if (m.lastCoverageAt != 0 && block.timestamp < uint256(m.lastCoverageAt) + COOLDOWN_SECONDS) {
            revert CooldownActive(uint256(m.lastCoverageAt) + COOLDOWN_SECONDS);
        }
        // Verify the borrower actually has outstanding debt (defensive — protects
        // the fund from being asked to "cover" a healthy account by mistake) and
        // clamp the request down to what is actually owed.
        uint256 owed = IAereLendingMarketForFund(market).debtBalance(borrower);
        if (owed == 0) revert NoDebtToCover();
        if (amount > owed) amount = owed;
        // Cap check runs on the CLAMPED amount (what will really be paid), not the
        // pre-clamp request. Otherwise a legit coverage that clamps under the
        // remaining cap would be spuriously blocked. Semantics are unchanged:
        // cumulative lifetime coverage per market still stays <= lifetimeCap.
        if (m.covered + amount > m.lifetimeCap) {
            revert CapExceeded(m.covered + amount, m.lifetimeCap);
        }

        address debtToken = IAereLendingMarketForFund(market).DEBT_TOKEN();
        if (balances[debtToken] < amount) revert InsufficientBalance(balances[debtToken], amount);
        balances[debtToken] -= amount;

        // Approve the market to pull tokens via repayOnBehalfOf, then call.
        IERC20(debtToken).approve(market, 0);
        IERC20(debtToken).approve(market, amount);
        IAereLendingMarketForFund(market).repayOnBehalfOf(borrower, amount);

        m.covered += amount;
        m.lastCoverageAt = uint64(block.timestamp);
        emit BadDebtCovered(market, borrower, amount);
    }

    /* ---------------------------------- views ------------------------------- */

    function coverageRemaining(address market) external view returns (uint256) {
        Market memory m = markets[market];
        if (!m.registered) return 0;
        return m.covered >= m.lifetimeCap ? 0 : m.lifetimeCap - m.covered;
    }

    function cooldownReady(address market) external view returns (bool) {
        Market memory m = markets[market];
        if (!m.registered) return false;
        if (m.lastCoverageAt == 0) return true;
        return block.timestamp >= uint256(m.lastCoverageAt) + COOLDOWN_SECONDS;
    }
}
