<?php
namespace boru\boruai\Models;

use boru\boruai\BoruAI;
use boru\query\Query;
use boru\query\Entity;
use boru\query\models\EntityDefinition;
use boru\boruai\Openai\OpenAIChat;
use boru\boruai\Openai\OpenAIAssistant;
use boru\boruai\Openai\OpenAIMessage;
use boru\boruai\Openai\Response\Parts\OutputMessage;
use boru\boruai\Openai\Response\ResponseObject;
use boru\boruai\Tools\CodeTool;
use boru\boruai\Tools\Util;
use boru\output\Output;
use boru\query\Factory;
use boru\query\models\Value;
use Exception;

class ResponseHistory extends Entity {
    private static $isInit = false;
    public static function entityDefinition() {
        $tableName = BoruAI::table('responses');
        $query = Query::create()->select('id','user','reference','template_id','created_at','model','response_id','input','output','result','compressed_input')->from($tableName);
        return new EntityDefinition([
            "className" => static::class,
            "name" => "ResponseHistory",
            "queryBase" => $query,
            "jsonFields" => ['input','output'],
            "createdField" => "created_at",
        ]);
    }

    /** @var ResponseObject */
    private $inputObject;
    /** @var ResponseObject */
    private $outputObject;

    public static $useCompressedInput = false;

    public static function doInit() {
        if(!self::$isInit) {
            self::$isInit = true;
            Factory::register(static::entityDefinition());
        }
    }

    public function save() {
        if(!self::$useCompressedInput) {
            $this->compressedInput(null);
        }
        return parent::save();
    }

    public function previous() {
        $query = Factory::query("ResponseHistory");
        $query->where("id","<",$this->id());
        $reference = $this->reference();
        if($reference) {
            $query->where("reference","=",$reference);
        }
        $query->orderBy("id","desc");
        $query->limit(0,1);
        $instance = $query->toRow();
        if(!$instance) {
            return false;
        }
        $instance = Factory::instanceById("ResponseHistory",$instance->get("id"));
        if(!$instance) {
            return false;
        }
        return $instance;
    }
    public function inputAsChatMessages() {
        $messages = [];
        $input = $this->input();
        if(is_array($input)) {
            if(isset($input["input"])) {
                $input = $input["input"];
            }
            foreach($input as $array) {
                if(isset($array["role"]) && isset($array["content"])) {
                    if(is_array($array["content"]) && isset($array["content"][0]) && isset($array["content"][0]["text"])) {
                        foreach($array["content"] as $content) {
                            if(isset($content["text"])) {
                                $messages[] = ["role"=>$array["role"],"content"=>$content["text"],"response_id"=>$this->responseId()];
                            } elseif(is_string($content)) {
                                $messages[] = ["role"=>$array["role"],"content"=>$content,"response_id"=>$this->responseId()];
                            }
                        }
                    } elseif(is_string($array["content"])) {
                        $messages[] = ["role"=>"user","content"=>$array["content"],"response_id"=>$this->responseId()];
                    }   
                }
            }
        }
        return $messages;
    }
    public function inputaAsString() {
        $outputArray = [];
        $input = $this->input();
        if(is_array($input)) {
            if(isset($input["input"])) {
                $input = $input["input"];
            }
            foreach($input as $index=>$array) {
                $index*=100; // ensure unique index
                if(isset($array["content"])) {
                    if(is_array($array["content"]) && isset($array["content"][0]) && isset($array["content"][0]["text"])) {
                        foreach($array["content"] as $content) {
                            if(isset($content["text"])) {
                                $outputArray[$index] = $content["text"];
                            } elseif(is_string($content)) {
                                $outputArray[$index] = $content;
                            }
                            $index++;
                        }
                        //$outputArray[$index] = $array["content"][0]["text"];
                    } elseif(is_string($array["content"])) {
                        $outputArray[$index] = $array["content"];
                    }   
                }
            }
        }
        if(is_array($outputArray)) {
            $output = implode("\n",$outputArray);
            return $output;
        }
        if(is_string($input)) {
            return $input;
        }
    }

    public function __construct($input=null) {
        self::doInit();
        parent::__construct($input);
        if(!$this->user()) {
            $this->user(Value::functionValue('0'));
        }
    }

    public function id() {
        return $this->get("id");
    }

    public function user($user=null) {
        if ($user) {
            $this->set("user",$user);
        }
        return $this->get("user");
    }
    public function reference($reference=null) {
        if ($reference) {
            $this->set("reference",$reference);
        }
        return $this->get("reference");
    }
    public function templateId($templateId=null) {
        if ($templateId) {
            $this->set("template_id",$templateId);
        }
        return $this->get("template_id");
    }

