<?php

namespace ZenoChatbot\Services\Chat\Embedding;

if (!defined('ABSPATH')) {
    exit;
}

use ZenoChatbot\Model\SavedReply;
use ZenoChatbot\Services\Chat\Contracts\EmbeddingProviderInterface;
use ZenoChatbot\Helper\Settings;

/**
 * Embedding Service
 *
 * Manages embedding generation and storage for saved replies.
 * Provides methods to generate, update, and clear embeddings.
 */
class EmbeddingService
{
    protected EmbeddingProviderInterface $provider;

    public function __construct(?EmbeddingProviderInterface $provider = null)
    {
        $this->provider = $provider ?? $this->createProviderFromSettings();
    }

    /**
     * Create embedding provider based on LLM settings
     */
    protected function createProviderFromSettings(): EmbeddingProviderInterface
    {
        $llmSettings = Settings::getLLMSettings(true);
        $provider = $llmSettings['provider'] ?? 'openai';

        if (strtolower($provider) === 'gemini') {
            return new GeminiEmbeddingProvider();
        }

        return new OpenAIEmbeddingProvider();
    }

    /**
     * Get the current provider name
     */
    public function getProviderName(): string
    {
        if ($this->provider instanceof GeminiEmbeddingProvider) {
            return 'gemini';
        }

        if ($this->provider instanceof OpenAIEmbeddingProvider) {
            return 'openai';
        }

        return 'unknown';
    }

