<?php
namespace boru\boruai\Openai;

use boru\boruai\BoruAI;
use boru\boruai\Openai\Api\Chat;
use boru\boruai\Openai\Api\Endpoints\ModelsAPI;
use boru\boruai\Openai\Api\Responses\ApiBaseResponse;
use boru\boruai\Openai\Api\Responses\ApiListResponse;

use GuzzleHttp\Exception\RequestException;

use boru\dhttp\core\Options;
use boru\dhttp\core\Request;
use boru\dhttp\core\Response;
use boru\dhutils\dhGlobal;
use boru\boruai\Openai\OpenAIAssistant;
use boru\boruai\Openai\Models\ListItem;
use boru\boruai\Config;
use boru\dhdb\dhDB;
use boru\output\Output;
use Exception;

class OpenAI extends BoruAI {
    private static $api_version = "v1";
    private static $api_url = "https://api.openai.com";
    //private static $api_url = "http://localhost/post.php?";

    private static $guzzleOptions = [
            //'timeout' => 60, // Response timeout
            'connect_timeout' => 5, // Connection timeout
    ];

    public static function init($api_key=null,$configFile=null) {
        parent::init($configFile);
        if($api_key !== null) {
            static::setApiKey($api_key);
        }
    }
    public static function initConfig($configFile) {
        static::init(null,$configFile);
    }
    
    
    public static function setApiKey($api_key) {
        Config::set("openai.api_key",$api_key);
    }
    public static function setApiVersion($api_version) {
        static::$api_version = $api_version;
    }
    public static function setApiUrl($api_url) {
        static::$api_url = $api_url;
    }

    public static function getEndpoint($path="") {
        return static::$api_url . "/" . static::$api_version . "/" . $path;
    }

    public static function guzzleOptions(...$options) {
        $return = static::$guzzleOptions;
        foreach($options as $option) {
            if(is_array($option)) {
                $return = array_merge($return,$option);
            }
        }
        return $return;
    }

    public static function guzzleHeaders($contentType="application/json") {
        $headers = [
            "Authorization"=>"Bearer " . Config::get("openai.api_key"),
            "Accept"=>"application/json",
            "User-Agent"=>"OpenAI-PHP/0.1",
            "OpenAI-Source"=>"boru/openai-php",
        ];
        return $headers;
    }

    private static function callGuzzle($client,$type,$url,$options) {
        $i=0;
        $errorMessage = "";
        while($i++<3) {
            try {
                $response = $client->request(strtoupper($type),$url,$options);
                self::printHttpDebug(strtoupper($type),"Completed");
                return $response;
            } catch(RequestException $e) {
                $errorMessage = $e->getResponse()->getBody()->getContents();
                self::printHttpDebug(strtoupper($type),"Error with request.. retrying..");
            } catch (Exception $e) {
                $errorMessage = $e->getMessage();
                self::printHttpDebug(strtoupper($type),"Error with request.. retrying..");
            }
        }
        throw new Exception($errorMessage);
    }

    /**
     * @param mixed $type 
     * @param string $path 
     * @param array $parameters 
     * @return ApiBaseResponse|false 
     * @throws Exception 
     */
    public static function request($type,$path="",$parameters=[],$guzzleOptions=[]) {
        static::verifyInit();

        $url = static::getEndpoint($path);
        self::printHttpDebug(strtoupper($type),"Requesting to: ".$url);
        $options = [];
        $options["headers"] = static::guzzleHeaders();
        if(!empty($parameters) && is_array($parameters)) {
            $options['json'] = $parameters;
        }
        $options = static::guzzleOptions($options,$guzzleOptions);
        $client = new \GuzzleHttp\Client();
        $response = static::callGuzzle($client,strtoupper($type),$url,$options);
        $parsed = static::responseParse($response);
        return ApiBaseResponse::fromArray($parsed);
    }
    public static function requestStream($type,$path="",$parameters=[],$guzzleOptions=[]) {
        static::verifyInit();
        $url = static::getEndpoint($path);
        self::printHttpDebug(strtoupper($type),"Requesting to: ".$url);
        $options = [];
        $options["headers"] = static::guzzleHeaders();
        $options["stream"] = true;
        $options["http_errors"] = false;
        $options["timeout"] = 0;
        if(!empty($parameters) && is_array($parameters)) {
            $options['json'] = $parameters;
        }
        $options = static::guzzleOptions($options,$guzzleOptions);
        $client = new \GuzzleHttp\Client();
        $response = static::callGuzzle($client,strtoupper($type),$url,$options);
        return $response;
    }
    /**
     * @param mixed $type 
     * @param string $path 
     * @param array $parameters 
     * @return mixed
     * @throws Exception 
     */
    public static function rawRequest($type,$path="",$parameters=[]) {
        static::verifyInit();
        $url = static::getEndpoint($path);
        self::printHttpDebug(strtoupper($type),"Requesting to: ".$url);
        $options = [];
        $options["headers"] = static::guzzleHeaders();
        if(!empty($parameters) && is_array($parameters)) {
            $options["json"] = $parameters;
        }
        $client = new \GuzzleHttp\Client();
        $response = static::callGuzzle($client,strtoupper($type),$url,$options);
        return $response->getBody()->getContents();
    }
    /**
     * @param mixed $type 
     * @param string $path 
     * @param array $parameters 
     * @return ApiListResponse|false 
     * @throws Exception 
     */
    public static function requestList($type,$path="",$parameters=[],$className=null) {
        static::verifyInit();
        $url = static::getEndpoint($path);
        self::printHttpDebug("GET","Getting list from: ".$url);
        $options = [];
        $options["headers"] = static::guzzleHeaders();
        if(!empty($parameters) && is_array($parameters)) {
            $options['query'] = $parameters;
        }
        if($className === null) {
            $className = ListItem::class;
        }
        $client = new \GuzzleHttp\Client();
        $response = static::callGuzzle($client,strtoupper($type),$url,$options);
        $parsed = static::responseParse($response);
        return ApiListResponse::fromListResponse($parsed,$className,[static::class,"requestList"],[$type,$path,$parameters,$className]);
    }

