package automation import ( "context" "fmt" applogger "github.com/gochat/gochat/pkg/logger" ) // BotTriggerConfigService provides CRUD for bot trigger condition configurations. // Reference: Chatwoot automation_rule trigger conditions adapted for AgentBot event subscription. // TriggerConfigs define when and under what conditions a bot should be triggered on an inbox. type BotTriggerConfigService struct { db DBProvider } // NewBotTriggerConfigService creates a new BotTriggerConfigService. func NewBotTriggerConfigService(db DBProvider) *BotTriggerConfigService { return &BotTriggerConfigService{db: db} } // GetByID retrieves a bot trigger config by ID. func (s *BotTriggerConfigService) GetByID(ctx context.Context, id uint) (*BotTriggerConfig, error) { var config BotTriggerConfig if err := s.db.DB().WithContext(ctx).First(&config, id).Error; err != nil { return nil, err } return &config, nil } // ListByAccount retrieves all bot trigger configs for an account. func (s *BotTriggerConfigService) ListByAccount(ctx context.Context, accountID uint) ([]BotTriggerConfig, error) { var configs []BotTriggerConfig if err := s.db.DB().WithContext(ctx). Where("account_id = ?", accountID). Order("id ASC"). Find(&configs).Error; err != nil { return nil, err } return configs, nil } // ListByAgentBot retrieves all trigger configs for a specific AgentBot. func (s *BotTriggerConfigService) ListByAgentBot(ctx context.Context, accountID uint, agentBotID uint) ([]BotTriggerConfig, error) { var configs []BotTriggerConfig if err := s.db.DB().WithContext(ctx). Where("account_id = ? AND agent_bot_id = ?", accountID, agentBotID). Order("id ASC"). Find(&configs).Error; err != nil { return nil, err } return configs, nil } // ListActiveByAccountAndEvent retrieves active trigger configs for a given account and event. // Used by AgentBotRuleListener to determine which bots should be triggered. func (s *BotTriggerConfigService) ListActiveByAccountAndEvent(ctx context.Context, accountID uint, eventName BotRuleEventType) ([]BotTriggerConfig, error) { var configs []BotTriggerConfig if err := s.db.DB().WithContext(ctx). Where("account_id = ? AND event_name = ? AND active = ?", accountID, eventName, true). Order("id ASC"). Find(&configs).Error; err != nil { return nil, err } return configs, nil } // Create creates a new bot trigger config. func (s *BotTriggerConfigService) Create(ctx context.Context, config *BotTriggerConfig) error { // Validate conditions before saving (convert TriggerConditions → Conditions for validation) conditions := make(Conditions, len(config.Conditions)) for i, tc := range config.Conditions { conditions[i] = Condition(tc) } if err := ValidateConditions(conditions); err != nil { return fmt.Errorf("invalid conditions: %w", err) } return s.db.DB().WithContext(ctx).Select( "AccountID", "AgentBotID", "EventName", "Name", "Description", "Conditions", "QueryOperator", "Active", ).Create(config).Error } // Update updates an existing bot trigger config. func (s *BotTriggerConfigService) Update(ctx context.Context, config *BotTriggerConfig) error { conditions := make(Conditions, len(config.Conditions)) for i, tc := range config.Conditions { conditions[i] = Condition(tc) } if err := ValidateConditions(conditions); err != nil { return fmt.Errorf("invalid conditions: %w", err) } return s.db.DB().WithContext(ctx).Save(config).Error } // Delete soft-deletes a bot trigger config. func (s *BotTriggerConfigService) Delete(ctx context.Context, id uint) error { return s.db.DB().WithContext(ctx).Delete(&BotTriggerConfig{}, id).Error } // ToggleActive toggles the active state of a bot trigger config. func (s *BotTriggerConfigService) ToggleActive(ctx context.Context, id uint, active bool) error { return s.db.DB().WithContext(ctx). Model(&BotTriggerConfig{}). Where("id = ?", id). Update("active", active).Error } // MatchAndTrigger checks all active trigger configs for the given event and account, // runs condition filters, and triggers the associated AgentBot for matching configs. // Reference: mirrors AutomationRuleService.MatchAndExecute but for trigger config scope. // The actual bot action execution is delegated to BotRuleService.MatchAndExecute. func (s *BotTriggerConfigService) MatchAndTrigger(ctx context.Context, accountID uint, eventName BotRuleEventType, conversationID uint, eventData map[string]interface{}) ([]uint, error) { configs, err := s.ListActiveByAccountAndEvent(ctx, accountID, eventName) if err != nil { return nil, fmt.Errorf("failed to load trigger configs: %w", err) } if len(configs) == 0 { applogger.L().Debugf("no active trigger configs for account %d event %s", accountID, eventName) return nil, nil } // Load the conversation for condition matching conversation, err := loadConversationForFilter(ctx, s.db.DB(), conversationID) if err != nil { return nil, fmt.Errorf("failed to load conversation %d: %w", conversationID, err) } var matchedBotIDs []uint for _, config := range configs { // Convert TriggerConditions → Conditions for MatchConditions call conditions := make(Conditions, len(config.Conditions)) for i, tc := range config.Conditions { conditions[i] = Condition(tc) } matched, err := MatchConditions(ctx, s.db.DB(), conditions, conversation, eventData) if err != nil { applogger.L().Errorf("condition matching failed for trigger config %d: %v", config.ID, err) continue } if !matched { applogger.L().Debugf("trigger config %d did not match conditions for conversation %d", config.ID, conversationID) continue } applogger.L().Infof("trigger config %d matched for conversation %d, bot %d should be triggered", config.ID, conversationID, config.AgentBotID) matchedBotIDs = append(matchedBotIDs, config.AgentBotID) } return matchedBotIDs, nil }