<?php
namespace boru\ai;

use boru\ai\Config\Config;
use boru\ai\Config\Env;
use boru\ai\Mcp\ClientBackedMcpToolDiscovery;
use boru\ai\Mcp\ClientBackedMcpToolInvoker;
use boru\ai\Mcp\HttpProxyMcpClientFactory;
use boru\ai\Mcp\McpServerConfig;
use boru\ai\Mcp\RoutingMcpClientFactory;
use boru\ai\Mcp\TcpMcpClientFactory;
use boru\ai\Persistence\Db\DbAssetStore;
use boru\ai\Persistence\Db\DbConfig;
use boru\ai\Persistence\Db\DbConnection;
use boru\ai\Persistence\Db\DbEmbeddingStore;
use boru\ai\Persistence\Db\DbPersistenceProvider;
use boru\ai\Persistence\Db\DbSchemaManager;
use boru\ai\Provider\Gemini\GeminiAssetProvider;
use boru\ai\Provider\Gemini\GeminiClient;
use boru\ai\Provider\Gemini\GeminiProvider;
use boru\ai\Provider\OpenAI\OpenAIAssetProvider;
use boru\ai\Provider\OpenAI\OpenAIClient;
use boru\ai\Provider\OpenAI\OpenAIProvider;


class Bootstrap
{
    public static function registerDefaults(Config $config = null)
    {
        $config = $config ? $config : static::defaultConfig();
        AI::config($config);
        $registry = AI::registry();

        if (!$registry->has('openai')) {
            $openAIConfig = $config->provider('openai');
            $client = new OpenAIClient(null, $openAIConfig);
            $registry->register(new OpenAIProvider($client));
            AI::assetProviders()->register(new OpenAIAssetProvider($client));
        }

        if (!$registry->has('gemini')) {
            $geminiConfig = $config->provider('gemini');
            $client = new GeminiClient(null, $geminiConfig);
            $registry->register(new GeminiProvider($client));
            AI::assetProviders()->register(new GeminiAssetProvider($client));
        }

        $registry->mapModelPrefix('gpt-', 'openai');
        $registry->mapModelPrefix('o', 'openai');
        $registry->mapModelPrefix('chatgpt-', 'openai');
        $registry->mapModelPrefix('gemini-', 'gemini');

        static::registerMcp($config);
        static::registerMcpServices($config);

        static::registerPersistence($config);

        return $registry;
    }

