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

// =============================================================
//                       INTERFACES
// =============================================================

interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);
}

interface IIntelliSilverToken is IERC20 {
    function mint(address to, uint256 amount) external;
    function burn(uint256 amount) external;
}

interface AggregatorV3Interface {
    function decimals() external view returns (uint8);
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
}

// =============================================================
//                   LIBRARIES & ABSTRACTS
// =============================================================

library SafeERC20 {
    error SafeERC20FailedOperation(address token);

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        (bool success, bytes memory returndata) = address(token).call(data);
        if (!success) {
            if (returndata.length > 0) {
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert SafeERC20FailedOperation(address(token));
            }
        }
        if (returndata.length > 0) {
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }
}

abstract contract Pausable is Context {
    event Paused(address account);
    event Unpaused(address account);

    bool private _paused;

    modifier whenNotPaused() {
        require(!paused(), "Pausable: paused");
        _;
    }

    function paused() public view virtual returns (bool) {
        return _paused;
    }

    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    function _unpause() internal virtual {
        require(paused(), "Pausable: not paused");
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

abstract contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    modifier nonReentrant() {
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }

    constructor() {
        _status = _NOT_ENTERED;
    }
}

// =============================================================
//             INTELLISILVER OTC RFQ CONTRACT (UPDATED)
// =============================================================

contract IntelliSilverOTC_RFQ is Pausable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeERC20 for IIntelliSilverToken;

    IIntelliSilverToken public immutable ISLV_TOKEN;
    IERC20 public immutable IUSD_TOKEN;

    address public immutable admin;
    address public treasuryAddress;

    AggregatorV3Interface public silverPriceAggregator;

    enum PriceSource { Chainlink, Manual }
    enum PositionType { Outright, Spread }

    struct Leg {
        uint256 maturity;
        uint256 entryPrice;
        uint256 amountISLV;
        bool isLong;
    }

    struct Position {
        uint256 positionId;
        address owner;
        uint256 purchaseTimestamp;
        bool isActive;
        uint256 closeTimestamp;
        PositionType positionType;
        uint256 stopLossPrice;    // Outright only
        uint256 takeProfitPrice;  // Outright only
        Leg[] legs;
    }

    struct InputLeg {
        uint256 maturity;
        uint256 amountISLV;
        bool isLong;
    }

    mapping(address => mapping(uint256 => Position)) public userPositions;
    mapping(address => uint256) public nextPositionIdForUser;
    mapping(address => bool) public isKeeper;

    uint256 public treasuryFeeBPS;     // e.g., 50 = 0.50%
    uint256 public annualRateBPS;      // carry rate
    uint256 public minMaturityOffset;
    uint256 public maxMaturityOffset;
    uint256 public oracleMaxAge;       // stale threshold in seconds (0 disables)
    PriceSource public spotSource;
    uint256 public manualSpotPrice;    // 18 decimals

    event PositionOpened(
        address indexed user,
        uint256 indexed positionId,
        PositionType pType,
        Leg[] legs,
        uint256 sl,
        uint256 tp
    );

    event PositionClosed(
        address indexed user,
        uint256 indexed positionId,
        int256 pnlValue,
        address payoutToken,
        string reason
    );

    event KeeperUpdated(address indexed keeper, bool isNowKeeper);
    event AdminSettingsChanged(address indexed admin, string setting, uint256 value);
    event AdminAddressChanged(address indexed admin, string setting, address addr);

    modifier onlyAdmin() {
        require(msg.sender == admin, "ISLVOTC: Caller is not admin");
        _;
    }

    constructor(
        address _islv,
        address _iusd,
        address _silverAggregator,
        address _admin,
        address _treasury,
        uint256 _treasuryFeeBPS
    ) {
        require(
            _islv != address(0) &&
            _iusd != address(0) &&
            _silverAggregator != address(0) &&
            _admin != address(0) &&
            _treasury != address(0),
            "ISLVOTC: Invalid address"
        );
        require(_treasuryFeeBPS <= 1000, "ISLVOTC: Fee too high");

        ISLV_TOKEN = IIntelliSilverToken(_islv);
        IUSD_TOKEN = IERC20(_iusd);
        silverPriceAggregator = AggregatorV3Interface(_silverAggregator);

        admin = _admin;
        treasuryAddress = _treasury;
        treasuryFeeBPS = _treasuryFeeBPS;

        minMaturityOffset = 1 days;
        maxMaturityOffset = 365 days * 5;
        oracleMaxAge = 3600; // 1 hour
        spotSource = PriceSource.Chainlink;
        annualRateBPS = 550; // 5.5%
    }

    // =============================================================
    //                      PRICING LOGIC
    // =============================================================

    function _getSpotPrice() internal view returns (uint256) {
        if (spotSource == PriceSource.Manual) {
            require(manualSpotPrice > 0, "ISLVOTC: Manual price not set");
            return manualSpotPrice;
        }

        (, int256 price, , uint256 updatedAt, ) = silverPriceAggregator.latestRoundData();
        require(price > 0, "ISLVOTC: Invalid oracle price");

        if (oracleMaxAge > 0) {
            require(block.timestamp - updatedAt < oracleMaxAge, "ISLVOTC: Oracle price is stale");
        }

        // Scale to 18 decimals
        uint8 d = silverPriceAggregator.decimals();
        require(d <= 18, "ISLVOTC: Oracle decimals too high");
        return uint256(price) * (10 ** (18 - d));
    }

    function priceFor(uint256 maturity) public view returns (uint256) {
        uint256 spot = _getSpotPrice();
        if (maturity == 0 || maturity <= block.timestamp) {
            return spot;
        }

        uint256 timeToMaturity = maturity - block.timestamp;
        uint256 interest = (spot * annualRateBPS * timeToMaturity) / (10000 * 365 days);
        return spot + interest;
    }

    // =============================================================
    //                     VALIDATION (OIL/GOLD STYLE)
    // =============================================================

    function _validateTradeConditions(uint256 currentPrice, uint256 amountISLV, uint256 sl, uint256 tp) internal view {
        if (sl > 0) require(sl < currentPrice, "ISLVOTC: SL must be BELOW current price for Longs");
        if (tp > 0) require(tp > currentPrice, "ISLVOTC: TP must be ABOVE current price for Longs");

        // Allowance lock so keeper can close + burn later
        uint256 allowance = ISLV_TOKEN.allowance(msg.sender, address(this));
        require(allowance >= amountISLV, "ISLVOTC: Insufficient ISLV allowance (approve first)");
    }

    function _validateMaturity(uint256 maturity) private view {
        require(maturity > block.timestamp + minMaturityOffset, "ISLVOTC: Maturity too soon");
        require(maturity < block.timestamp + maxMaturityOffset, "ISLVOTC: Maturity too far");
    }

    // =============================================================
    //                     OPENING POSITIONS
    // =============================================================

    function openPositionWithIUSD(
        uint256 maturity,
        uint256 amount,
        uint256 sl,
        uint256 tp,
        uint256 maxCost
    ) external nonReentrant whenNotPaused {
        require(amount > 0, "ISLVOTC: Amount must be positive");
        _validateMaturity(maturity);

        uint256 entryPrice = priceFor(maturity);
        _validateTradeConditions(entryPrice, amount, sl, tp);

        Leg[] memory legs = new Leg[](1);
        legs[0] = Leg({ maturity: maturity, entryPrice: entryPrice, amountISLV: amount, isLong: true });

        uint256 principal = (entryPrice * amount) / 1e18;
        _executeTrade(int256(principal), address(IUSD_TOKEN), maxCost, 0);

        ISLV_TOKEN.mint(msg.sender, amount);
        _createPosition(msg.sender, legs, PositionType.Outright, sl, tp);
    }

    function openPositionWithEth(
        uint256 maturity,
        uint256 amount,
        uint256 sl,
        uint256 tp,
        uint256 maxCost
    ) external payable nonReentrant whenNotPaused {
        require(amount > 0, "ISLVOTC: Amount must be positive");
        _validateMaturity(maturity);

        uint256 entryPrice = priceFor(maturity);
        _validateTradeConditions(entryPrice, amount, sl, tp);

        Leg[] memory legs = new Leg[](1);
        legs[0] = Leg({ maturity: maturity, entryPrice: entryPrice, amountISLV: amount, isLong: true });

        uint256 principal = (entryPrice * amount) / 1e18;
        _executeTrade(int256(principal), address(0), maxCost, 0);

        ISLV_TOKEN.mint(msg.sender, amount);
        _createPosition(msg.sender, legs, PositionType.Outright, sl, tp);
    }

    function openSpreadPosition(
        InputLeg[] calldata inputLegs,
        address paymentToken, // address(0)=ETH else IUSD
        uint256 maxCost,
        uint256 minCredit
    ) external payable nonReentrant whenNotPaused {
        require(inputLegs.length > 1, "ISLVOTC: Spread needs multiple legs");

        Leg[] memory legs = new Leg[](inputLegs.length);
        int256 netCost = 0;
        uint256 totalToMint = 0;
        int256 netExposure = 0; // blocks net short spreads

        for (uint256 i = 0; i < inputLegs.length; i++) {
            InputLeg calldata L = inputLegs[i];
            require(L.amountISLV > 0, "ISLVOTC: Leg amount positive");
            _validateMaturity(L.maturity);

            uint256 px = priceFor(L.maturity);
            legs[i] = Leg({ maturity: L.maturity, entryPrice: px, amountISLV: L.amountISLV, isLong: L.isLong });

            int256 legValue = int256((px * L.amountISLV) / 1e18);

            if (L.isLong) {
                netCost += legValue;
                totalToMint += L.amountISLV;
                netExposure += int256(L.amountISLV);
            } else {
                netCost -= legValue;
                netExposure -= int256(L.amountISLV);
            }
        }

        require(netExposure >= 0, "ISLVOTC: Net short spreads not allowed");

        if (totalToMint > 0) {
            uint256 allowance = ISLV_TOKEN.allowance(msg.sender, address(this));
            require(allowance >= totalToMint, "ISLVOTC: Insufficient ISLV allowance for spread");
        }

        _executeTrade(netCost, paymentToken, maxCost, minCredit);

        if (totalToMint > 0) {
            ISLV_TOKEN.mint(msg.sender, totalToMint);
        }

        _createPosition(msg.sender, legs, PositionType.Spread, 0, 0);
    }

    // =============================================================
    //                     CLOSING POSITIONS
    // =============================================================

    function triggerCloseByKeeper(address user, uint256 posId, address payoutToken)
        external
        nonReentrant
        whenNotPaused
    {
        require(isKeeper[msg.sender], "ISLVOTC: Caller is not keeper");

        Position storage pos = userPositions[user][posId];
        require(pos.isActive, "ISLVOTC: Position not active");

        if (pos.positionType == PositionType.Outright && pos.legs.length > 0) {
            uint256 currentPrice = priceFor(pos.legs[0].maturity);
            bool slHit = (pos.stopLossPrice > 0 && currentPrice <= pos.stopLossPrice);
            bool tpHit = (pos.takeProfitPrice > 0 && currentPrice >= pos.takeProfitPrice);
            require(slHit || tpHit, "ISLVOTC: SL/TP not hit yet");
        }

        _closePosition(user, posId, payoutToken, "Keeper Trigger");
    }

    function closePosition(uint256 positionId, address payoutToken)
        external
        payable
        nonReentrant
        whenNotPaused
    {
        _closePosition(msg.sender, positionId, payoutToken, "User Manual Close");
    }

    function _closePosition(address user, uint256 posId, address payoutToken, string memory reason) private {
        Position storage pos = userPositions[user][posId];
        require(pos.owner == user && pos.isActive, "ISLVOTC: Position invalid");

        int256 pnlValue = 0;
        uint256 totalBurn = 0;

        for (uint256 i = 0; i < pos.legs.length; i++) {
            Leg storage leg = pos.legs[i];
            uint256 currentPrice = priceFor(leg.maturity);

            int256 pnl = (int256(currentPrice) - int256(leg.entryPrice)) * int256(leg.amountISLV) / 1e18;

            if (leg.isLong) {
                pnlValue += pnl;
                totalBurn += leg.amountISLV;
            } else {
                pnlValue -= pnl;
            }
        }

        pos.isActive = false;
        pos.closeTimestamp = block.timestamp;

        if (totalBurn > 0) {
            ISLV_TOKEN.safeTransferFrom(user, address(this), totalBurn);
            ISLV_TOKEN.burn(totalBurn);
        }

        uint256 fee = (pnlValue > 0 ? (uint256(pnlValue) * treasuryFeeBPS) / 10000 : 0);
        int256 netSettlement = pnlValue - int256(fee);

        if (netSettlement > 0) {
            uint256 profit = uint256(netSettlement);
            if (payoutToken == address(0)) {
                (bool sent, ) = payable(user).call{value: profit}("");
                require(sent, "ISLVOTC: ETH profit send failed");
            } else {
                IUSD_TOKEN.safeTransfer(user, profit);
            }
        } else if (netSettlement < 0) {
            uint256 loss = uint256(-netSettlement);
            if (payoutToken == address(0)) {
                require(msg.value >= loss, "ISLVOTC: Insufficient ETH for loss");
                (bool sent, ) = payable(treasuryAddress).call{value: loss}("");
                require(sent, "ISLVOTC: ETH loss transfer failed");
            } else {
                // Loss paid by USER to TREASURY (Oil/Gold style)
                IUSD_TOKEN.safeTransferFrom(user, treasuryAddress, loss);
            }
        }

        if (fee > 0) {
            if (payoutToken == address(0)) {
                (bool sent, ) = payable(treasuryAddress).call{value: fee}("");
                require(sent, "ISLVOTC: ETH fee transfer failed");
            } else {
                IUSD_TOKEN.safeTransfer(treasuryAddress, fee);
            }
        }

        emit PositionClosed(user, posId, pnlValue, payoutToken, reason);
    }

    // =============================================================
    //                     CASHFLOW EXECUTION
    // =============================================================

    function _executeTrade(int256 netCost, address paymentToken, uint256 maxCost, uint256 minCredit) internal {
        if (netCost == 0) return;

        uint256 absCost = netCost > 0 ? uint256(netCost) : uint256(-netCost);
        uint256 fee = (absCost * treasuryFeeBPS) / 10000;

        if (netCost > 0) {
            uint256 totalCost = uint256(netCost) + fee;
            require(totalCost <= maxCost, "ISLVOTC: Slippage: Cost > maxCost");

            if (paymentToken == address(0)) {
                require(msg.value >= totalCost, "ISLVOTC: Insufficient ETH sent");
                (bool sent, ) = payable(treasuryAddress).call{value: totalCost}("");
                require(sent, "ISLVOTC: ETH transfer failed");
            } else {
                IUSD_TOKEN.safeTransferFrom(msg.sender, treasuryAddress, totalCost);
            }
        } else {
            uint256 totalCredit = uint256(-netCost);
            require(totalCredit >= minCredit, "ISLVOTC: Slippage: Credit < minCredit");

            uint256 creditAfterFee = totalCredit > fee ? totalCredit - fee : 0;

            if (paymentToken == address(0)) {
                (bool creditSent, ) = payable(msg.sender).call{value: creditAfterFee}("");
                require(creditSent, "ISLVOTC: ETH credit transfer failed");

                (bool feeSent, ) = payable(treasuryAddress).call{value: fee}("");
                require(feeSent, "ISLVOTC: ETH fee transfer failed");
            } else {
                // FIX: transfer(), not transferFrom(address(this), ...)
                IUSD_TOKEN.safeTransfer(treasuryAddress, fee);
                IUSD_TOKEN.safeTransfer(msg.sender, creditAfterFee);
            }
        }
    }

    // =============================================================
    //                     POSITION BOOKKEEPING
    // =============================================================

    function _createPosition(address u, Leg[] memory legs, PositionType pt, uint256 sl, uint256 tp) private {
        uint256 positionId = nextPositionIdForUser[u];
        Position storage newPos = userPositions[u][positionId];

        newPos.positionId = positionId;
        newPos.owner = u;
        newPos.purchaseTimestamp = block.timestamp;
        newPos.isActive = true;
        newPos.positionType = pt;
        newPos.stopLossPrice = sl;
        newPos.takeProfitPrice = tp;

        for (uint256 i = 0; i < legs.length; i++) {
            newPos.legs.push(legs[i]);
        }

        nextPositionIdForUser[u]++;

        emit PositionOpened(u, positionId, pt, legs, sl, tp);
    }

    // =============================================================
    //                      ADMIN & VIEWS
    // =============================================================

    function setOracleMaxAge(uint256 v) external onlyAdmin {
        oracleMaxAge = v;
        emit AdminSettingsChanged(msg.sender, "oracleMaxAge", v);
    }

    function setPriceSource(PriceSource s) external onlyAdmin {
        spotSource = s;
        emit AdminSettingsChanged(msg.sender, "spotSource", uint256(uint8(s)));
    }

    function setManualPrice(uint256 p) external onlyAdmin {
        manualSpotPrice = p;
        emit AdminSettingsChanged(msg.sender, "manualSpotPrice", p);
    }

    function setMaturityOffsets(uint256 min, uint256 max) external onlyAdmin {
        minMaturityOffset = min;
        maxMaturityOffset = max;
        emit AdminSettingsChanged(msg.sender, "minMaturityOffset", min);
        emit AdminSettingsChanged(msg.sender, "maxMaturityOffset", max);
    }

    function setTreasury(address t) external onlyAdmin {
        require(t != address(0), "ISLVOTC: Zero address");
        treasuryAddress = t;
        emit AdminAddressChanged(msg.sender, "treasuryAddress", t);
    }

    function setAnnualRateBPS(uint256 r) external onlyAdmin {
        annualRateBPS = r;
        emit AdminSettingsChanged(msg.sender, "annualRateBPS", r);
    }

    function setTreasuryFee(uint256 f) external onlyAdmin {
        require(f <= 1000, "ISLVOTC: Fee too high");
        treasuryFeeBPS = f;
        emit AdminSettingsChanged(msg.sender, "treasuryFeeBPS", f);
    }

    function setKeeper(address k, bool s) external onlyAdmin {
        isKeeper[k] = s;
        emit KeeperUpdated(k, s);
    }

    function rescueTokens(address t, address to, uint256 a) external onlyAdmin {
        require(t != address(ISLV_TOKEN) && t != address(IUSD_TOKEN), "ISLVOTC: Cannot rescue core tokens");
        IERC20(t).safeTransfer(to, a);
    }

    function pause() external onlyAdmin { _pause(); }
    function unpause() external onlyAdmin { _unpause(); }

    function getPositionById(address owner, uint256 positionId) external view returns (Position memory) {
        return userPositions[owner][positionId];
    }

    receive() external payable {}
}
