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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "./AereVaultRelayer.sol";
import "./AereSolverRegistry.sol";

/**
 * @title AereSettlement
 * @notice Batch-auction MEV-resistant DEX for AERE.
 *
 *   Users sign EIP-712 orders off-chain (no gas). An authorised solver collects
 *   open orders, finds a uniform clearing price per token pair, and submits the
 *   whole batch atomically via `settle()`. The contract:
 *     1. Verifies each user's EIP-712 signature.
 *     2. Checks each order's deadline and per-token cap.
 *     3. Pulls each order's sellAmount via AereVaultRelayer (users approved that contract).
 *     4. Routes the inter-order trades atomically — coincidence-of-wants matches
 *        at the uniform price.
 *     5. Pays each user their executedBuyAmount (which must be ≥ minBuyAmount).
 *
 *   MEV protection comes from atomicity: the whole batch executes in one transaction,
 *   so no actor can insert a sandwich tx between two orders in the batch.
 *
 *   Permissioned solvers in Phase 1 (AereSolverRegistry-controlled). Phase 2 adds
 *   permissionless bonded-solver entry.
 *
 *   This is a simplified port of CoW Protocol's GPv2Settlement — captures the core
 *   batch-atomic-clearing primitive without the full feature set (partial fills,
 *   ERC-1271, balance modes, dynamic fees). Audit-grade hardening deferred to Phase 2.
 */
contract AereSettlement is Ownable, ReentrancyGuard, EIP712 {
    using SafeERC20 for IERC20;

    AereVaultRelayer public immutable vaultRelayer;
    AereSolverRegistry public immutable solverRegistry;

    /// EIP-712 order struct.
    /// Buy/sell tokens, amounts, deadline, salt (for replay protection).
    struct Order {
        address user;
        IERC20 sellToken;
        IERC20 buyToken;
        uint256 sellAmount;     // exact sell amount (fill-or-kill in v1)
        uint256 minBuyAmount;   // user-specified slippage floor
        uint64  validTo;        // unix deadline
        uint256 salt;           // unique-per-user replay nonce
    }

    /// What the solver claims it will execute for this order, given the batch's
    /// uniform clearing price. Must satisfy executedBuyAmount >= order.minBuyAmount.
    struct Execution {
        uint256 executedBuyAmount;
    }

    bytes32 private constant ORDER_TYPEHASH = keccak256(
        "Order(address user,address sellToken,address buyToken,uint256 sellAmount,uint256 minBuyAmount,uint64 validTo,uint256 salt)"
    );

    /// Replay protection: orderHash → consumed.
    mapping(bytes32 => bool) public filled;

    event Settled(address indexed solver, uint256 orderCount, uint256 timestamp);
    event OrderFilled(
        bytes32 indexed orderHash,
        address indexed user,
        address sellToken,
        address buyToken,
        uint256 sellAmount,
        uint256 buyAmount
    );

    modifier onlySolver() {
        require(solverRegistry.isSolver(msg.sender), "Settlement: not a solver");
        _;
    }

    constructor(AereVaultRelayer _vault, AereSolverRegistry _registry) EIP712("AereSettlement", "1") {
        vaultRelayer = _vault;
        solverRegistry = _registry;
    }

    /// Compute the canonical hash of an order for EIP-712 verification.
    function orderHash(Order calldata o) public view returns (bytes32) {
        bytes32 structHash = keccak256(abi.encode(
            ORDER_TYPEHASH,
            o.user,
            address(o.sellToken),
            address(o.buyToken),
            o.sellAmount,
            o.minBuyAmount,
            o.validTo,
            o.salt
        ));
        return _hashTypedDataV4(structHash);
    }

    /**
     * @notice Settle a batch of orders atomically.
     * @dev Solver must provide one signature per order and one execution amount per order.
     *      The solver is responsible for sourcing buyToken liquidity (via internal CoW
     *      matching between orders, or by interacting with AereSwap, or by holding its
     *      own inventory). After the batch, every user must have received at least their
     *      minBuyAmount of buyToken.
     *
     *      Implementation: this contract pulls sellTokens FROM each user TO the solver
     *      via the VaultRelayer, then expects the solver to push buyTokens to each user.
     *      Solver supplies buyTokens from its working capital + post-batch arbitrage.
     */
    function settle(
        Order[] calldata orders,
        bytes[] calldata signatures,
        Execution[] calldata executions
    ) external onlySolver nonReentrant {
        require(orders.length == signatures.length && orders.length == executions.length,
            "Settlement: length mismatch");
        require(orders.length > 0, "Settlement: empty batch");

        for (uint256 i = 0; i < orders.length; i++) {
            Order calldata o = orders[i];
            require(block.timestamp <= o.validTo, "Settlement: order expired");
            require(executions[i].executedBuyAmount >= o.minBuyAmount, "Settlement: below minBuy");

            bytes32 h = orderHash(o);
            require(!filled[h], "Settlement: already filled");

            // Verify EIP-712 signature.
            address signer = ECDSA.recover(h, signatures[i]);
            require(signer == o.user, "Settlement: bad signature");

            filled[h] = true;

            // 1. Pull sellAmount from user → solver. (VaultRelayer has approval.)
            vaultRelayer.transferFromUser(o.sellToken, o.user, msg.sender, o.sellAmount);

            // 2. Solver MUST push executedBuyAmount of buyToken to user.
            //    The settlement contract orchestrates by pulling from solver and pushing to user.
            //    Solver must approve THIS contract for buyToken first (or use internal CoW match
            //    where the buyToken flows from another order's sellToken — handled outside this loop).
            o.buyToken.safeTransferFrom(msg.sender, o.user, executions[i].executedBuyAmount);

            emit OrderFilled(h, o.user, address(o.sellToken), address(o.buyToken), o.sellAmount, executions[i].executedBuyAmount);
        }

        emit Settled(msg.sender, orders.length, block.timestamp);
    }

    /// User can cancel an open order on-chain (rare; mostly orders expire naturally).
    function cancelOrder(Order calldata o) external {
        require(msg.sender == o.user, "Settlement: not order owner");
        filled[orderHash(o)] = true; // mark consumed → solver can't fill
    }

    /// Check whether an order is still openable.
    function isOrderFillable(Order calldata o) external view returns (bool) {
        return !filled[orderHash(o)] && block.timestamp <= o.validTo;
    }
}
