<?php
namespace boru\boruai\OCR;

use boru\boruai\BoruAI;
use boru\queue\Queue;
use boru\boruai\OCR\Contract\OCRCallbackInterface;
use boru\ocr\OcrEngine;
use boru\queue\Entity\QueueItem;
use boru\queue\Task\ClosureTask;
use boru\queue\Task\TaskRegistry;

class VtigerDocument {


    /**
     * @var string Default field to update in vtiger with OCR data
     */
    public static $defaultUpdateField = 'cf_ocr_data';

    public static $useOcrLibrary = true;
    
    /**
     * @var int Document CRM Id
     */
    public $id;
    public $filepath;
    public $md5hash;
    public $filesize;

    public $updateField;

    /** @var OCRCallbackInterface[] */
    public static $callbacks = [];

    public function __construct($data = [], $updateField = null) {
        if($updateField === null) {
            $updateField = self::$defaultUpdateField;
        }
        $this->updateField = $updateField;
        foreach ($data as $key => $value) {
            if (property_exists($this, $key)) {
                $this->$key = $value;
            }
        }
        if(!$this->md5hash && $this->filepath && is_file($this->filepath)) {
            $this->md5hash = md5_file($this->filepath);
        }
    }

    public function toArray() {
        return [
            'id' => $this->id,
            'filepath' => $this->filepath,
            'md5hash' => $this->md5hash,
            'filesize' => $this->filesize,
            'updateField' => $this->updateField,
        ];
    }

    public function payload() {
        return $this->toArray();
    }

    /**
     * Load a VtigerDocument object for a given payload
     * @param mixed $payload 
     * @return VtigerDocument 
     */
    public static function fromPayload($payload) {
        if(!is_array($payload) && !is_object($payload)) {
            if(is_string($payload)) {
                $payload = json_decode($payload, true);
            } else {
                if(is_object($payload) && method_exists($payload, 'toArray')) {
                    $payload = $payload->toArray();
                } else {
                    $payload = [];
                }
            }
        }
        if(!is_array($payload)) {
            throw new \Exception("Invalid payload for VtigerDocument");
        }
        $payloadField = static::$defaultUpdateField;
        if(isset($payload["updateField"])) {
            $payloadField = $payload["updateField"];
            unset($payload["updateField"]);
        }
        return new self($payload, $payloadField);
    }

    /**
     * Load a VtigerDocument by its document CRM Id
     * @param int $docid 
     * @param string|null $updateField
     * @return VtigerDocument|null 
     */
    public static function fromDocumentId($docid, $updateField = null, $includeIfNoFile=false) {
        $sql = "SELECT  vtiger_attachments.* , vtiger_notes.title,vtiger_notes.filename,vtiger_crmentity.createdtime , vtiger_notes.*
        FROM vtiger_notes
        INNER JOIN vtiger_crmentity ON vtiger_crmentity.crmid= vtiger_notes.notesid AND vtiger_crmentity.deleted=0
        LEFT JOIN vtiger_seattachmentsrel  ON vtiger_seattachmentsrel.crmid =vtiger_notes.notesid
        LEFT JOIN vtiger_attachments ON vtiger_seattachmentsrel.attachmentsid = vtiger_attachments.attachmentsid
        WHERE vtiger_notes.notesid = ?";
        $db = \boru\boruai\BoruAI::db();
        $result = $db->run($sql, [$docid]);
        while($row=$db->next($result)) {
            $filepath = $row['path'].$row['attachmentsid']."_".$row['name'];
            if(is_file($filepath) || $includeIfNoFile) {
                $data = [
                    'id' => $row['notesid'],
                    'filepath' => $filepath,
                    'filesize' => is_file($filepath) ? filesize($filepath) : null,
                ];
                return new self($data, $updateField);
            }
        }
    }

    /**
     * @param Queue $queue 
     * @return void 
     */
    public function queue($queue) {
        $queueItem = $queue->enqueue("vtiger_document_ocr", $this->toArray(), $this->id);
        if(!$this->filepath || !is_file($this->filepath)) {
            $storage = $queue->getStorage();
            $storage->markError($queueItem, "File does not exist: ".$this->filepath);
            return false;
        }
        return true;
    }