    public static function defaultConfig()
    {
        $config = new Config();
        $config->defaults(array(
            'provider' => 'openai',
            'model' => 'gpt-4.1',
        ));
        $config->runtime(array(
            'replay' => array(
                'enabled' => false,
                'strategy' => 'full',
                'max_turns' => 10,
                'summary' => array(
                    'keep_turns' => 3,
                    'max_chars' => 4000,
                ),
                'tool_data' => array(
                    'enabled' => false,
                    'max_chars' => 2000,
                    'args_max_chars' => 600,
                    'output_max_chars' => 1000,
                ),
            ),
            'conversation_memory' => array(
                'tools' => array(
                    'enabled' => false,
                    'title' => true,
                    'summary' => true,
                    'append_note' => true,
                    'replace_notes' => false,
                    'require_persistence' => true,
                    'only_after_turn' => 2,
                ),
            ),
        ));
        $config->vector(array(
            'cache' => array(
                'enabled' => true,
                'read' => true,
                'write' => true,
                'ttl_seconds' => null,
                'namespace' => null,
            ),
        ));
        $config->provider('openai', array(
            'api_key' => Env::get('OPENAI_API_KEY'),
            'base_url' => Env::get('OPENAI_BASE_URL', 'https://api.openai.com/v1'),
            'timeout' => (int)Env::get('OPENAI_TIMEOUT', 60),
            'raw_http_debug' => false,
            'stream_read_size' => 1024,
            'vector' => array(
                'model' => Env::get('OPENAI_EMBEDDING_MODEL', 'text-embedding-3-small'),
            ),
            'replay' => array(
                'enabled' => true,
                'strategy' => 'full',
                'max_turns' => 10,
                'summary' => array(
                    'keep_turns' => 3,
                    'max_chars' => 4000,
                ),
                'tool_data' => array(
                    'enabled' => false,
                    'max_chars' => 2000,
                    'args_max_chars' => 600,
                    'output_max_chars' => 1000,
                ),
            ),
            'asset_bindings' => array(
                'reuse' => true,
                'max_age_seconds' => null,
                'delete_remote_on_cleanup' => false,
                'track_last_used_at' => true,
            ),
        ));
        $config->provider('gemini', array(
            'api_key' => Env::get('GEMINI_API_KEY'),
            'base_url' => Env::get('GEMINI_BASE_URL', 'https://generativelanguage.googleapis.com/v1beta'),
            'timeout' => (int)Env::get('GEMINI_TIMEOUT', 60),
            'raw_http_debug' => false,
            'stream_read_size' => 1024,
            'vector' => array(
                'model' => Env::get('GEMINI_EMBEDDING_MODEL', 'text-embedding-004'),
            ),
            'replay' => array(
                'enabled' => true,
                'strategy' => 'full',
                'max_turns' => 10,
                'summary' => array(
                    'keep_turns' => 3,
                    'max_chars' => 4000,
                ),
                'tool_data' => array(
                    'enabled' => false,
                    'max_chars' => 2000,
                    'args_max_chars' => 600,
                    'output_max_chars' => 1000,
                ),
            ),
            'asset_bindings' => array(
                'reuse' => true,
                'max_age_seconds' => null,
                'delete_remote_on_cleanup' => false,
                'track_last_used_at' => true,
            ),
        ));
        $config->persistence(array(
            'enabled' => false,
            'driver' => 'db',
            'auto_schema' => false,
            'persist_tool_calls' => false,
            'tables' => array(
                'conversations' => 'ai_conversations',
                'turns' => 'ai_turns',
                'assets' => 'ai_assets',
                'provider_asset_bindings' => 'ai_provider_asset_bindings',
                'tool_calls' => 'ai_tool_calls',
                'embeddings' => 'ai_embeddings',
                'conversation_notes' => 'ai_conversation_notes',
            ),
            'asset_bindings' => array(
                'reuse' => true,
                'max_age_seconds' => null,
                'delete_remote_on_cleanup' => false,
                'track_last_used_at' => true,
            ),
        ));
        $config->db(array(
            'use' => true,
            'dbhost' => Env::get('AI_DB_HOST', '127.0.0.1'),
            'dbport' => (int)Env::get('AI_DB_PORT', 3306),
            'dbname' => Env::get('AI_DB_NAME', 'ai'),
            'dbuser' => Env::get('AI_DB_USER', 'root'),
            'dbpass' => Env::get('AI_DB_PASS', ''),
            'dbtype' => Env::get('AI_DB_ADAPTER', 'mysql'),
        ));
        $config->mcp(array(
            'enabled' => false,
            'servers' => array(),
            'client' => array(
                'driver' => null,
                'timeout' => 10,
            ),
            'discovery' => array(
                'enabled' => false,
                'auto_register' => false,
            ),
        ));

        return $config;
    }

    public static function ensurePersistenceSchema(Config $config = null)
    {
        $config = $config ? $config : static::defaultConfig();
        $dbConfig = DbConfig::fromConfig($config);

        if (!$dbConfig->enabled()) {
            return false;
        }

        if ($dbConfig->driver() !== 'db') {
            throw new \RuntimeException('Unsupported persistence driver: ' . $dbConfig->driver());
        }

        $connection = new DbConnection($dbConfig);
        $schema = new DbSchemaManager($connection);
        $schema->ensureSchema();

        return $schema;
    }