    public function createdAt($createdAt=null) {
        if ($createdAt) {
            $this->set("created_at",$createdAt);
        }
        return $this->get("created_at");
    }
    public function responseId($responseId=null) {
        if ($responseId) {
            $this->set("response_id",$responseId);
        }
        return $this->get("response_id");
    }
    public function result($result=null) {
        if ($result) {
            $this->set("result",$result);
        }
        return $this->get("result");
    }
    public function compressedInput($input=null) {
        if(!self::$useCompressedInput) {
            $this->set("compressed_input","not-enabled");
            return "not-enabled";
        }
        if ($input) {
            if(is_array($input)) {
                if(isset($input["instructions"])) {
                    unset($input["instructions"]);
                }
            }
            $input = Util::compress($input);
            $this->set("compressed_input",$input);
        }
        $content = $this->get("compressed_input");
        if($content) {
            $content = Util::decompress($content);
            return $content;
        }
    }

    public function input($input=null) {
        if ($input) {
            if($input instanceof ResponseObject) {
                $this->inputObject = $input;
                $this->model($input->model());
                $input = $input->forInput();
            } elseif(is_array($input)) {
                if(!isset($input["input"])) {
                    $input = ["input"=>$input];
                }
                $object = new ResponseObject($input);
                return $this->input($object);
            }
            if($this->inputObject) {
                $this->compressedInput($input);
                $input = $this->inputObject->forInput(true);
            }
            $this->set("input",$input);
        }
        if($this->inputObject) {
            return $this->inputObject;
        }
        return $this->get("input");
    }
    public function output($output=null) {
        if ($output) {
            if($output instanceof ResponseObject) {
                $this->outputObject = $output;
                $this->result($output->getResult());
                $this->responseId($output->id());
                $output = $output->toArray();
            } elseif(is_array($output)) {
                $object = new ResponseObject($output);
                return $this->output($object);
            }
            $this->set("output",$output);
        }
        if($this->outputObject) {
            return $this->outputObject;
        }
        $output = $this->get("output");
        if($output && is_array($output)) {
            $object = new ResponseObject($output);
            $this->outputObject = $object;
            $this->set("output",$object->toArray());
            return $this->outputObject;
        }
        return $this->get("output");
    }
    public function model($model=null) {
        if ($model) {
            $this->set("model",$model);
        }
        $model = $this->get("model");
        if(!$model && $this->inputObject) {
            $model = $this->inputObject->model();
            $this->set("model",$model);
        }
        if(!$model && $this->outputObject) {
            $model = $this->outputObject->model();
            $this->set("model",$model);
        }
        return $model;
    }






    /**
     * 
     * @param mixed $responseId 
     * @return false|self
     * @throws Exception 
     */
    public static function fromId($id) {
        self::doInit();
        $instance = Factory::instanceById("ResponseHistory",$id);
        if(!$instance) {
            return new self($instance->id());
        }
        return $instance;
    }

    /**
     * 
     * @param mixed $responseId 
     * @return false|self
     * @throws Exception 
     */
    public static function fromReference($reference) {
        self::doInit();
        $db = BoruAI::db();
        $query = Factory::query("ResponseHistory");
        $query->where("reference","=",$reference);
        $query->limit(0,1);
        $query->orderBy("id","desc");
        $instance = $query->toRow();
        if(!$instance) {
            return false;
        }
        $instance = Factory::instanceById("ResponseHistory",$instance->get("id"));
        if(!$instance) {
            return false;
        }
        return $instance;
    }
    /**
     * 
     * @param mixed $responseId 
     * @return false|self
     * @throws Exception 
     */
    public static function fromUserReference($user=null,$reference=null,$create=true) {
        self::doInit();
        if(!$user && !$reference) {
            return false;
        }
        if(!$reference) {
            //default session to user if not one provided
            //this keeps the sessions separated by user.
            $reference = $user;
        }
        $db = BoruAI::db();
        $query = Factory::query("ResponseHistory");
        if($user) {
            $query->where("user","=",$user);
        }
        if($reference) {
            $query->where("reference","=",$reference);
        }
        $query->limit(0,1);
        $query->orderBy("id","desc");
        $instance = $query->toRow();
        if(!$instance) {
            if($create) {
                $instance = new self();
                $instance->reference($reference);
                $instance->user($user);
                $instance->save();
            } else {
                return false;
            }
        }
        $instance = Factory::instanceById("ResponseHistory",$instance->get("id"));
        if(!$instance) {
            if($create) {
                $instance = new self();
                $instance->reference($reference);
                $instance->user($user);
                $instance->save();
            } else {
                return false;
            }
        }
        return $instance;
    }

