<?php
namespace DragonX;
use Tigusigalpa\BingX\BingxClient;

class Manager
{
    public function __construct()
    {
        $configFile = file_get_contents(__DIR__ . '/../config.md');
        //Break key=value pairs into an array
        $config = explode("\n", $configFile);
        foreach ($config as $line) {
            if (strpos($line, '=') !== false) {
                list($key, $value) = explode('=', $line, 2);
                $key = trim($key);
                $value = trim($value);
                //Define constants for each key=value pair
                define(strtoupper($key), $value);
            }
        }
    }

    public function trade(){
        try{
        $bingx = $this->buildBingxClient();
        $response = $this->placeTrade($bingx, 'sell', TRADE_SYMBOL, []);
        return $response;
        } catch (\Throwable $e) {
            $this->log('requests.log', [
                'error'   => 'uncaught_exception',
                'message' => $e->getMessage(),
            ]);
            http_response_code(500);
            echo 'error: ' . $e->getMessage();
        }
    }

    public function close($symbol){
        try{
            $bingx = $this->buildBingxClient();
            $closeBuilder = $bingx->trade()->closeAllPositions($symbol);
            return $closeBuilder;
        } catch (\Throwable $e) {
            $this->log('requests.log', [
                'error'   => 'uncaught_exception',
                'message' => $e->getMessage(),
            ]);
            http_response_code(500);
            return $e->getMessage();
        }
    }

    public function processRequest()
    {

        $data = $this->captureRequestData();

        $bingxIPs = [BINGX_IP_1, BINGX_IP_2, BINGX_IP_3, BINGX_IP_4];
        $clientIP = $data['server']['HTTP_X_FORWARDED_FOR'];
        if (!in_array($clientIP, $bingxIPs)) {
            http_response_code(403);
            exit;
        }

        $body = $data['body'];
        $bodyData = [];

        if (is_array($body)) {
            $bodyData = $body;
        } elseif (!empty($body) && is_string($body)) {
            $decoded = json_decode($body, true);
            if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
                $bodyData = $decoded;
            } else {
                foreach (explode(',', $body) as $item) {
                    if (strpos($item, ':') !== false) {
                        list($k, $v) = explode(':', $item, 2);
                        $bodyData[trim($k)] = trim($v);
                    }
                }
            }
        }

        if (empty($bodyData)) {
            $this->log('requests.log', ['error' => 'empty_body', 'raw' => $data['raw']]);
            http_response_code(400);
            exit;
        }

        $this->log('requests.log', $bodyData);

        // Determine signal
        if (intval($bodyData['buy'] ?? 0) === 1) {
            $signal = 'buy';
        } elseif (intval($bodyData['sell'] ?? 0) === 1) {
            $signal = 'sell';
        } else {
            $this->log('requests.log', ['error' => 'unknown_signal', 'body' => $bodyData]);
            http_response_code(422);
            exit;
        }

        $symbol = TRADE_SYMBOL;

