<?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\Tools\CodeTool;
use boru\boruai\Tools\Util;
use boru\output\Output;
use boru\query\Factory;

class ChatSession extends Entity {

    /** @var OpenAIChat */
    private $chatObject;

    /** @var bool */
    private $deleteOnClose = false;

    /** @var Template */
    private $template = null;
    

    public static function entityDefinition() {
        $tableName = BoruAI::table('sessions');
        $query = Query::create()->select('*')->from($tableName);
        return new EntityDefinition([
            "className" => static::class,
            "name" => "Session",
            "queryBase" => $query,
            "jsonFields" => ['messages','important'],
            "createdField" => "created",
            "updatedField" => "updated",
        ]);
    }
    public function __destrict() {
        if($this->deleteOnClose) {
            $this->delete();
        }
    }
    public function __construct($idOrData=null) {
        if($idOrData instanceof OpenAIChat) {
            $this->chatObject = $idOrData;
        } elseif(is_string($idOrData)) {
            $records = Factory::search("Session",[0,1],["name","=",$idOrData]);
            if($records) {
                $this->chatObject = new OpenAIChat($records[0]->get("model","gpt-4.1"));
                parent::__construct($records[0]);
            } else {
                //new session
                $this->chatObject = new OpenAIChat($this->get("model","gpt-4.1"));
            }
        } else {
            parent::__construct($idOrData);
            $this->chatObject = new OpenAIChat($this->get("model","gpt-4.1"));
        }
        $important = $this->get("important",[]);
        if(!empty($important)) {
            if(isset($important["include"]) && is_array($important["include"])) {
                foreach($important["include"] as $fileName) {
                    Util::globalIncludeFile($fileName);
                }
            }
            if(isset($important["tools"]) && is_array($important["tools"])) {
                foreach($important["tools"] as $toolName) {
                    $toolDefs = BoruAI::loadTool($toolName);
                    if($toolDefs && is_array($toolDefs)) {
                        foreach($toolDefs as $toolDef) {
                            $this->chatObject->addTool($toolDef);
                        }
                    }
                }
            }
        }
        $messages = $this->get("messages",[]);
        if(!empty($messages)) {
            foreach($messages as $msg) {
                $this->message(new OpenAIMessage($msg));
            }
        }
        if(empty($this->get("important"))) {
            $this->set("important",[]);
        }
    }

    public function deleteOnClose($deleteOnClose=true) {
        $this->deleteOnClose = $deleteOnClose ? true : false;
        return $this;
    }
    public function delete() {
        if($this->id()) {
            Query::create()->delete(BoruAI::table('sessions'))->where('id',"=",$this->id())->run();
        }
    }

    public function chat($message,$attachments=[],$metadata=[],$isRetry=false) {
        if(!$isRetry) {
            $this->addMessage("user",$message,$attachments,$metadata);
        } else {
            Output::outLine(">> Retrying chat with message: ",$message);
        }
        try {
            $response = $this->chatObject->run(0,false);
        } catch(\Exception $e) {
            if($isRetry) {
                //we already compressed, so throw the error.
                throw $e;
            }
            $response = $this->handleError($e->getMessage());
            if($response) {
                return $this->chat($message,$attachments,$metadata,true);
            }
        }
        if(isset($response["message"])) {
            $this->chatObject->message(new OpenAIMessage($response["message"]));
            $this->set("messages",$this->getMessages(true));
            $this->save();
            return $response["message"]["content"];
        } else {
            print_r($response);
        }
    }

    public function chatWithTemplate($templateId,$data=[], $referenceId=null, $referenceType=null) {
        $this->template = Template::fromId($templateId);
        if($this->template) {
            $this->template->renderInstructions($data);
            $this->template->renderPrompt($data);
            $response = false;
            if($this->template->instructions()) {
                $this->addMessage("system",$this->template->instructions());
            }
            if($this->template->prompt()) {
                $response = $this->chat($this->template->prompt());
                $this->template->history($response,$referenceId,$referenceType);
            }
            return $response;
        }
        return false;
    }

    public function handleError($responseJson) {
        Output::outLine("Error:",$responseJson);
        print_r($responseJson);
        if(!is_array($responseJson)) {
            $responseJson = json_decode($responseJson,true);
        }
        if(!isset($responseJson["error"])) {
            return;
        }
        if(isset($responseJson["error"]["code"]) && $responseJson["error"]["code"] == "context_length_exceeded") {
            if(isset($responseJson["error"]["param"]) && $responseJson["error"]["param"] == "messages") {
                $keepLastN = 1;
                if(count($this->getMessages(true))> 3) {
                    $keepLastN = 3;
                }
                Output::outLine("Compressing messages to fit within context length");
                $this->compress($keepLastN);
                return true;
            }
        }
        return false;
    }

