using {{ namespace }}.Data;

namespace {{ namespace }}.Services;

/// <summary>
/// Backfills `mcp_store.db`'s `semantic_endpoints` table — mcpify's
/// shared generation pipeline writes the table's schema but leaves it
/// empty (vectors are computed here, in the target language, not by
/// mcpify's own Rust generator — see the plan's embeddings decision).
/// Invoked by `Dockerfile` before the image is considered usable, and by
/// `run_generated_tests` (Story C8) before the test suite runs — the same
/// way v1 sequences `populate-embeddings` before `npm test`.
/// </summary>
public sealed class PopulateEmbeddingsService(SqliteVecStore store, EmbeddingService embeddingService)
{
    /// <summary>
    /// Which store file(s) to populate: bare invocation (both <paramref
    /// name="all"/> and <paramref name="apiVersion"/> unset) targets just
    /// `mcp_store.db` (the default version), matching every deployment
    /// that only ever ships that one file (this project's own Dockerfile
    /// included); an explicit <paramref name="apiVersion"/> targets
    /// exactly that version's file; <paramref name="all"/> walks every
    /// version this project's ledger knows about
    /// (<see cref="SqliteVecStore.AllStoreFiles"/>) — the one-time local
    /// backfill needed after `mcpify add-version` adds a new version
    /// file, since each version's `.db` gets its own independent
    /// `semantic_endpoints` table.
    /// </summary>
    public static IEnumerable<string> TargetDbPaths(bool all, string? apiVersion)
    {
        if (all)
        {
            return SqliteVecStore.AllStoreFiles;
        }
        if (apiVersion is not null)
        {
            return [SqliteVecStore.ResolveStorePath(apiVersion)];
        }
        return ["mcp_store.db"];
    }

    public async Task<int> PopulateAsync(CancellationToken cancellationToken = default)
    {
        var rows = new List<(string OperationId, string Text)>();
        using (var connection = store.OpenReadWrite())
        {
            using var selectCommand = connection.CreateCommand();
            selectCommand.CommandText = "SELECT operation_id, method, path, summary, description FROM endpoints";
            using var reader = selectCommand.ExecuteReader();
            while (reader.Read())
            {
                var parts = new[]
                {
                    reader.GetString(1),
                    reader.GetString(2),
                    reader.IsDBNull(3) ? null : reader.GetString(3),
                    reader.IsDBNull(4) ? null : reader.GetString(4),
                }.Where(p => !string.IsNullOrEmpty(p));
                rows.Add((reader.GetString(0), string.Join(' ', parts)));
            }
        }

        var count = 0;
        using (var connection = store.OpenReadWrite())
        {
            foreach (var (operationId, text) in rows)
            {
                var vector = await embeddingService.EmbedAsync(text, cancellationToken);

                // sqlite-vec's vec0 virtual tables don't implement conflict
                // resolution (no ON CONFLICT / INSERT OR REPLACE support — a
                // duplicate primary key always raises a UNIQUE constraint
                // error), so re-running this against a db that already has
                // rows requires an explicit delete before each insert to
                // stay idempotent.
                using var deleteCommand = connection.CreateCommand();
                deleteCommand.CommandText = "DELETE FROM semantic_endpoints WHERE operation_id = $operationId";
                deleteCommand.Parameters.AddWithValue("$operationId", operationId);
                deleteCommand.ExecuteNonQuery();

                using var insertCommand = connection.CreateCommand();
                insertCommand.CommandText =
                    "INSERT INTO semantic_endpoints (operation_id, embedding) VALUES ($operationId, $embedding)";
                insertCommand.Parameters.AddWithValue("$operationId", operationId);
                insertCommand.Parameters.AddWithValue("$embedding", SqliteVecStore.SerializeFloat32(vector));
                insertCommand.ExecuteNonQuery();
                count++;
            }
        }

        return count;
    }
}
