<?php

namespace App\LLM;

/**
 * Loads LLM provider config and returns a configured, ready-to-use provider instance.
 *
 * Two scopes:
 *   Application-level  — stored in sp_settings (group='llm'), admin-managed, shared across all orgs.
 *   Org-level          — stored in sp_orgs_meta, per-org overrides (optional future use).
 *
 * Usage:
 *   $llm = LLMManager::forApp('anthropic');          // application-level (sp_settings)
 *   $llm = LLMManager::forOrg($orgId, 'openai');     // org-level (sp_orgs_meta)
 */
class LLMManager
{
    // Maps provider key to provider class
    private static array $providers = [
        'openai'    => svcOpenAI::class,
        'anthropic' => svcAnthropic::class,
        'gemini'    => svcGemini::class,
        'mistral'   => svcMistral::class,
        'deepseek'  => svcDeepSeek::class,
    ];

    // Maps provider key to sp_settings.keyval  (application-level)
    private static array $settingsKeys = [
        'openai'    => 'llm_openai',
        'anthropic' => 'llm_anthropic',
        'gemini'    => 'llm_gemini',
        'mistral'   => 'llm_mistral',
        'deepseek'  => 'llm_deepseek',
    ];

    // Maps provider key to sp_orgs_meta.keyval  (org-level overrides)
    private static array $metaKeys = [
        'openai'    => 'llmOpenAI',
        'anthropic' => 'llmAnthropic',
        'gemini'    => 'llmGemini',
        'mistral'   => 'llmMistral',
        'deepseek'  => 'llmDeepSeek',
    ];

    // Provider preference order for auto-selection
    public static array $priority = ['anthropic', 'openai', 'gemini', 'mistral', 'deepseek'];

    /**
     * Instantiate a provider directly from a pre-loaded config array.
     * Use this when you already have the config and don't need another DB lookup.
     *
     * @throws \Exception if provider unknown
     */
    public static function make(string $provider, array $config): LLMProvider
    {
        $provider = strtolower($provider);
        if (!isset(self::$providers[$provider])) {
            throw new \Exception("Unknown LLM provider: '$provider'");
        }
        $class = self::$providers[$provider];
        return new $class($config);
    }

    // ─── Application-level (sp_settings) ────────────────────────────────────

    /**
     * Return a configured provider instance from the application-level sp_settings table.
     *
     * @throws \Exception if provider unknown or not configured
     */
    public static function forApp(string $provider): LLMProvider
    {
        $provider = strtolower($provider);

        if (!isset(self::$providers[$provider])) {
            throw new \Exception("Unknown LLM provider: '$provider'");
        }

        $config = self::getAppConfig($provider);

        if (empty($config)) {
            throw new \Exception("No '$provider' configuration found. Add one under Admin → Integrations.");
        }

        $class = self::$providers[$provider];
        return new $class($config);
    }

    /**
     * Read and normalize a provider's config from sp_settings.
     * Returns [] if not found or api_key is empty.
     */
    public static function getAppConfig(string $provider): array
    {
        $provider    = strtolower($provider);
        $settingsKey = self::$settingsKeys[$provider] ?? null;
        if (!$settingsKey) return [];

        $row = \Db::getRow(
            "SELECT metval FROM sp_settings WHERE `group` = 'llm' AND keyval = ? LIMIT 1",
            [$settingsKey]
        );

        if (!$row || empty($row['metval'])) return [];

        $raw = json_decode($row['metval'], true);
        if (!is_array($raw) || empty($raw['api_key'])) return [];

        // Normalize sp_settings field names to LLMProvider expected names
        return array_filter([
            'secretKey'   => $raw['api_key'],
            'model'       => $raw['model']       ?? null,
            'max_tokens'  => $raw['max_tokens']  ?? null,
            'temperature' => $raw['temperature'] ?? null,
        ]);
    }