    public function compress($excludeLastN=2,$startAt=2) {
        $summary = $this->chatObject->compress($excludeLastN,$startAt);
        $this->getMessages(true);
        return $summary;
    }


    public function addTools($arrayOfTools=[]) {
        foreach($arrayOfTools as $tool) {
            $this->addTool($tool);
        }
        return $this;
    }
    public function addTool($toolName) {
        $toolDefs = BoruAI::loadTool($toolName);
        if($toolDefs && is_array($toolDefs)) {
            foreach($toolDefs as $toolDef) {
                $this->chatObject->addTool($toolDef);
            }
            $important = $this->get("important",[]);
            $important["tools"][] = $toolName;
            $this->set("important",$important);
        }
        return $this;
    }

    public function addInclude($filePath) {
        if(!file_exists($filePath)) {
            throw new \Exception("File does not exist: ".$filePath,0);
        }
        $important = $this->get("important",[]);
        $important["include"][] = $filePath;
        $this->set("important",$important);
        Util::globalIncludeFile($filePath);
        return $this;
    }

    /**
     * 
     * @param Document $document 
     * @return $this 
     */
    public function addDocument($document) {
        if($document->summary()) {
            $this->addMessage("user","Document Content::\n".$document->summary());
        } else {
            throw new \Exception("Document has no content: Document ".$document->id(),0);
        }
    }

    public function addMessage($role,$content,$attachments=[],$metadata=[]) {
        $message = [
            "role" => $role,
            "content" => $content,
            "attachments" => $attachments,
            "metadata" => $metadata
        ];
        $this->message(new OpenAIMessage($message));
        return $this;
    }
    public function message($message) {
        $this->chatObject->message($message);
        $this->set("messages",$this->getMessages(true));
        return $this;
    }

    public function getMessages($asArray=true) {
        if($asArray) {
            $this->set('messages',$this->chatObject->messages()->toArray());
            return $this->get("messages");
        }
        return $this->chatObject->messages();
    }

    // Getters and setters

    public function assistant($assistant=null) {
        if ($assistant !== null) {
            $this->set('assistant', $assistant);
            return $this;
        }
        return $this->get('assistant');
    }

    public function name($name=null) {
        if ($name !== null) {
            $this->set('name', $name);
            return $this;
        }
        return $this->get('name');
    }

    public function model($model=null) {
        if ($model !== null) {
            $this->set('model', $model);
            return $this;
        }
        return $this->get('model');
    }

    public function important($important=null) {
        if ($important !== null) {
            $this->set('important', $important);
            return $this;
        }
        return $this->get('important');
    }

    public function created($created=null) {
        if ($created !== null) {
            $this->set('created', $created);
            return $this;
        }
        return $this->get('created');
    }

    public function updated($updated=null) {
        if ($updated !== null) {
            $this->set('updated', $updated);
            return $this;
        }
        return $this->get('updated');
    }

    public function active($active=null) {
        if ($active !== null) {
            $this->set('active', $active);
            return $this;
        }
        return $this->get('active');
    }

    public function messages($messages=null) {
        if ($messages !== null) {
            $this->set('messages', $messages);
            return $this;
        }
        return $this->get('messages');
    }

    public function object($object=null) {
        if ($object !== null) {
            $this->chatObject = $object;
            return $this;
        }
        return $this->chatObject;
    }

    // End Getters and setters

    // Static methods

    /**
     * Get a session by name
     * @param string $name
     * @return ChatSession|false
     */
    public static function fromName($name) {
        $sessions = Factory::search("Session",[0,1],["name","=",$name]);
        if($sessions) {
            return new self($sessions[0]->id());
        }
        return false;
    }
    /**
     * Get a session by id
     * @param string $id
     * @return ChatSession|false
     */
    public static function fromId($id) {
        $sessions = Factory::search("Session",[0,1],["id","=",$id]);
        if($sessions) {
            return new self($sessions[0]->id());
        }
        return false;
    }

    /**
     * List all sessions
     * @param int $limit
     * @param int $offset
     * @return ChatSession[]
     */
    public static function listSessions($limit=25,$offset=0) {
        return Factory::search("Session",[$offset,$limit],[1,"=",1]);
    }

    public static function create($model="gpt-4.1",$name=null) {
        $session = new self();
        $session->set("model",$model);
        if($name) {
            $session->set("name",$name);
        } else {
            $session->deleteOnClose(true);
        }
        $session->save();
        return $session;
    }
}