    protected static function registerPersistence(Config $config)
    {
        $dbConfig = DbConfig::fromConfig($config);

        if (!$dbConfig->enabled()) {
            return;
        }

        if ($dbConfig->driver() !== 'db') {
            throw new \RuntimeException('Unsupported persistence driver: ' . $dbConfig->driver());
        }

        $connection = new DbConnection($dbConfig);
        AI::persistence(new DbPersistenceProvider($connection));
        AI::assetStore(new DbAssetStore($connection));
        AI::embeddingStore(new DbEmbeddingStore($connection));

        if ($dbConfig->autoSchema()) {
            $schema = new DbSchemaManager($connection);
            $schema->ensureSchema();
        }
    }

    protected static function registerMcp(Config $config)
    {
        $mcp = $config->mcp();
        if (!isset($mcp['enabled']) || !$mcp['enabled']) {
            return;
        }

        $servers = isset($mcp['servers']) && is_array($mcp['servers']) ? $mcp['servers'] : array();
        foreach ($servers as $name => $serverConfig) {
            if (!is_array($serverConfig)) {
                continue;
            }

            if (!isset($serverConfig['name']) || !is_string($serverConfig['name']) || trim($serverConfig['name']) === '') {
                if (is_string($name) && trim($name) !== '') {
                    $serverConfig['name'] = $name;
                }
            }

            AI::mcpServers()->register(new McpServerConfig($serverConfig));
        }
    }

    protected static function registerMcpServices(Config $config)
    {
        $mcp = $config->mcp();
        if (!isset($mcp['enabled']) || !$mcp['enabled']) {
            return;
        }

        $clientConfig = isset($mcp['client']) && is_array($mcp['client']) ? $mcp['client'] : array();
        $factory = static::buildMcpClientFactory(AI::mcpServers()->all(), $clientConfig);
        if (!$factory) {
            return;
        }

        AI::mcpClientFactory($factory);
        AI::mcpToolInvoker(new ClientBackedMcpToolInvoker(AI::mcpServers(), $factory));

        $discoveryConfig = isset($mcp['discovery']) && is_array($mcp['discovery']) ? $mcp['discovery'] : array();
        if (!isset($discoveryConfig['enabled']) || $discoveryConfig['enabled']) {
            AI::mcpToolDiscovery(new ClientBackedMcpToolDiscovery($factory));
        }
    }

    protected static function buildMcpClientFactory(array $servers, array $clientConfig = array())
    {
        $driver = isset($clientConfig['driver']) && is_string($clientConfig['driver']) ? trim($clientConfig['driver']) : '';
        if ($driver !== '') {
            return static::makeMcpClientFactoryForDriver($driver, $clientConfig);
        }

        $transports = array();
        $routingFactory = new RoutingMcpClientFactory();
        foreach ($servers as $server) {
            if (!$server instanceof McpServerConfig) {
                continue;
            }

            $transport = strtolower((string)$server->transport());
            if ($transport === 'http' || $transport === 'http_proxy') {
                $transports['http_proxy'] = true;
                if (!$routingFactory->has($transport)) {
                    $routingFactory->register($transport, static::makeMcpClientFactoryForDriver('http_proxy', $clientConfig));
                }
                continue;
            }

            if ($transport === 'tcp') {
                $transports['tcp'] = true;
                $routingFactory->register($transport, static::makeMcpClientFactoryForDriver('tcp', $clientConfig));
            }
        }

        if (count($transports) === 1) {
            $names = array_keys($transports);
            return static::makeMcpClientFactoryForDriver($names[0], $clientConfig);
        }

        if ($routingFactory->has('http') || $routingFactory->has('http_proxy') || $routingFactory->has('tcp')) {
            return $routingFactory;
        }

        return null;
    }

    protected static function makeMcpClientFactoryForDriver($driver, array $clientConfig = array())
    {
        if ($driver === 'http_proxy') {
            return new HttpProxyMcpClientFactory(null, array(
                'timeout' => isset($clientConfig['timeout']) ? (int)$clientConfig['timeout'] : 10,
            ));
        }

        if ($driver === 'tcp') {
            return new TcpMcpClientFactory();
        }

        throw new \RuntimeException('Unsupported MCP client driver: ' . $driver);
    }

}
