<?php
namespace boru\boruai\Openai\Models;

use boru\boruai\Models\Tool;
use boru\dhutils\dhGlobal;
use boru\boruai\Openai\Api\Endpoints\MessagesAPI;
use boru\boruai\Openai\Api\Responses\ApiListResponse;
use boru\boruai\Openai\OpenAI;
use Exception;

class Run extends BaseModel {
    private $id;
    private $object = "thread.run";
    private $createdAt;
    private $threadId;
    private $assistantId;
    private $status;
    private $requiredAction;
    private $lastError;
    private $expiresAt;
    private $startedAt;
    private $cancelledAt;
    private $failedAt;
    private $completedAt;
    private $model;
    private $instructions;
    private $tools;
    private $fileIds;
    private $metadata;

    public function id($id=null) {
        if ($id) {
            $this->id = $id;
        }
        return $this->id;
    }
    public function object($object=null) {
        if ($object) {
            $this->object = $object;
        }
        return $this->object;
    }
    public function createdAt($created=null) {
        if ($created) {
            $this->createdAt = $created;
        }
        return $this->createdAt;
    }
    public function threadId($threadId=null) {
        if ($threadId) {
            $this->threadId = $threadId;
        }
        return $this->threadId;
    }
    public function thread_id($threadId=null) {
        return $this->threadId($threadId);
    }
    public function assistantId($assistantId=null) {
        if ($assistantId) {
            $this->assistantId = $assistantId;
        }
        return $this->assistantId;
    }
    public function status($status=null) {
        if ($status) {
            $this->status = $status;
        }
        return $this->status;
    }
    public function requiredAction($requiredAction=null) {
        if ($requiredAction) {
            $this->requiredAction = $requiredAction;
        }
        return $this->requiredAction;
    }
    public function lastError($lastError=null) {
        if ($lastError) {
            $this->lastError = $lastError;
        }
        return $this->lastError;
    }
    public function expiresAt($expiresAt=null) {
        if ($expiresAt) {
            $this->expiresAt = $expiresAt;
        }
        return $this->expiresAt;
    }
    public function startedAt($startedAt=null) {
        if ($startedAt) {
            $this->startedAt = $startedAt;
        }
        return $this->startedAt;
    }
    public function cancelledAt($cancelledAt=null) {
        if ($cancelledAt) {
            $this->cancelledAt = $cancelledAt;
        }
        return $this->cancelledAt;
    }
    public function failedAt($failedAt=null) {
        if ($failedAt) {
            $this->failedAt = $failedAt;
        }
        return $this->failedAt;
    }
    public function completedAt($completedAt=null) {
        if ($completedAt) {
            $this->completedAt = $completedAt;
        }
        return $this->completedAt;
    }
    public function model($model=null) {
        if ($model) {
            $this->model = $model;
        }
        return $this->model;
    }
    public function instructions($instructions=null) {
        if ($instructions) {
            $this->instructions = $instructions;
        }
        return $this->instructions;
    }
    public function tools($tools=null) {
        if ($tools) {
            $this->tools = $tools;
        }
        return $this->tools;
    }
    public function fileIds($fileIds=null) {
        if ($fileIds) {
            $this->fileIds = $fileIds;
        }
        return $this->fileIds;
    }
    public function metadata($metadata=null) {
        if ($metadata) {
            $this->metadata = $metadata;
        }
        return $this->metadata;
    }

    /**
     * Check the status of this run
     * @return bool true if the run is completed, false otherwise
     * @throws Exception
     */
    public function checkStatus() {
        $run = \boru\boruai\Openai\Api\Endpoints\RunsAPI::getRun($this->threadId(),$this->id());
        //print_r($run);
        $this->status($run->status());
        $this->lastError($run->lastError());
        $this->expiresAt($run->expiresAt());
        $this->startedAt($run->startedAt());
        $this->cancelledAt($run->cancelledAt());
        $this->failedAt($run->failedAt());
        $this->completedAt($run->completedAt());
        if($this->status() === "requires_action") {
            $this->requiredAction($run->requiredAction());
            return $this->handleRequiresAction();
        }
        if(in_array($this->status(),["completed","failed","cancelled","expired"])) {
            return true;
        }
        return false;
    }
    /**
     * Get the messages for this run
     * @return Message[]|false 
     * @throws Exception 
     */
    public function getMessages() {
        $output = [];
        $messages = MessagesAPI::listMessages($this->threadId());
        foreach($messages as $message) {
            $output[] = $message;
        }
        return $output;
    }
    public function handleRequiresAction() {
        if(is_array($this->requiredAction())) {
            $actionType = $this->requiredAction()["type"];
            if($actionType == "submit_tool_outputs") {
                $toolOutputs = [];
                $callsRequired = $this->requiredAction()["submit_tool_outputs"]["tool_calls"];
                OpenAI::printDebug("Calling tools..");
                foreach($callsRequired as $toolRequest) {
                    $tool = new Tool($toolRequest);
                    if(($toolOutput = $tool->run()) !== false) {
                        $toolOutputs[] = $toolOutput;
                    } else {
                        if(($toolOutput = ToolFunction::fromToolCall($toolRequest,$this->assistantId())) !== false) {
                            $toolOutputs[] = $toolOutput;
                        } else {
                            throw new Exception("Tool call not found ".json_encode($toolRequest));
                        }
                    }
                }
                OpenAI::printDebug("Submitting tool outputs");
                $run = \boru\boruai\Openai\Api\Endpoints\RunsAPI::submitToolOutput($this->threadId(),$this->id(),["tool_outputs"=>$toolOutputs]);
                return false;
            }
        }
        print_r($this->requiredAction());
        return true;
    }
    public function result() {
        OpenAI::printDebug("RUN","Waiting for result");
        while(!$this->checkStatus()) {
            OpenAI::printDebug("RUN","current status ".$this->status());
            sleep(1);
        }
        OpenAI::printDebug("RUN","completed with status ".$this->status());
        if($this->status() == "failed") {
            throw new Exception("\n\nRun failed:\n".json_encode($this->lastError(),JSON_PRETTY_PRINT)."\n\n");
        }
        $messages = $this->getMessages();
        $response = $messages[0];
        return $response->content();
    }

    public function toArray() {
        return [
            "id" => $this->id(),
            "object" => $this->object(),
            "created_at" => $this->createdAt(),
            "thread_id" => $this->threadId(),
            "assistant_id" => $this->assistantId(),
            "status" => $this->status(),
            "required_action" => $this->requiredAction(),
            "last_error" => $this->lastError(),
            "expires_at" => $this->expiresAt(),
            "started_at" => $this->startedAt(),
            "cancelled_at" => $this->cancelledAt(),
            "failed_at" => $this->failedAt(),
            "completed_at" => $this->completedAt(),
            "model" => $this->model(),
            "instructions" => $this->instructions(),
            "tools" => $this->tools(),
            "file_ids" => $this->fileIds(),
            "metadata" => $this->metadata()
        ];
    }
}