    public function ocr() {
        if(!$this->filepath || !is_file($this->filepath)) {
            throw new \Exception("File does not exist: ".$this->filepath);
        }
        if(self::$useOcrLibrary && class_exists("\\boru\\ocr\\OcrEngine")) {
            return $this->ocrWithBoruOcr();
        } else {
            return $this->ocrWithBoruAi();
        }
    }

    public function ocrWithBoruOcr() {
        if(!$this->filepath || !is_file($this->filepath)) {
            throw new \Exception("File does not exist: ".$this->filepath);
        }
        $engine = OcrEngine::forFile($this->filepath);
        if (!$engine) {
            throw new \Exception("No OCR engine available for file: " . $this->filepath);
        }
        $engine->withPlanner()->withAI()->withTableInterpreter();
        $bundle = $engine->run();
        if (!$bundle) {
            self::processError($this, "Failed to process OCR for document ID: " . $this->id);
            return;
        }
        $ocrData = $bundle->getText();
        if (!$ocrData) {
            self::processError($this, "No OCR data found for document ID: " . $this->id);
            return; // No OCR data to update
        }
        $this->updateWithOcrSuccess($ocrData);
        self::processSuccess($this, $ocrData);
        return $ocrData;
    }

    public function ocrWithBoruAI() {
        if(!$this->filepath || !is_file($this->filepath)) {
            throw new \Exception("File does not exist: ".$this->filepath);
        }
        $ocr = new OCR($this->filepath, "DocumentID:".$this->id);
        $result = $ocr->run("DocumentID:".$this->id);
        if(!$result) {
            self::processError($this, "Failed to process OCR for document ID: ".$this->id);
            return;
        }
        if(!is_array($result) && substr(ltrim($result), 0, 11) === "[OCR FAILED") {
            self::processError($this, "Invalid OCR result: ".$result);
            return;
        }
        $db = BoruAI::db();
        $ocrData = $result;
        if(is_array($result)) {
            $ocrData = json_encode($result, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
        }
        $this->updateWithOcrSuccess($ocrData);
        self::processSuccess($this, $ocrData);
        return $ocrData;
    }

    private function updateWithOcrSuccess($ocrData) {
        $db = BoruAI::db();
        if($this->updateField !== false && $this->id) {
            $sql = "UPDATE vtiger_notes
                LEFT JOIN vtiger_notescf ON vtiger_notescf.notesid= vtiger_notes.notesid
                INNER JOIN vtiger_crmentity ON vtiger_crmentity.crmid= vtiger_notes.notesid
                SET ".$this->updateField." = ? WHERE vtiger_notes.notesid = ?";
            $db->run($sql, [$ocrData, $this->id]);
        }
    }

    /**
     * Register a task to process OCR for Vtiger documents
     * @param Queue $queue 
     * @param string $taskName 
     * @return void 
     */
    public static function registerTask($queue, $taskName = 'vtiger_document_ocr') {
        $queue->registerTask($taskName, function($queueItem, $payload) {
            $payload = $queueItem->getPayload();
            return self::processOcr($payload);
        });
    }

    /**
     * Add a callback to handle OCR success or error events
     * @param OCRCallbackInterface $callback 
     * @return void 
     */
    public static function registerCallback($callback) {
        self::$callbacks[] = $callback;
    }


    /**
     * Process OCR success or error events
     * @param VtigerDocument $document 
     * @param mixed $ocrData 
     * @return void 
     */
    public static function processSuccess( $document, $ocrData) {
        foreach(self::$callbacks as $cb) {
            $cb->handleSuccess($ocrData, $document);
        }
    }

    /**
     * Process OCR success or error events
     * @param VtigerDocument $document 
     * @param string $error 
     * @return void 
     */
    public static function processError($document, $error) {
        foreach(self::$callbacks as $cb) {
            $cb->handleError($error, $document);
        }
    }

    public static function processOcr($payload) {
        $document = self::fromPayload($payload);
        return $document->ocr();
    }
}