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

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC2981.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title AereNFTMarketplace
 * @notice On-chain NFT marketplace for AERE Network with EIP-2981 royalty support.
 * @dev Sellers list owned ERC-721 tokens for a fixed price in native AERE.
 *      Buyers purchase atomically: protocol fee → fee recipient, royalty → creator,
 *      remainder → seller. Buyers can also place expiring offers on any token.
 *      Marketplace must be approved (setApprovalForAll or approve) before listing.
 */
contract AereNFTMarketplace is Ownable, ReentrancyGuard {
    uint96 public constant MAX_FEE_BPS = 1000; // 10%
    uint96 public protocolFeeBps = 250;        // 2.5%
    address public feeRecipient;

    struct Listing {
        address seller;
        uint256 price;
        uint64 expiresAt; // 0 = never
    }

    struct Offer {
        address buyer;
        uint256 price;
        uint64 expiresAt;
    }

    // collection => tokenId => Listing
    mapping(address => mapping(uint256 => Listing)) public listings;
    // collection => tokenId => buyer => Offer
    mapping(address => mapping(uint256 => mapping(address => Offer))) public offers;
    // escrowed offer balances per buyer (refundable)
    mapping(address => uint256) public pendingWithdrawals;

    event Listed(address indexed collection, uint256 indexed tokenId, address indexed seller, uint256 price, uint64 expiresAt);
    event ListingCancelled(address indexed collection, uint256 indexed tokenId, address indexed seller);
    event Sold(address indexed collection, uint256 indexed tokenId, address seller, address indexed buyer, uint256 price);
    event OfferPlaced(address indexed collection, uint256 indexed tokenId, address indexed buyer, uint256 price, uint64 expiresAt);
    event OfferCancelled(address indexed collection, uint256 indexed tokenId, address indexed buyer);
    event OfferAccepted(address indexed collection, uint256 indexed tokenId, address seller, address indexed buyer, uint256 price);
    event ProtocolFeeUpdated(uint96 newFeeBps);
    event FeeRecipientUpdated(address newRecipient);

    constructor(address _feeRecipient) {
        require(_feeRecipient != address(0), "zero");
        feeRecipient = _feeRecipient;
    }

    // ── Listings ────────────────────────────────────────────────
    function list(address collection, uint256 tokenId, uint256 price, uint64 expiresAt) external {
        require(price > 0, "price=0");
        IERC721 nft = IERC721(collection);
        require(nft.ownerOf(tokenId) == msg.sender, "not owner");
        require(
            nft.getApproved(tokenId) == address(this) || nft.isApprovedForAll(msg.sender, address(this)),
            "not approved"
        );
        listings[collection][tokenId] = Listing(msg.sender, price, expiresAt);
        emit Listed(collection, tokenId, msg.sender, price, expiresAt);
    }

    function cancelListing(address collection, uint256 tokenId) external {
        Listing memory l = listings[collection][tokenId];
        require(l.seller == msg.sender, "not seller");
        delete listings[collection][tokenId];
        emit ListingCancelled(collection, tokenId, msg.sender);
    }

    function buy(address collection, uint256 tokenId) external payable nonReentrant {
        Listing memory l = listings[collection][tokenId];
        require(l.seller != address(0), "not listed");
        require(l.expiresAt == 0 || block.timestamp <= l.expiresAt, "expired");
        require(msg.value == l.price, "bad value");

        delete listings[collection][tokenId];
        _settleSale(collection, tokenId, l.seller, msg.sender, l.price);
        emit Sold(collection, tokenId, l.seller, msg.sender, l.price);
    }

    // ── Offers ──────────────────────────────────────────────────
    function placeOffer(address collection, uint256 tokenId, uint64 expiresAt) external payable nonReentrant {
        require(msg.value > 0, "price=0");
        require(expiresAt > block.timestamp, "expired");
        Offer memory existing = offers[collection][tokenId][msg.sender];
        if (existing.price > 0) {
            // refund prior offer to escrow
            pendingWithdrawals[msg.sender] += existing.price;
        }
        offers[collection][tokenId][msg.sender] = Offer(msg.sender, msg.value, expiresAt);
        emit OfferPlaced(collection, tokenId, msg.sender, msg.value, expiresAt);
    }

    function cancelOffer(address collection, uint256 tokenId) external nonReentrant {
        Offer memory o = offers[collection][tokenId][msg.sender];
        require(o.price > 0, "no offer");
        delete offers[collection][tokenId][msg.sender];
        pendingWithdrawals[msg.sender] += o.price;
        emit OfferCancelled(collection, tokenId, msg.sender);
    }

    function acceptOffer(address collection, uint256 tokenId, address buyer) external nonReentrant {
        IERC721 nft = IERC721(collection);
        require(nft.ownerOf(tokenId) == msg.sender, "not owner");
        Offer memory o = offers[collection][tokenId][buyer];
        require(o.price > 0, "no offer");
        require(block.timestamp <= o.expiresAt, "expired");

        delete offers[collection][tokenId][buyer];
        // active listing (if any) becomes invalid after transfer; clear it
        if (listings[collection][tokenId].seller == msg.sender) {
            delete listings[collection][tokenId];
        }
        _settleSale(collection, tokenId, msg.sender, buyer, o.price);
        emit OfferAccepted(collection, tokenId, msg.sender, buyer, o.price);
    }

    function withdraw() external nonReentrant {
        uint256 amount = pendingWithdrawals[msg.sender];
        require(amount > 0, "nothing");
        pendingWithdrawals[msg.sender] = 0;
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok, "transfer");
    }

    // ── Internal settlement ─────────────────────────────────────
    function _settleSale(address collection, uint256 tokenId, address seller, address buyer, uint256 price) internal {
        IERC721(collection).safeTransferFrom(seller, buyer, tokenId);

        uint256 fee = (price * protocolFeeBps) / 10000;
        uint256 royalty = 0;
        address royaltyReceiver = address(0);

        try IERC2981(collection).royaltyInfo(tokenId, price) returns (address r, uint256 amt) {
            if (r != address(0) && amt > 0 && amt + fee <= price) {
                royaltyReceiver = r;
                royalty = amt;
            }
        } catch { /* collection doesn't implement EIP-2981 */ }

        uint256 sellerProceeds = price - fee - royalty;

        if (fee > 0) _send(feeRecipient, fee);
        if (royalty > 0) _send(royaltyReceiver, royalty);
        _send(seller, sellerProceeds);
    }

    function _send(address to, uint256 amount) internal {
        (bool ok, ) = to.call{value: amount}("");
        if (!ok) {
            // fall back to escrow so a malicious receiver can't block sales
            pendingWithdrawals[to] += amount;
        }
    }

    // ── Admin ───────────────────────────────────────────────────
    function setProtocolFee(uint96 newFeeBps) external onlyOwner {
        require(newFeeBps <= MAX_FEE_BPS, "too high");
        protocolFeeBps = newFeeBps;
        emit ProtocolFeeUpdated(newFeeBps);
    }

    function setFeeRecipient(address newRecipient) external onlyOwner {
        require(newRecipient != address(0), "zero");
        feeRecipient = newRecipient;
        emit FeeRecipientUpdated(newRecipient);
    }
}
