package conversation import ( "context" "database/sql" "fmt" "time" "gorm.io/gorm" "customer-support-system/internal/ai" "customer-support-system/internal/database" "customer-support-system/internal/models" "customer-support-system/pkg/logger" ) // ConversationService handles conversation operations type ConversationService struct { db *gorm.DB aiService *ai.AIService } // NewConversationService creates a new conversation service func NewConversationService() *ConversationService { return &ConversationService{ db: database.GetDB(), aiService: ai.NewAIService(), } } // CreateConversation creates a new conversation func (s *ConversationService) CreateConversation(userID uint, req *models.CreateConversationRequest) (*models.Conversation, error) { conversation := models.Conversation{ Title: req.Title, UserID: userID, Department: req.Department, Priority: req.Priority, Tags: req.Tags, Status: "active", } if err := s.db.Create(&conversation).Error; err != nil { logger.WithError(err).WithField("user_id", userID).Error("Failed to create conversation") return nil, fmt.Errorf("failed to create conversation: %w", err) } logger.WithField("conversation_id", conversation.ID).Info("Conversation created successfully") return &conversation, nil } // GetConversation retrieves a conversation by ID func (s *ConversationService) GetConversation(conversationID uint, userID uint) (*models.Conversation, error) { var conversation models.Conversation if err := s.db.Where("id = ? AND user_id = ?", conversationID, userID).First(&conversation).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("conversation not found") } logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get conversation") return nil, fmt.Errorf("failed to get conversation: %w", err) } return &conversation, nil } // ListConversations retrieves a list of conversations for a user func (s *ConversationService) ListConversations(userID uint, page, pageSize int, status string) (*models.ConversationListResponse, error) { var conversations []models.Conversation var total int64 query := s.db.Model(&models.Conversation{}).Where("user_id = ?", userID) if status != "" { query = query.Where("status = ?", status) } // Get total count if err := query.Count(&total).Error; err != nil { logger.WithError(err).WithField("user_id", userID).Error("Failed to count conversations") return nil, fmt.Errorf("failed to count conversations: %w", err) } // Get paginated results offset := (page - 1) * pageSize if err := query.Order("last_message_at DESC").Offset(offset).Limit(pageSize).Find(&conversations).Error; err != nil { logger.WithError(err).WithField("user_id", userID).Error("Failed to list conversations") return nil, fmt.Errorf("failed to list conversations: %w", err) } return &models.ConversationListResponse{ Conversations: conversations, Total: total, Page: page, PageSize: pageSize, }, nil } // UpdateConversation updates a conversation func (s *ConversationService) UpdateConversation(conversationID uint, userID uint, req *models.UpdateConversationRequest) (*models.Conversation, error) { var conversation models.Conversation if err := s.db.Where("id = ? AND user_id = ?", conversationID, userID).First(&conversation).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("conversation not found") } logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get conversation") return nil, fmt.Errorf("failed to get conversation: %w", err) } // Update fields if req.Title != "" { conversation.Title = req.Title } if req.Status != "" { conversation.Status = req.Status } if req.Department != "" { conversation.Department = req.Department } if req.Priority != "" { conversation.Priority = req.Priority } if req.Tags != "" { conversation.Tags = req.Tags } if req.AgentID != nil { conversation.AgentID = req.AgentID } if err := s.db.Save(&conversation).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to update conversation") return nil, fmt.Errorf("failed to update conversation: %w", err) } logger.WithField("conversation_id", conversationID).Info("Conversation updated successfully") return &conversation, nil } // DeleteConversation deletes a conversation func (s *ConversationService) DeleteConversation(conversationID uint, userID uint) error { var conversation models.Conversation if err := s.db.Where("id = ? AND user_id = ?", conversationID, userID).First(&conversation).Error; err != nil { if err == gorm.ErrRecordNotFound { return fmt.Errorf("conversation not found") } logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get conversation") return fmt.Errorf("failed to get conversation: %w", err) } if err := s.db.Delete(&conversation).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to delete conversation") return fmt.Errorf("failed to delete conversation: %w", err) } logger.WithField("conversation_id", conversationID).Info("Conversation deleted successfully") return nil } // CreateMessage creates a new message in a conversation func (s *ConversationService) CreateMessage(conversationID uint, userID uint, req *models.CreateMessageRequest) (*models.Message, error) { // Check if conversation exists and belongs to user var conversation models.Conversation if err := s.db.Where("id = ? AND user_id = ?", conversationID, userID).First(&conversation).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("conversation not found") } logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get conversation") return nil, fmt.Errorf("failed to get conversation: %w", err) } // Create message message := models.Message{ ConversationID: conversationID, UserID: userID, Content: req.Content, Type: req.Type, Status: "sent", IsAI: false, } if err := s.db.Create(&message).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to create message") return nil, fmt.Errorf("failed to create message: %w", err) } logger.WithField("message_id", message.ID).Info("Message created successfully") return &message, nil } // GetMessages retrieves messages in a conversation func (s *ConversationService) GetMessages(conversationID uint, userID uint, page, pageSize int) (*models.MessageListResponse, error) { // Check if conversation exists and belongs to user var conversation models.Conversation if err := s.db.Where("id = ? AND user_id = ?", conversationID, userID).First(&conversation).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("conversation not found") } logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get conversation") return nil, fmt.Errorf("failed to get conversation: %w", err) } var messages []models.Message var total int64 // Get total count if err := s.db.Model(&models.Message{}).Where("conversation_id = ?", conversationID).Count(&total).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to count messages") return nil, fmt.Errorf("failed to count messages: %w", err) } // Get paginated results offset := (page - 1) * pageSize if err := s.db.Where("conversation_id = ?", conversationID).Order("created_at ASC").Offset(offset).Limit(pageSize).Find(&messages).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get messages") return nil, fmt.Errorf("failed to get messages: %w", err) } return &models.MessageListResponse{ Messages: messages, Total: total, Page: page, PageSize: pageSize, }, nil } // UpdateMessage updates a message func (s *ConversationService) UpdateMessage(messageID uint, userID uint, req *models.UpdateMessageRequest) (*models.Message, error) { var message models.Message if err := s.db.Where("id = ? AND user_id = ?", messageID, userID).First(&message).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("message not found") } logger.WithError(err).WithField("message_id", messageID).Error("Failed to get message") return nil, fmt.Errorf("failed to get message: %w", err) } // Update fields if req.Content != "" { message.Content = req.Content } if req.Status != "" { message.Status = req.Status } if err := s.db.Save(&message).Error; err != nil { logger.WithError(err).WithField("message_id", messageID).Error("Failed to update message") return nil, fmt.Errorf("failed to update message: %w", err) } logger.WithField("message_id", messageID).Info("Message updated successfully") return &message, nil } // DeleteMessage deletes a message func (s *ConversationService) DeleteMessage(messageID uint, userID uint) error { var message models.Message if err := s.db.Where("id = ? AND user_id = ?", messageID, userID).First(&message).Error; err != nil { if err == gorm.ErrRecordNotFound { return fmt.Errorf("message not found") } logger.WithError(err).WithField("message_id", messageID).Error("Failed to get message") return fmt.Errorf("failed to get message: %w", err) } if err := s.db.Delete(&message).Error; err != nil { logger.WithError(err).WithField("message_id", messageID).Error("Failed to delete message") return fmt.Errorf("failed to delete message: %w", err) } logger.WithField("message_id", messageID).Info("Message deleted successfully") return nil } // SendMessageWithAI sends a message and gets an AI response func (s *ConversationService) SendMessageWithAI(conversationID uint, userID uint, content string) (*models.Message, *models.Message, error) { // Check if conversation exists and belongs to user var conversation models.Conversation if err := s.db.Where("id = ? AND user_id = ?", conversationID, userID).First(&conversation).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, nil, fmt.Errorf("conversation not found") } logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get conversation") return nil, nil, fmt.Errorf("failed to get conversation: %w", err) } // Create user message userMessage := models.Message{ ConversationID: conversationID, UserID: userID, Content: content, Type: "text", Status: "sent", IsAI: false, } if err := s.db.Create(&userMessage).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to create user message") return nil, nil, fmt.Errorf("failed to create user message: %w", err) } // Get conversation history var messages []models.Message if err := s.db.Where("conversation_id = ?", conversationID).Order("created_at ASC").Find(&messages).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get conversation history") return nil, nil, fmt.Errorf("failed to get conversation history: %w", err) } // Analyze complexity of the message complexity := s.aiService.AnalyzeComplexity(content) // Get AI response ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() aiResponse, err := s.aiService.Query(ctx, content, messages, complexity) if err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get AI response") return &userMessage, nil, fmt.Errorf("failed to get AI response: %w", err) } // Create AI message aiMessage := models.Message{ ConversationID: conversationID, UserID: userID, // AI responses are associated with the user who asked the question Content: aiResponse, Type: "text", Status: "sent", IsAI: true, AIModel: "gpt-4", // This should be determined based on which AI model was used } if err := s.db.Create(&aiMessage).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to create AI message") return &userMessage, nil, fmt.Errorf("failed to create AI message: %w", err) } logger.WithFields(map[string]interface{}{ "conversation_id": conversationID, "user_message_id": userMessage.ID, "ai_message_id": aiMessage.ID, }).Info("AI response created successfully") return &userMessage, &aiMessage, nil } // GetConversationStats retrieves statistics for a conversation func (s *ConversationService) GetConversationStats(conversationID uint, userID uint) (*models.ConversationStats, error) { // Check if conversation exists and belongs to user var conversation models.Conversation if err := s.db.Where("id = ? AND user_id = ?", conversationID, userID).First(&conversation).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("conversation not found") } logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get conversation") return nil, fmt.Errorf("failed to get conversation: %w", err) } var stats models.ConversationStats // Get total messages count if err := s.db.Model(&models.Message{}).Where("conversation_id = ?", conversationID).Count(&stats.TotalMessages).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to count messages") return nil, fmt.Errorf("failed to count messages: %w", err) } // Get average sentiment var avgSentiment sql.NullFloat64 if err := s.db.Model(&models.Message{}).Where("conversation_id = ?", conversationID).Select("AVG(sentiment)").Scan(&avgSentiment).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to calculate average sentiment") return nil, fmt.Errorf("failed to calculate average sentiment: %w", err) } if avgSentiment.Valid { stats.AverageSentiment = avgSentiment.Float64 } // Get first and last message timestamps var firstMessage, lastMessage models.Message if err := s.db.Where("conversation_id = ?", conversationID).Order("created_at ASC").First(&firstMessage).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get first message") return nil, fmt.Errorf("failed to get first message: %w", err) } stats.FirstMessageAt = firstMessage.CreatedAt if err := s.db.Where("conversation_id = ?", conversationID).Order("created_at DESC").First(&lastMessage).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get last message") return nil, fmt.Errorf("failed to get last message: %w", err) } stats.LastMessageAt = lastMessage.CreatedAt // Calculate average response time (time between user messages and AI responses) // This is a simplified calculation and could be improved if stats.TotalMessages > 1 { var responseTimes []int64 var prevMessage models.Message isUserMessage := false var allMessages []models.Message if err := s.db.Where("conversation_id = ?", conversationID).Order("created_at ASC").Find(&allMessages).Error; err != nil { logger.WithError(err).WithField("conversation_id", conversationID).Error("Failed to get messages for response time calculation") return nil, fmt.Errorf("failed to get messages for response time calculation: %w", err) } for _, msg := range allMessages { if !msg.IsAI { if isUserMessage { // Consecutive user messages, skip prevMessage = msg continue } isUserMessage = true prevMessage = msg } else { if isUserMessage { // AI response to user message, calculate response time responseTime := msg.CreatedAt.Sub(prevMessage.CreatedAt).Seconds() responseTimes = append(responseTimes, int64(responseTime)) } isUserMessage = false } } if len(responseTimes) > 0 { var totalResponseTime int64 = 0 for _, rt := range responseTimes { totalResponseTime += rt } stats.ResponseTime = totalResponseTime / int64(len(responseTimes)) } } return &stats, nil }