    /**
     * Return the list of application-level providers that have a non-empty api_key.
     *
     * @return array e.g. ['anthropic', 'openai']
     */
    public static function getAppProviders(): array
    {
        $available = [];

        foreach (self::$settingsKeys as $provider => $keyval) {
            $row = \Db::getRow(
                "SELECT metval FROM sp_settings WHERE `group` = 'llm' AND keyval = ? LIMIT 1",
                [$keyval]
            );
            if (!$row || empty($row['metval'])) continue;
            $cfg = json_decode($row['metval'], true);
            if (!empty($cfg['api_key'])) {
                $available[] = $provider;
            }
        }

        return $available;
    }

    /**
     * Return the first configured application-level provider (by priority), or null.
     */
    public static function getDefaultAppProvider(): ?string
    {
        $available = self::getAppProviders();
        foreach (self::$priority as $p) {
            if (in_array($p, $available)) return $p;
        }
        return null;
    }

    // ─── Org-level (sp_orgs_meta) ────────────────────────────────────────────

    /**
     * Return a configured provider instance from sp_orgs_meta (org-level override).
     *
     * @throws \Exception if provider unknown or not configured for the org
     */
    public static function forOrg(int $orgId, string $provider): LLMProvider
    {
        $provider = strtolower($provider);

        if (!isset(self::$providers[$provider])) {
            throw new \Exception("Unknown LLM provider: '$provider'");
        }

        $config = self::getOrgConfig($orgId, $provider);

        if (empty($config)) {
            throw new \Exception("No '$provider' config found for org $orgId");
        }

        $class = self::$providers[$provider];
        return new $class($config);
    }

    /**
     * Read and decode a provider config from sp_orgs_meta for the given org.
     */
    public static function getOrgConfig(int $orgId, string $provider): array
    {
        $provider = strtolower($provider);
        $metaKey  = self::$metaKeys[$provider] ?? null;
        if (!$metaKey) return [];

        $row = \Db::getRow(
            "SELECT metval FROM sp_orgs_meta WHERE orgid = ? AND keyval = ? AND active = 1 LIMIT 1",
            [$orgId, $metaKey]
        );

        if (!$row || empty($row['metval'])) return [];

        $config = json_decode($row['metval'], true);
        return is_array($config) ? $config : [];
    }

    /**
     * Return which providers are configured for the given org (sp_orgs_meta only).
     */
    public static function getAvailableProviders(int $orgId): array
    {
        $metaKeys = array_values(self::$metaKeys);
        $rows = \Db::select(
            "SELECT keyval FROM sp_orgs_meta WHERE orgid = ? AND keyval IN ('" . implode("','", $metaKeys) . "') AND active = 1",
            [$orgId]
        );

        $flip      = array_flip(self::$metaKeys);
        $available = [];

        foreach ($rows as $row) {
            if (isset($flip[$row['keyval']])) {
                $available[] = $flip[$row['keyval']];
            }
        }

        return $available;
    }

    /**
     * Save or update a provider config for an org in sp_orgs_meta.
     */
    public static function saveOrgConfig(int $orgId, string $provider, array $config): void
    {
        $provider = strtolower($provider);
        $metaKey  = self::$metaKeys[$provider] ?? null;

        if (!$metaKey) {
            throw new \Exception("Unknown LLM provider: '$provider'");
        }

        $existing = \Db::getRow(
            "SELECT orgmetaid FROM sp_orgs_meta WHERE orgid = ? AND keyval = ? LIMIT 1",
            [$orgId, $metaKey]
        );

        if ($existing) {
            \Db::update('sp_orgs_meta',
                ['metval' => json_encode($config)],
                'orgmetaid = ?',
                [$existing['orgmetaid']]
            );
        } else {
            \Db::insert('sp_orgs_meta', [
                'orgid'  => $orgId,
                'keyval' => $metaKey,
                'metval' => json_encode($config),
                'active' => 1,
            ]);
        }
    }
}