        try {
            sleep((int)(defined('TRADE_DELAY') ? TRADE_DELAY : 20));

            $bingx = $this->buildBingxClient();
            $response = $this->placeTrade($bingx, $signal, $symbol);

            http_response_code(200);
            return $response;
        } catch (\Throwable $e) {
            $this->log('requests.log', [
                'error'   => 'uncaught_exception',
                'signal'  => $signal,
                'symbol'  => $symbol,
                'message' => $e->getMessage(),
            ]);
            http_response_code(500);
            echo 'error: ' . $e->getMessage();
        }
    }

    private function placeTrade(BingxClient $bingx, string $signal, string $symbol)
    {
        $margin    = (float) TRADE_MARGIN;
        $leverage  = (int)   TRADE_LEVERAGE;
        $tpPercent = (float) TRADE_TP_PERCENT;
        $slPercent = (float) TRADE_SL_PERCENT;
        $orderType = TRADE_ORDER_TYPE;

        try {
            // Close opposite position first
            $this->closeOppositePosition($bingx, $signal, $symbol);
            sleep(2);

            if ($this->hasSameDirectionPosition($bingx, $signal, $symbol)) {
                $this->close($symbol);
                
                $this->log('trades.log', [
                    'status' => 'duplicate_signal_closed',
                    'signal' => $signal,
                    'symbol' => $symbol,
                    'reason' => 'signal flickered - closed existing position for safety',
                ]);
                return 'signal flickered, existing position closed';
            }

            // Get current price
            $priceData = $bingx->market()->getLatestPrice($symbol)['data'];
            $currentPrice = (float) $priceData['markPrice'];

            // Calculate TP/SL prices
            $tpPrice = null;
            $slPrice = null;
            if (is_numeric($currentPrice) && $currentPrice > 0) {
                if ($signal === 'buy') {
                    $tpPrice = $currentPrice * (1 + $tpPercent / 100.0);
                    $slPrice = $currentPrice * (1 - $slPercent / 100.0);
                } else {
                    $tpPrice = $currentPrice * (1 - $tpPercent / 100.0);
                    $slPrice = $currentPrice * (1 + $slPercent / 100.0);
                }
            } else {
                $this->log('trades.log', [
                    'status' => 'invalid_price',
                    'signal' => $signal,
                    'symbol' => $symbol,
                    'price_data' => $priceData,
                ]);
            }
            
            $positionValue = $margin * $leverage;
            $quantity = $positionValue / $currentPrice;
            
            // Round to appropriate precision for the symbol
            // XAUT-USDT typically uses 3 decimal places
            $quantity = round($quantity, 3);

            $builder = $bingx->trade()->order()->futures()->symbol($symbol);

            if ($signal === 'buy') {
                $builder = $builder->buy()->long();
            } else {
                $builder = $builder->sell()->short();
            }

            $result = $builder
                ->type($orderType)
                ->margin($margin)
                ->quantity($quantity)
                ->leverage($leverage)
                ->execute();

            $this->log('trades.log', [
                'status'   => 'placed',
                'signal'   => $signal,
                'symbol'   => $symbol,
                'quantity' => $quantity,
                'price'    => $currentPrice,
                'margin'   => $margin,
                'leverage' => $leverage,
                'tp_price_calc' => $tpPrice,
                'sl_price_calc' => $slPrice,
                'response' => $result,
            ]);

            if ($tpPrice && $slPrice) {
                sleep(2); // Wait for position to settle
                $this->setTPSL($bingx, $symbol, $signal, $quantity, $tpPrice, $slPrice);
            }

            return $result;

        } catch (\Throwable $e) {
            $this->log('trades.log', [
                'status'  => 'error',
                'signal'  => $signal,
                'symbol'  => $symbol,
                'message' => $e->getMessage(),
                'trace'   => $e->getTraceAsString(),
            ]);
            http_response_code(500);
            echo 'order failed: ' . $e->getMessage();
            exit;
        }
    }

    private function setTPSL(BingxClient $bingx, string $symbol, string $signal, float $quantity, float $tpPrice, float $slPrice): void
    {
        try {
            // Build TP order
            $tpBuilder = $bingx->trade()->order()->futures()->symbol($symbol);
            
            if ($signal === 'buy') {
                $tpBuilder = $tpBuilder->sell()->long();
            } else {
                $tpBuilder = $tpBuilder->buy()->short();
            }

            $tpResult = $tpBuilder
                ->type('TAKE_PROFIT_MARKET')
                ->quantity($quantity)
                ->stopPrice($tpPrice)
                ->workingType('MARK_PRICE')
                ->execute();

            $this->log('trades.log', [
                'status' => 'tp_set',
                'signal' => $signal,
                'symbol' => $symbol,
                'tp_price' => $tpPrice,
                'quantity' => $quantity,
                'response' => $tpResult,
            ]);

            // Build SL order
            $slBuilder = $bingx->trade()->order()->futures()->symbol($symbol);
            
            if ($signal === 'buy') {
                $slBuilder = $slBuilder->sell()->long();
            } else {
                $slBuilder = $slBuilder->buy()->short();
            }

            $slResult = $slBuilder
                ->type('STOP_MARKET')
                ->quantity($quantity)
                ->stopPrice($slPrice)
                ->workingType('MARK_PRICE')
                ->execute();

            $this->log('trades.log', [
                'status' => 'sl_set',
                'signal' => $signal,
                'symbol' => $symbol,
                'sl_price' => $slPrice,
                'quantity' => $quantity,
                'response' => $slResult,
            ]);

        } catch (\Throwable $e) {
            $this->log('trades.log', [
                'status' => 'tpsl_error',
                'signal' => $signal,
                'symbol' => $symbol,
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
        }
    }

    private function closeOppositePosition(BingxClient $bingx, string $signal, string $symbol): void
    {
        $oppositeSide = ($signal === 'buy') ? 'SHORT' : 'LONG';

        try {
            $positions = $bingx->account()->getPositions($symbol);
        } catch (\Throwable $e) {
            $this->log('trades.log', [
                'status'  => 'position_fetch_error',
                'symbol'  => $symbol,
                'message' => $e->getMessage(),
            ]);
            return;
        }

        if (empty($positions) || !isset($positions['data']) || !is_array($positions['data'])) {
            return;
        }

        foreach ($positions['data'] as $pos) {
            $side = strtoupper($pos['positionSide'] ?? '');
            $qty  = abs((float) ($pos['positionAmt'] ?? 0));

            if ($side !== $oppositeSide || $qty <= 0) {
                continue;
            }

            try {

                $closeBuilder = $this->close($symbol);
                $this->log('trades.log', [
                    'status'        => 'opposite_closed',
                    'closed_side'   => $oppositeSide,
                    'symbol'        => $symbol,
                    'qty_closed'    => $qty,
                    'response'      => $closeBuilder,
                ]);

            } catch (\Throwable $e) {
                $this->log('trades.log', [
                    'status'    => 'close_opposite_error',
                    'side'      => $oppositeSide,
                    'symbol'    => $symbol,
                    'message'   => $e->getMessage(),
                ]);
                throw $e;
            }
        }
    }
    
    private function hasSameDirectionPosition(BingxClient $bingx, string $signal, string $symbol): bool
    {
        $sameSide = ($signal === 'buy') ? 'LONG' : 'SHORT';

        try {
            $positions = $bingx->account()->getPositions($symbol);
        } catch (\Throwable $e) {
            $this->log('errors.log', [
                'status'  => 'position_fetch_error',
                'symbol'  => $symbol,
                'message' => $e->getMessage(),
            ]);
            return false;
        }

        if (empty($positions) || !isset($positions['data']) || !is_array($positions['data'])) {
            return false;
        }

        foreach ($positions['data'] as $pos) {
            $side = strtoupper($pos['positionSide'] ?? '');
            $qty  = abs((float) ($pos['positionAmt'] ?? 0));
            if ($side === $sameSide && $qty > 0) {
                return true; // Position exists, caller will close it
            }
        }

        return false;
    }

    private function buildBingxClient(): BingxClient
    {
        return new BingxClient(
            BINGX_API_KEY,
            BINGX_API_SECRET,
            BINGX_BASE_URI,
            null,
            'hex' 
        );
    }

    private function log(string $file, array $data): void
    {
        file_put_contents(
            __DIR__ . '/' . $file,
            date('Y-m-d H:i:s') . ' - ' . json_encode($data) . PHP_EOL,
            FILE_APPEND
        );
    }

    public function captureRequestData()
    {
      
       $raw = @file_get_contents('php://input');
       $headers = [];
       if (function_exists('getallheaders')) {
           $all = getallheaders();
           if (is_array($all)) {
               $headers = $all;
           }
       } else {
           foreach ($_SERVER as $name => $value) {
               if (substr($name, 0, 5) === 'HTTP_') {
                   $headerName = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
                   $headers[$headerName] = $value;
               }
           }
           if (isset($_SERVER['CONTENT_TYPE'])) {
               $headers['Content-Type'] = $_SERVER['CONTENT_TYPE'];
           }
           if (isset($_SERVER['CONTENT_LENGTH'])) {
               $headers['Content-Length'] = $_SERVER['CONTENT_LENGTH'];
           }
       }

       $contentType = '';
       if (!empty($headers)) {
           foreach ($headers as $k => $v) {
               if (strtolower($k) === 'content-type') {
                   $contentType = $v;
                   break;
               }
           }
       }
       if (empty($contentType) && isset($_SERVER['CONTENT_TYPE'])) {
           $contentType = $_SERVER['CONTENT_TYPE'];
       }

       $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null;
       $rawString = is_string($raw) ? $raw : (is_scalar($raw) ? (string)$raw : json_encode($raw));

       $parsed = null;
       if (is_array($raw)) {
           $parsed = $raw;
       } else {
          
           $json = json_decode($rawString, true);
           if (json_last_error() === JSON_ERROR_NONE && ($json !== null)) {
               $parsed = $json;
           }
       }

       if ($parsed === null && stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
           parse_str($rawString, $out);
           $parsed = $out;
       }

       if ($parsed === null && stripos($contentType, 'multipart/form-data') !== false) {
           $parsed = $_POST;
       }

       if ($parsed === null && (stripos($contentType, 'xml') !== false)) {
           libxml_use_internal_errors(true);
           $xml = @simplexml_load_string($rawString);
           if ($xml !== false) {
               $parsed = json_decode(json_encode($xml), true);
           }
       }

       if ($parsed === null && !empty($_POST)) {
           $parsed = $_POST;
       }

       if ($parsed === null) {
           $parsed = $rawString;
       }

       // Files
       $files = [];
       if (!empty($_FILES)) {
           $files = $_FILES;
       }

       $result = [
           'raw' => $rawString,
           'body' => $parsed,
           'headers' => $headers,
           'get' => $_GET,
           'post' => $_POST,
           'files' => $files,
           'cookies' => $_COOKIE,
           'server' => $_SERVER,
           'method' => $method,
           'content_type' => $contentType,
           'client_ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
       ];

       return $result;
    }
    
}