<?php
namespace boru\ai\Tests\Integration;

use boru\ai\AI;
use boru\ai\Bootstrap;
use boru\ai\Config\Config;
use boru\ai\Config\Env;
use boru\ai\Http\HttpResponse;
use boru\ai\Persistence\Db\DbConfig;
use boru\ai\Persistence\Db\DbConnection;
use boru\ai\Persistence\Db\DbSchemaManager;
use boru\ai\Tests\Fakes\FakeHttpClient;
use boru\dhutils\dhGlobal;

abstract class DbIntegrationTestCase extends IntegrationTestCase
{
    /** @var DbConfig|null */
    protected $dbConfig;

    /** @var array */
    protected $tableNames = array();

    /** @var string|null */
    protected $skipReason;

    protected function setUp(): void
    {
        parent::setUp();
        dhGlobal::remove('dhDB');
    }

    protected function tearDown(): void
    {
        $this->dropTestTables();
        dhGlobal::remove('dhDB');
        $this->dbConfig = null;
        $this->tableNames = array();
        $this->skipReason = null;
        parent::tearDown();
    }

    protected function bootstrapWithDbPersistence(FakeHttpClient $openAIHttpClient = null, FakeHttpClient $geminiHttpClient = null, array $persistence = array())
    {
        $config = new Config();
        $config->provider('openai', array(
            'api_key' => 'test-openai-key',
            'http_client' => $openAIHttpClient,
        ));
        $config->provider('gemini', array(
            'api_key' => 'test-gemini-key',
            'http_client' => $geminiHttpClient,
        ));

        $prefix = 'ai_test_' . strtolower(substr(sha1(uniqid('', true)), 0, 12));
        $tables = array(
            'conversations' => $prefix . '_conversations',
            'turns' => $prefix . '_turns',
            'assets' => $prefix . '_assets',
            'provider_asset_bindings' => $prefix . '_provider_asset_bindings',
            'tool_calls' => $prefix . '_tool_calls',
            'embeddings' => $prefix . '_embeddings',
            'conversation_notes' => $prefix . '_conversation_notes',
        );

        $config->persistence(array_merge(array(
            'enabled' => true,
            'driver' => 'db',
            'auto_schema' => true,
            'persist_tool_calls' => true,
            'tables' => $tables,
        ), $persistence));

        $config->db(array(
            'use' => true,
            'dbhost' => Env::get('AI_TEST_DB_HOST', '127.0.0.1'),
            'dbport' => (int)Env::get('AI_TEST_DB_PORT', 3306),
            'dbname' => Env::get('AI_TEST_DB_NAME', 'ai_test'),
            'dbuser' => Env::get('AI_TEST_DB_USER', 'root'),
            'dbpass' => Env::get('AI_TEST_DB_PASS', ''),
            'dbtype' => Env::get('AI_TEST_DB_TYPE', 'mysql'),
        ));

        $this->dbConfig = DbConfig::fromConfig($config);
        $this->tableNames = $tables;

        if (Env::get('AI_TEST_DB_ENABLED', '0') !== '1') {
            $this->skipReason = 'AI_TEST_DB_ENABLED is not set to 1';
            $this->markTestSkipped($this->skipMessage());
        }

        if (!$this->canConnect()) {
            $this->markTestSkipped($this->skipMessage());
        }

        Bootstrap::registerDefaults($config);
        return $config;
    }

    protected function canConnect()
    {
        if (!$this->dbConfig instanceof DbConfig) {
            return false;
        }

        try {
            dhGlobal::remove('dhDB');
            $db = new DbConnection($this->dbConfig);
            $handle = $db->db();
            if (!$handle) {
                $this->skipReason = 'DbConnection did not return a DB handle';
                return false;
            }

            $result = $handle->run('select 1 as one');
            $row = $handle->next($result);
            if (!$row || (int)$row->get('one') !== 1) {
                $this->skipReason = 'Live DB query did not return the expected row';
                return false;
            }
            return $row && (int)$row->get('one') === 1;
        } catch (\Throwable $e) {
            $this->skipReason = get_class($e) . ': ' . $e->getMessage();
            return false;
        }
    }

    protected function connection()
    {
        return new DbConnection($this->dbConfig);
    }

    protected function fetchOne($sql, array $params = array())
    {
        $db = $this->connection()->db($this->dbConfig->toArray());
        $result = $db->run($sql, $params);
        return $db->next($result);
    }

    protected function countRows($table)
    {
        $row = $this->fetchOne('select count(*) as row_count from `' . $table . '`');
        if (!$row) {
            return 0;
        }
        return (int)$row->get('row_count');
    }

    protected function dropTestTables()
    {
        if (!$this->dbConfig instanceof DbConfig || empty($this->tableNames) || !$this->canConnect()) {
            return;
        }

        $db = $this->connection()->db($this->dbConfig->toArray());
        $ordered = array_reverse(array_values($this->tableNames));
        foreach ($ordered as $table) {
            $db->run('drop table if exists `' . $table . '`');
        }
    }

    protected function skipMessage()
    {
        return sprintf(
            'DB integration tests unavailable for %s@%s:%s/%s (%s). Reason: %s',
            $this->dbConfig ? $this->dbConfig->get('dbuser', 'unknown-user') : 'unknown-user',
            $this->dbConfig ? $this->dbConfig->get('dbhost', 'unknown-host') : 'unknown-host',
            $this->dbConfig ? $this->dbConfig->get('dbport', 'unknown-port') : 'unknown-port',
            $this->dbConfig ? $this->dbConfig->get('dbname', 'unknown-db') : 'unknown-db',
            $this->dbConfig ? $this->dbConfig->get('dbtype', 'unknown-driver') : 'unknown-driver',
            $this->skipReason ? $this->skipReason : 'unknown reason'
        );
    }
}
