using ChatApi.Data; using ChatRAG.Contracts.VectorSearch; using ChatRAG.Data; using ChatRAG.Models; using ChatRAG.Services.Contracts; using ChatRAG.Services.SearchVectors; using ChatRAG.Settings.ChatRAG.Configuration; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.SemanticKernel.Embeddings; #pragma warning disable SKEXP0001 namespace ChatApi.Controllers { [ApiController] [Route("api/[controller]")] public class MigrationController : ControllerBase { private readonly IVectorDatabaseFactory _factory; private readonly ILogger _logger; private readonly VectorDatabaseSettings _settings; private readonly ITextEmbeddingGenerationService _embeddingService; private readonly TextDataRepository _mongoRepository; public MigrationController( IVectorDatabaseFactory factory, ILogger logger, IOptions settings, ITextEmbeddingGenerationService embeddingService, TextDataRepository mongoRepository) { _factory = factory; _logger = logger; _settings = settings.Value; _embeddingService = embeddingService; _mongoRepository = mongoRepository; } /// /// Status da migração e informações dos providers /// [HttpGet("status")] public async Task GetMigrationStatus() { try { var currentProvider = _factory.GetActiveProvider(); var status = new { CurrentProvider = currentProvider, Settings = new { Provider = _settings.Provider, MongoDB = _settings.MongoDB != null ? new { DatabaseName = _settings.MongoDB.DatabaseName, TextCollection = _settings.MongoDB.TextCollectionName } : null, Qdrant = _settings.Qdrant != null ? new { Host = _settings.Qdrant.Host, Port = _settings.Qdrant.Port, Collection = _settings.Qdrant.CollectionName, VectorSize = _settings.Qdrant.VectorSize } : null }, Stats = await GetProvidersStats() }; return Ok(status); } catch (Exception ex) { _logger.LogError(ex, "Erro ao obter status da migração"); return StatusCode(500, new { error = ex.Message }); } } /// /// Migra dados do MongoDB para Qdrant /// [HttpPost("mongo-to-qdrant")] public async Task MigrateMongoToQdrant( [FromQuery] string? projectId = null, [FromQuery] int batchSize = 50, [FromQuery] bool dryRun = false) { try { if (_settings.Provider != "MongoDB") { return BadRequest("Migração só funciona quando o provider atual é MongoDB"); } _logger.LogInformation("Iniciando migração MongoDB → Qdrant. ProjectId: {ProjectId}, DryRun: {DryRun}", projectId, dryRun); var result = await PerformMigration(projectId, batchSize, dryRun); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Erro durante migração MongoDB → Qdrant"); return StatusCode(500, new { error = ex.Message }); } } /// /// Migra dados do Qdrant para MongoDB /// [HttpPost("qdrant-to-mongo")] public async Task MigrateQdrantToMongo( [FromQuery] string? projectId = null, [FromQuery] int batchSize = 50, [FromQuery] bool dryRun = false) { try { if (_settings.Provider != "Qdrant") { return BadRequest("Migração só funciona quando o provider atual é Qdrant"); } _logger.LogInformation("Iniciando migração Qdrant → MongoDB. ProjectId: {ProjectId}, DryRun: {DryRun}", projectId, dryRun); // TODO: Implementar migração reversa se necessário return BadRequest("Migração Qdrant → MongoDB ainda não implementada"); } catch (Exception ex) { _logger.LogError(ex, "Erro durante migração Qdrant → MongoDB"); return StatusCode(500, new { error = ex.Message }); } } /// /// Compara dados entre MongoDB e Qdrant /// [HttpPost("compare")] public async Task CompareProviders([FromQuery] string? projectId = null) { try { // Cria serviços para ambos os providers manualmente var mongoService = CreateMongoService(); var qdrantService = await CreateQdrantService(); var comparison = await CompareData(mongoService, qdrantService, projectId); return Ok(comparison); } catch (Exception ex) { _logger.LogError(ex, "Erro ao comparar providers"); return StatusCode(500, new { error = ex.Message }); } } /// /// Limpa todos os dados do provider atual /// [HttpDelete("clear-current")] public async Task ClearCurrentProvider([FromQuery] string? projectId = null) { try { var vectorSearchService = _factory.CreateVectorSearchService(); if (string.IsNullOrEmpty(projectId)) { // Limpar tudo - PERIGOSO! return BadRequest("Limpeza completa requer confirmação. Use /clear-current/confirm"); } // Limpar apenas um projeto var documents = await vectorSearchService.GetDocumentsByProjectAsync(projectId); var ids = documents.Select(d => d.Id).ToList(); foreach (var id in ids) { await vectorSearchService.DeleteDocumentAsync(id); } return Ok(new { provider = _settings.Provider, projectId, deletedCount = ids.Count }); } catch (Exception ex) { _logger.LogError(ex, "Erro ao limpar dados do provider"); return StatusCode(500, new { error = ex.Message }); } } /// /// Testa conectividade dos providers /// [HttpGet("test-connections")] public async Task TestConnections() { var results = new Dictionary(); // Testar MongoDB try { var mongoService = CreateMongoService(); var mongoHealth = await mongoService.IsHealthyAsync(); var mongoStats = await mongoService.GetStatsAsync(); results["MongoDB"] = new { healthy = mongoHealth, stats = mongoStats }; } catch (Exception ex) { results["MongoDB"] = new { healthy = false, error = ex.Message }; } // Testar Qdrant try { var qdrantService = await CreateQdrantService(); var qdrantHealth = await qdrantService.IsHealthyAsync(); var qdrantStats = await qdrantService.GetStatsAsync(); results["Qdrant"] = new { healthy = qdrantHealth, stats = qdrantStats }; } catch (Exception ex) { results["Qdrant"] = new { healthy = false, error = ex.Message }; } return Ok(results); } // ======================================== // MÉTODOS PRIVADOS // ======================================== private async Task PerformMigration(string? projectId, int batchSize, bool dryRun) { var mongoService = CreateMongoService(); var qdrantService = await CreateQdrantService(); // 1. Buscar dados do MongoDB List documents; if (string.IsNullOrEmpty(projectId)) { var allDocs = await _mongoRepository.GetAsync(); documents = allDocs.ToList(); } else { var projectDocs = await _mongoRepository.GetByProjectIdAsync(projectId); documents = projectDocs.ToList(); } _logger.LogInformation("Encontrados {Count} documentos para migração", documents.Count); if (dryRun) { return new { dryRun = true, documentsFound = documents.Count, projects = documents.GroupBy(d => d.ProjetoId).Select(g => new { projectId = g.Key, count = g.Count() }).ToList() }; } // 2. Migrar em batches var migrated = 0; var errors = new List(); for (int i = 0; i < documents.Count; i += batchSize) { var batch = documents.Skip(i).Take(batchSize); foreach (var doc in batch) { try { // Verificar se já existe no Qdrant var exists = await qdrantService.DocumentExistsAsync(doc.Id); if (exists) { _logger.LogDebug("Documento {Id} já existe no Qdrant, pulando", doc.Id); continue; } // Migrar documento await qdrantService.AddDocumentAsync( title: doc.Titulo, content: doc.Conteudo, projectId: doc.ProjetoId, embedding: doc.Embedding, metadata: new Dictionary { ["project_name"] = doc.ProjetoNome ?? "", ["document_type"] = doc.TipoDocumento ?? "", ["category"] = doc.Categoria ?? "", ["tags"] = doc.Tags ?? Array.Empty(), ["migrated_from"] = "MongoDB", ["migrated_at"] = DateTime.UtcNow.ToString("O") } ); migrated++; if (migrated % 10 == 0) { _logger.LogInformation("Migrados {Migrated}/{Total} documentos", migrated, documents.Count); } } catch (Exception ex) { var error = $"Erro ao migrar documento {doc.Id}: {ex.Message}"; errors.Add(error); _logger.LogError(ex, error); } } } return new { totalDocuments = documents.Count, migrated, errors = errors.Count, errorDetails = errors.Take(10).ToList(), // Primeiros 10 erros batchSize, duration = "Completed" }; } private async Task CompareData( IVectorSearchService mongoService, IVectorSearchService qdrantService, string? projectId) { var mongoStats = await mongoService.GetStatsAsync(); var qdrantStats = await qdrantService.GetStatsAsync(); var mongoCount = await mongoService.GetDocumentCountAsync(projectId); var qdrantCount = await qdrantService.GetDocumentCountAsync(projectId); return new { projectId, comparison = new { MongoDB = new { documentCount = mongoCount, healthy = await mongoService.IsHealthyAsync(), stats = mongoStats }, Qdrant = new { documentCount = qdrantCount, healthy = await qdrantService.IsHealthyAsync(), stats = qdrantStats } }, differences = new { documentCountDiff = qdrantCount - mongoCount, inSync = mongoCount == qdrantCount } }; } private MongoVectorSearchService CreateMongoService() { return new MongoVectorSearchService(_mongoRepository, _embeddingService); } private async Task CreateQdrantService() { var qdrantSettings = Microsoft.Extensions.Options.Options.Create(_settings); var logger = HttpContext.RequestServices.GetService>()!; return new QdrantVectorSearchService(qdrantSettings, logger); } private async Task> GetProvidersStats() { var stats = new Dictionary(); try { var currentService = _factory.CreateVectorSearchService(); stats["current"] = await currentService.GetStatsAsync(); } catch (Exception ex) { stats["current"] = new { error = ex.Message }; } return stats; } } } #pragma warning restore SKEXP0001