    /**
     * Generate and store embedding for a single saved reply
     *
     * @param int $replyId SavedReply ID
     * @return bool Success status
     */
    public function generateForReply(int $replyId): bool
    {
        $reply = SavedReply::find($replyId);

        if (!$reply) {
            return false;
        }

        try {
            $embedding = $this->provider->embed($reply->question);

            if (empty($embedding)) {
                return false;
            }

            $reply->embedding = json_encode($embedding);
            $reply->embedding_provider = $this->getProviderName();
            $reply->updated_at = current_time('mysql');
            return $reply->save();

        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Generate embeddings for all saved replies without embeddings
     *
     * @param int $batchSize Number of replies to process per batch
     * @param callable|null $progressCallback Called with (processed, total) for progress
     * @return array ['success' => int, 'failed' => int, 'skipped' => int]
     */
    public function generateForAll(int $batchSize = 20, ?callable $progressCallback = null): array
    {
        $stats = ['success' => 0, 'failed' => 0, 'skipped' => 0];

        // Get all active replies and filter those without embeddings in PHP
        // (Framework QueryBuilder doesn't support nested where closures)
        $allReplies = SavedReply::query()
            ->where('is_active', 1)
            ->get();

        // Filter to only those without embeddings
        $replies = [];
        foreach ($allReplies as $reply) {
            if (empty($reply->embedding)) {
                $replies[] = $reply;
            }
        }

        $total = count($replies);
        $processed = 0;

        if ($total === 0) {
            return $stats;
        }

        // Process in batches to use batch API
        $batches = array_chunk($replies, $batchSize);

        foreach ($batches as $batch) {
            $questions = [];
            $replyMap = [];

            foreach ($batch as $reply) {
                if (empty(trim($reply->question))) {
                    $stats['skipped']++;
                    $processed++;
                    continue;
                }
                $questions[] = $reply->question;
                $replyMap[] = $reply;
            }

            if (empty($questions)) {
                continue;
            }

            try {
                $embeddings = $this->provider->embedBatch($questions);
                $providerName = $this->getProviderName();

                foreach ($embeddings as $index => $embedding) {
                    if (isset($replyMap[$index])) {
                        $reply = SavedReply::find($replyMap[$index]->id);
                        if ($reply) {
                            $reply->embedding = json_encode($embedding);
                            $reply->embedding_provider = $providerName;
                            $reply->updated_at = current_time('mysql');
                            if ($reply->save()) {
                                $stats['success']++;
                            } else {
                                $stats['failed']++;
                            }
                        }
                    }
                    $processed++;

                    if ($progressCallback) {
                        $progressCallback($processed, $total);
                    }
                }

            } catch (\Exception $e) {
                $stats['failed'] += count($questions);
                $processed += count($questions);
            }
        }

        return $stats;
    }

    /**
     * Clear all embeddings
     *
     * @return int Number of replies updated
     */
    public function clearAll(): int
    {
        return SavedReply::query()->update(['embedding' => null, 'embedding_provider' => null]);
    }

    /**
     * Generate embeddings for ALL saved replies (regenerate)
     *
     * @param int $batchSize Number of replies to process per batch
     * @param callable|null $progressCallback Called with (processed, total) for progress
     * @return array ['success' => int, 'failed' => int, 'skipped' => int]
     */
    public function regenerateAll(int $batchSize = 20, ?callable $progressCallback = null): array
    {
        $stats = ['success' => 0, 'failed' => 0, 'skipped' => 0];

        // Get all active replies as array of objects
        $repliesCollection = SavedReply::query()
            ->where('is_active', 1)
            ->get();

        // Convert to plain array
        $replies = [];
        foreach ($repliesCollection as $reply) {
            $replies[] = $reply;
        }

        $total = count($replies);
        $processed = 0;

        if ($total === 0) {
            return $stats;
        }

        // Process in batches to use batch API
        $batches = array_chunk($replies, $batchSize);
        $providerName = $this->getProviderName();

        foreach ($batches as $batch) {
            $questions = [];
            $replyMap = [];

            foreach ($batch as $reply) {
                if (empty(trim($reply->question))) {
                    $stats['skipped']++;
                    $processed++;
                    if ($progressCallback) {
                        $progressCallback($processed, $total);
                    }
                    continue;
                }
                $questions[] = $reply->question;
                $replyMap[] = $reply;
            }

            if (empty($questions)) {
                continue;
            }

            try {
                $embeddings = $this->provider->embedBatch($questions);

                foreach ($embeddings as $index => $embedding) {
                    if (isset($replyMap[$index])) {
                        $replyToUpdate = SavedReply::find($replyMap[$index]->id);
                        if ($replyToUpdate) {
                            $replyToUpdate->embedding = json_encode($embedding);
                            $replyToUpdate->embedding_provider = $providerName;
                            $replyToUpdate->updated_at = current_time('mysql');
                            if ($replyToUpdate->save()) {
                                $stats['success']++;
                            } else {
                                $stats['failed']++;
                            }
                        }
                    }
                    $processed++;

                    if ($progressCallback) {
                        $progressCallback($processed, $total);
                    }
                }

            } catch (\Exception $e) {
                $stats['failed'] += count($questions);
                $processed += count($questions);

                if ($progressCallback) {
                    $progressCallback($processed, $total);
                }
            }
        }

        return $stats;
    }

    /**
     * Get embedding statistics including provider breakdown
     *
     * @return array Statistics about embeddings
     */
    public function getStats(): array
    {
        global $wpdb;

        $total = SavedReply::query()
            ->where('is_active', 1)
            ->count();

        $withEmbeddings = SavedReply::query()
            ->where('is_active', 1)
            ->whereNotNull('embedding')
            ->count();

        $cacheKey   = 'zeno_embedding_provider_stats';
        $cacheGroup = 'zeno_chatbot';

        $providerStats = wp_cache_get($cacheKey, $cacheGroup);

        if ($providerStats === false) {
            $table = esc_sql($wpdb->prefix . 'zenochatbot_saved_replies');

            $sql = "
            SELECT embedding_provider, COUNT(*) AS count
            FROM {$table}
            WHERE is_active = 1
              AND embedding IS NOT NULL
              AND embedding != ''
            GROUP BY embedding_provider
        ";

            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery
            $providerStats = $wpdb->get_results($sql, ARRAY_A);

            wp_cache_set($cacheKey, $providerStats, $cacheGroup, 300);
        }

        $providers = [];
        foreach ($providerStats as $row) {
            $provider = $row['embedding_provider'] ?? 'unknown';
            $providers[$provider] = (int) $row['count'];
        }

        $currentProvider = $this->getProviderName();

        $mismatched = 0;
        foreach ($providers as $provider => $count) {
            if ($provider !== $currentProvider && $provider !== 'unknown') {
                $mismatched += $count;
            }
        }

        return [
            'total'              => $total,
            'with_embeddings'    => $withEmbeddings,
            'without_embeddings' => max(0, $total - $withEmbeddings),
            'provider'           => $currentProvider,
            'provider_stats'     => $providers,
            'mismatched'         => $mismatched,
        ];
    }

    /**
     * Check if a reply has an embedding
     */
    public function hasEmbedding(int $replyId): bool
    {
        $reply = SavedReply::find($replyId);
        return $reply && !empty($reply->embedding);
    }
}