    public static function getPlayback($user=null,$reference=null,$limit=5,$beforeResponseId=false) {
        self::doInit();
        if(!$user && !$reference) {
            return false;
        }
        if(!$reference) {
            //default session to user if not one provided
            //this keeps the sessions separated by user.
            $reference = $user;
        }
        $result = [
            "reference" => $reference,
            "user" => $user,
            "output" => []
        ];
        if($beforeResponseId) {
            $response = Responsehistory::fromResponseId($beforeResponseId);
            $response = $response->previous();
        } else {
            $response = Responsehistory::fromUserReference($user,$reference,false);
        }
        if($response) {
            while($response) {
    
                $result["output"][] = ["role"=>"system","content"=>$response->result(),"response_id"=>$response->responseId()];
                $inputMessages = $response->inputAsChatMessages();
                if(is_array($inputMessages)) {
                    $inputMessages = array_reverse($inputMessages);
                    foreach($inputMessages as $message) {
                        $result["output"][] = $message;
                    }
                }
                //$result["output"][] = ["role"=>"user","content"=>$response->inputAsString(),"response_id"=>$response->responseId()];

                $response = $response->previous();
    
            }
        }
        if($limit) {
            $result["output"] = array_slice($result["output"],0,$limit);
        }
        $results = array_reverse($result["output"]);
        $result["output"] = $results;
        
        return $result;
    }

    /**
     * Search for response history
     * @param mixed $user 
     * @param mixed $reference 
     * @param mixed $limit 
     * @param mixed $offset 
     * @param array $conditions 
     * @return array
     */
    public static function search($user=null,$reference=null,$limit=10,$offset=0,$orderBy=[],$conditions=[]) {
        self::doInit();
        $db = BoruAI::db();
        $query = Factory::query("ResponseHistory");
        if($user) {
            $query->where("user","=",$user);
        }
        if($reference) {
            $query->where("reference","=",$reference);
        }
        if($limit || $offset) {
            if(!$limit) {
                $limit = 1;
            }
            $query->limit($offset,$limit);
        }
        if($conditions) {
            foreach($conditions as $condition) {
                if(is_array($condition)) {
                    $query->where(...$condition);
                } else {
                    $query->where($condition);
                }
            }
        }
        if($orderBy) {
            foreach($orderBy as $order) {
                if(is_array($order)) {
                    $query->orderBy(...$order);
                } else {
                    $query->orderBy($order);
                }
            }
        } else {
            $query->orderBy("id","desc");
        }
        $instances = [];
        while($row = $query->asRows()) {
            $instance = Factory::instanceById("ResponseHistory",$row->get("id"));
            if($instance) {
                $instances[] = $instance;
            }
        }
    }

    /**
     * 
     * @param mixed $responseId 
     * @return false|self
     * @throws Exception 
     */
    public static function fromResponseId($responseId) {
        self::doInit();
        $db = BoruAI::db();
        $instance = Factory::search("ResponseHistory",[0,1],["response_id","=",$responseId]);
        if(!$instance) {
            return false;
        }
        return $instance[0];
    }

    public static function deleteByReference($reference) {
        self::doInit();
        $db = BoruAI::db();
        $query = Factory::query("ResponseHistory");
        $query->delete()->where("reference","=",$reference);
        return $query->run();
    }
    public static function deleteByUser($user) {
        self::doInit();
        $db = BoruAI::db();
        $query = Factory::query("ResponseHistory");
        $query->delete()->where("user","=",$user);
        return $query->run();
    }
    public static function deleteByUserReference($user,$reference) {
        self::doInit();
        $db = BoruAI::db();
        $query = Factory::query("ResponseHistory");
        $query->delete()->where("user","=",$user)->where("reference","=",$reference);
        return $query->run();
    }
    public static function renameReference($oldReference,$newReference,$user=null) {
        self::doInit();
        $db = BoruAI::db();
        $query = Factory::query("ResponseHistory");
        $query->update()->set("reference",$newReference)->where("reference","=",$oldReference);
        if($user) {
            $query->where("user","=",$user);
        }
        return $query->run();
    }

    public static function lastTemplate($reference,$user=null) {
        self::doInit();
        $db = BoruAI::db();
        $query = Factory::query("ResponseHistory");
        if($user) {
            $query->where("user","=",$user);
        }
        $query->where("reference","=",$reference);
        $query->orderBy("id","desc");
        while($row = $query->asRows()) {
            if($row->get("template_id")) {
                return $row->get("template_id");
            }
        }
        return false;
    }
}