    /**
     * @param string $path
     * @param array $paramaters
     * @return Request
     * @throws \Exception
     */
    public static function post($path="",$parameters=[]) {
        static::verifyInit();
        $url = static::getEndpoint($path);
        self::printHttpDebug("POST","Posting to: ".$url);
        $options = [];
        $options["headers"] = static::guzzleHeaders();
        if(!empty($parameters) && is_array($parameters)) {
            $options['json'] = $parameters;
        }
        $client = new \GuzzleHttp\Client();
        $response = static::callGuzzle($client,"POST",$url,$options);
        return static::responseParse($response);
        
    }
    /**
     * @param string $path
     * @param array $paramaters
     * @return ApiBaseResponse
     * @throws \Exception
     */
    public static function upload($path="",$parameters=[]) {
        static::verifyInit();
        $url = static::getEndpoint($path);
        self::printHttpDebug("UPLOAD","Uploading to: ".$url);

        // IMPORTANT:
        // Do NOT set Content-Type manually for multipart requests. Guzzle will add the correct
        // boundary automatically. Setting it yourself (e.g. "multipart/form-data") can break uploads.
        $options = [];
        $options["headers"] = static::guzzleHeaders();
        $options["http_errors"] = false;

        $options["multipart"] = [];
        foreach($parameters as $key=>$value) {
            $options["multipart"][] = [
                "name" => $key,
                "contents" => $value
            ];
        }

        $client = new \GuzzleHttp\Client();
        $response = static::callGuzzle($client,"POST",$url,$options);

        $body = $response->getBody()->getContents();
        $parsed = json_decode($body,true);
        if(!is_array($parsed)) {
            // Fall back to raw body; ApiBaseResponse::fromArray expects array.
            throw new \Exception("Invalid JSON response from OpenAI upload endpoint: " . substr($body,0,500));
        }
        return ApiBaseResponse::fromArray($parsed);
    }

    public static function getRelPath($from, $to, $ps = DIRECTORY_SEPARATOR)
    {
        $arFrom = explode($ps, rtrim($from, $ps));
        $arTo = explode($ps, rtrim($to, $ps));
        while(count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) {
            array_shift($arFrom);
            array_shift($arTo);
        }
        if(empty($arFrom) && count($arTo) === 1) {
            return ".".$ps.$arTo[0];
        }
        return str_pad("", count($arFrom) * 3, '..'.$ps).implode($ps, $arTo);
    }

    public static function assertConfig() {
        if(Config::get("openai.api_key","") == "") {
            throw new \Exception("OpenAI API Key not set");
        }
    }


    public static function assistant($name) {
        if(is_object($name) && get_class($name) == OpenAIAssistant::class) {
            return $name;
        }
        if(is_array($name) && isset($name["id"])) {
            return OpenAIAssistant::fromArray($name);
        }
        
        $assistantId = Config::get("assistants.".$name,false);
        if($assistantId !== false) {
            return OpenAIAssistant::fromId($assistantId);
        }

        if(substr($name,0,5) == "asst_") {
            return OpenAIAssistant::fromId($name);
        }
        return OpenAIAssistant::fromName($name);
        
    }

    
    public static function toUtf8($str) {
        return mb_convert_encoding($str, 'UTF-8', 'auto');
    }
    private static function responseParse($response) {
        $body = $response->getBody()->getContents();
        $json = json_decode($body,true);
        if($json === null) {
            return $body;
        }
        return $json;
    }
}