<?php
namespace boru\ai\Persistence\Db;

use boru\ai\Embedding\EmbeddingRequest;
use boru\ai\Embedding\EmbeddingResult;
use boru\ai\Embedding\EmbeddingStoreInterface;

class DbEmbeddingStore implements EmbeddingStoreInterface
{
    /** @var DbConnection */
    protected $connection;

    public function __construct(DbConnection $connection = null)
    {
        $this->connection = $connection ? $connection : new DbConnection();
    }

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

    public function findResult($cacheKey)
    {
        $db = $this->connection()->db();
        $sql = 'select * from `' . $this->table() . '` where `cache_key`=? limit 1';
        $result = $this->connection()->run($sql, array($cacheKey));
        $row = $db->next($result);

        if (!$row) {
            return false;
        }

        $expiresAt = $row->get('expires_at');
        if ($expiresAt && strtotime($expiresAt) !== false && strtotime($expiresAt) < time()) {
            $this->connection()->run('delete from `' . $this->table() . '` where `cache_key`=?', array($cacheKey));
            return false;
        }

        $payload = $row->get('result_json');
        if (!is_string($payload) || $payload === '') {
            return false;
        }

        $decoded = json_decode($payload, true);
        if (!is_array($decoded)) {
            return false;
        }

        return EmbeddingResult::fromArray($decoded);
    }

    public function saveResult($cacheKey, EmbeddingRequest $request, EmbeddingResult $result, $ttlSeconds = null)
    {
        $db = $this->connection()->db();
        $table = $this->table();
        $now = date('Y-m-d H:i:s');
        $expiresAt = null;

        if ($ttlSeconds !== null) {
            $ttlSeconds = (int)$ttlSeconds;
            if ($ttlSeconds > 0) {
                $expiresAt = date('Y-m-d H:i:s', time() + $ttlSeconds);
            }
        }

        $existingId = $this->findRowId($cacheKey);
        $metadata = array(
            'options' => $request->option(),
            'request_metadata' => $request->metadata(),
        );

        if ($existingId !== null) {
            $sql = 'update `' . $table . '` set `provider`=?, `model`=?, `input_hash`=?, `inputs_json`=?, `result_json`=?, `metadata`=?, `updated_at`=?, `expires_at`=? where `id`=?';
            $this->connection()->run($sql, array(
                $request->provider(),
                $request->model(),
                $this->inputHash($request),
                json_encode($request->inputs()),
                json_encode($result->toArray()),
                json_encode($metadata),
                $now,
                $expiresAt,
                $existingId,
            ));
        } else {
            $sql = 'insert into `' . $table . '` (`cache_key`, `provider`, `model`, `input_hash`, `inputs_json`, `result_json`, `metadata`, `created_at`, `updated_at`, `expires_at`) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
            $this->connection()->run($sql, array(
                $cacheKey,
                $request->provider(),
                $request->model(),
                $this->inputHash($request),
                json_encode($request->inputs()),
                json_encode($result->toArray()),
                json_encode($metadata),
                $now,
                $now,
                $expiresAt,
            ));
        }

        return $result;
    }

    protected function findRowId($cacheKey)
    {
        $db = $this->connection()->db();
        $sql = 'select `id` from `' . $this->table() . '` where `cache_key`=? limit 1';
        $result = $this->connection()->run($sql, array($cacheKey));
        $row = $db->next($result);
        return $row ? $row->get('id') : null;
    }

    protected function inputHash(EmbeddingRequest $request)
    {
        return sha1(json_encode(array(
            'provider' => $request->provider(),
            'model' => $request->model(),
            'inputs' => $request->inputs(),
        )));
    }

    protected function table()
    {
        return $this->connection()->config()->table('embeddings');
    }
}
