package knowledge

import (
	"database/sql"
	"fmt"
	"strings"

	"gorm.io/gorm"

	"customer-support-system/internal/database"
	"customer-support-system/internal/models"
	"customer-support-system/pkg/logger"
)

// KnowledgeService handles knowledge base operations
type KnowledgeService struct {
	db *gorm.DB
}

// NewKnowledgeService creates a new knowledge service
func NewKnowledgeService() *KnowledgeService {
	return &KnowledgeService{
		db: database.GetDB(),
	}
}

// CreateKnowledgeEntry creates a new knowledge base entry
func (s *KnowledgeService) CreateKnowledgeEntry(userID uint, req *models.CreateKnowledgeBaseRequest) (*models.KnowledgeBase, error) {
	knowledge := models.KnowledgeBase{
		Question:   req.Question,
		Answer:     req.Answer,
		Category:   req.Category,
		Tags:       req.Tags,
		Priority:   req.Priority,
		ViewCount:  0,
		Helpful:    0,
		NotHelpful: 0,
		Active:     true,
		CreatedBy:  userID,
		UpdatedBy:  userID,
	}

	if err := s.db.Create(&knowledge).Error; err != nil {
		logger.WithError(err).WithField("user_id", userID).Error("Failed to create knowledge entry")
		return nil, fmt.Errorf("failed to create knowledge entry: %w", err)
	}

	logger.WithField("knowledge_id", knowledge.ID).Info("Knowledge entry created successfully")

	return &knowledge, nil
}

// GetKnowledgeEntry retrieves a knowledge base entry by ID
func (s *KnowledgeService) GetKnowledgeEntry(knowledgeID uint) (*models.KnowledgeBase, error) {
	var knowledge models.KnowledgeBase
	if err := s.db.First(&knowledge, knowledgeID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("knowledge entry not found")
		}
		logger.WithError(err).WithField("knowledge_id", knowledgeID).Error("Failed to get knowledge entry")
		return nil, fmt.Errorf("failed to get knowledge entry: %w", err)
	}

	// Increment view count
	s.db.Model(&knowledge).Update("view_count", knowledge.ViewCount+1)

	return &knowledge, nil
}

// ListKnowledgeEntries retrieves a list of knowledge base entries
func (s *KnowledgeService) ListKnowledgeEntries(page, pageSize int, category string, active *bool) (*models.KnowledgeBaseListResponse, error) {
	var knowledge []models.KnowledgeBase
	var total int64

	query := s.db.Model(&models.KnowledgeBase{})

	if category != "" {
		query = query.Where("category = ?", category)
	}

	if active != nil {
		query = query.Where("active = ?", *active)
	}

	// Get total count
	if err := query.Count(&total).Error; err != nil {
		logger.WithError(err).Error("Failed to count knowledge entries")
		return nil, fmt.Errorf("failed to count knowledge entries: %w", err)
	}

	// Get paginated results
	offset := (page - 1) * pageSize
	if err := query.Order("priority DESC, view_count DESC, created_at DESC").Offset(offset).Limit(pageSize).Find(&knowledge).Error; err != nil {
		logger.WithError(err).Error("Failed to list knowledge entries")
		return nil, fmt.Errorf("failed to list knowledge entries: %w", err)
	}

	return &models.KnowledgeBaseListResponse{
		Entries:  knowledge,
		Total:    total,
		Page:     page,
		PageSize: pageSize,
	}, nil
}

// UpdateKnowledgeEntry updates a knowledge base entry
func (s *KnowledgeService) UpdateKnowledgeEntry(knowledgeID uint, userID uint, req *models.UpdateKnowledgeBaseRequest) (*models.KnowledgeBase, error) {
	var knowledge models.KnowledgeBase
	if err := s.db.First(&knowledge, knowledgeID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("knowledge entry not found")
		}
		logger.WithError(err).WithField("knowledge_id", knowledgeID).Error("Failed to get knowledge entry")
		return nil, fmt.Errorf("failed to get knowledge entry: %w", err)
	}

	// Check if user is the creator or an admin
	if knowledge.CreatedBy != userID {
		// Check if user is admin
		var user models.User
		if err := s.db.First(&user, userID).Error; err != nil {
			logger.WithError(err).WithField("user_id", userID).Error("Failed to get user")
			return nil, fmt.Errorf("failed to get user: %w", err)
		}
		if user.Role != "admin" {
			return nil, fmt.Errorf("unauthorized to update this knowledge entry")
		}
	}

	// Update fields
	if req.Question != "" {
		knowledge.Question = req.Question
	}
	if req.Answer != "" {
		knowledge.Answer = req.Answer
	}
	if req.Category != "" {
		knowledge.Category = req.Category
	}
	if req.Tags != "" {
		knowledge.Tags = req.Tags
	}
	if req.Priority != 0 {
		knowledge.Priority = req.Priority
	}
	if req.Active != nil {
		knowledge.Active = *req.Active
	}
	knowledge.UpdatedBy = userID

	if err := s.db.Save(&knowledge).Error; err != nil {
		logger.WithError(err).WithField("knowledge_id", knowledgeID).Error("Failed to update knowledge entry")
		return nil, fmt.Errorf("failed to update knowledge entry: %w", err)
	}

	logger.WithField("knowledge_id", knowledgeID).Info("Knowledge entry updated successfully")

	return &knowledge, nil
}

// DeleteKnowledgeEntry deletes a knowledge base entry
func (s *KnowledgeService) DeleteKnowledgeEntry(knowledgeID uint, userID uint) error {
	var knowledge models.KnowledgeBase
	if err := s.db.First(&knowledge, knowledgeID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return fmt.Errorf("knowledge entry not found")
		}
		logger.WithError(err).WithField("knowledge_id", knowledgeID).Error("Failed to get knowledge entry")
		return fmt.Errorf("failed to get knowledge entry: %w", err)
	}

	// Check if user is the creator or an admin
	if knowledge.CreatedBy != userID {
		// Check if user is admin
		var user models.User
		if err := s.db.First(&user, userID).Error; err != nil {
			logger.WithError(err).WithField("user_id", userID).Error("Failed to get user")
			return fmt.Errorf("failed to get user: %w", err)
		}
		if user.Role != "admin" {
			return fmt.Errorf("unauthorized to delete this knowledge entry")
		}
	}

	if err := s.db.Delete(&knowledge).Error; err != nil {
		logger.WithError(err).WithField("knowledge_id", knowledgeID).Error("Failed to delete knowledge entry")
		return fmt.Errorf("failed to delete knowledge entry: %w", err)
	}

	logger.WithField("knowledge_id", knowledgeID).Info("Knowledge entry deleted successfully")

	return nil
}

// SearchKnowledge searches for knowledge base entries
func (s *KnowledgeService) SearchKnowledge(query string, page, pageSize int, category string) (*models.KnowledgeBaseSearchResponse, error) {
	var knowledge []models.KnowledgeBase
	var total int64

	// Build search query
	searchQuery := s.db.Model(&models.KnowledgeBase{})

	if query != "" {
		// Search in question, answer, and tags
		searchQuery = searchQuery.Where(
			"active = ? AND (question ILIKE ? OR answer ILIKE ? OR tags ILIKE ?)",
			true,
			"%"+query+"%",
			"%"+query+"%",
			"%"+query+"%",
		)
	} else {
		searchQuery = searchQuery.Where("active = ?", true)
	}

	if category != "" {
		searchQuery = searchQuery.Where("category = ?", category)
	}

	// Get total count
	if err := searchQuery.Count(&total).Error; err != nil {
		logger.WithError(err).WithField("query", query).Error("Failed to count knowledge entries")
		return nil, fmt.Errorf("failed to count knowledge entries: %w", err)
	}

	// Get paginated results
	offset := (page - 1) * pageSize
	if err := searchQuery.Order("priority DESC, view_count DESC, created_at DESC").Offset(offset).Limit(pageSize).Find(&knowledge).Error; err != nil {
		logger.WithError(err).WithField("query", query).Error("Failed to search knowledge entries")
		return nil, fmt.Errorf("failed to search knowledge entries: %w", err)
	}

	// Convert to search results with relevance scores
	results := make([]models.KnowledgeBaseSearchResult, len(knowledge))
	for i, entry := range knowledge {
		results[i] = models.KnowledgeBaseSearchResult{
			KnowledgeBase:  entry,
			RelevanceScore: s.calculateRelevanceScore(query, entry),
			MatchedFields:  s.getMatchedFields(query, entry),
		}
	}

	return &models.KnowledgeBaseSearchResponse{
		Results:  results,
		Total:    total,
		Query:    query,
		Page:     page,
		PageSize: pageSize,
	}, nil
}

// GetCategories retrieves all unique categories in the knowledge base
func (s *KnowledgeService) GetCategories() ([]string, error) {
	var categories []string
	if err := s.db.Model(&models.KnowledgeBase{}).Where("active = ?", true).Distinct().Pluck("category", &categories).Error; err != nil {
		logger.WithError(err).Error("Failed to get knowledge categories")
		return nil, fmt.Errorf("failed to get knowledge categories: %w", err)
	}
	return categories, nil
}

// GetTags retrieves all unique tags in the knowledge base
func (s *KnowledgeService) GetTags() ([]string, error) {
	var tags []string
	if err := s.db.Model(&models.KnowledgeBase{}).Where("active = ?", true).Find(&[]models.KnowledgeBase{}).Error; err != nil {
		logger.WithError(err).Error("Failed to get knowledge entries for tags")
		return nil, fmt.Errorf("failed to get knowledge entries for tags: %w", err)
	}

	// Extract and deduplicate tags
	tagMap := make(map[string]bool)
	for _, entry := range []models.KnowledgeBase{} {
		if entry.Tags != "" {
			entryTags := strings.Split(entry.Tags, ",")
			for _, tag := range entryTags {
				tag = strings.TrimSpace(tag)
				if tag != "" {
					tagMap[tag] = true
				}
			}
		}
	}

	// Convert map to slice
	tags = make([]string, 0, len(tagMap))
	for tag := range tagMap {
		tags = append(tags, tag)
	}

	return tags, nil
}

// RateKnowledgeEntry rates a knowledge base entry as helpful or not
func (s *KnowledgeService) RateKnowledgeEntry(knowledgeID uint, userID uint, helpful bool, comment string) error {
	var knowledge models.KnowledgeBase
	if err := s.db.First(&knowledge, knowledgeID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return fmt.Errorf("knowledge entry not found")
		}
		logger.WithError(err).WithField("knowledge_id", knowledgeID).Error("Failed to get knowledge entry")
		return fmt.Errorf("failed to get knowledge entry: %w", err)
	}

	// Create feedback record
	feedback := models.KnowledgeBaseFeedback{
		KnowledgeBaseID: knowledgeID,
		UserID:          userID,
		Helpful:         helpful,
		Comment:         comment,
	}

	if err := s.db.Create(&feedback).Error; err != nil {
		logger.WithError(err).WithField("knowledge_id", knowledgeID).Error("Failed to create knowledge feedback")
		return fmt.Errorf("failed to create knowledge feedback: %w", err)
	}

	// Update helpful/not helpful counts
	if helpful {
		knowledge.Helpful++
	} else {
		knowledge.NotHelpful++
	}

	if err := s.db.Save(&knowledge).Error; err != nil {
		logger.WithError(err).WithField("knowledge_id", knowledgeID).Error("Failed to rate knowledge entry")
		return fmt.Errorf("failed to rate knowledge entry: %w", err)
	}

	logger.WithFields(map[string]interface{}{
		"knowledge_id": knowledgeID,
		"user_id":      userID,
		"helpful":      helpful,
	}).Info("Knowledge entry rated successfully")

	return nil
}

// GetPopularKnowledge retrieves popular knowledge base entries
func (s *KnowledgeService) GetPopularKnowledge(limit int) ([]models.KnowledgeBase, error) {
	var knowledge []models.KnowledgeBase
	if err := s.db.Where("active = ?", true).
		Order("view_count DESC, helpful DESC").
		Limit(limit).
		Find(&knowledge).Error; err != nil {
		logger.WithError(err).Error("Failed to get popular knowledge entries")
		return nil, fmt.Errorf("failed to get popular knowledge entries: %w", err)
	}
	return knowledge, nil
}

// GetRecentKnowledge retrieves recent knowledge base entries
func (s *KnowledgeService) GetRecentKnowledge(limit int) ([]models.KnowledgeBase, error) {
	var knowledge []models.KnowledgeBase
	if err := s.db.Where("active = ?", true).
		Order("created_at DESC").
		Limit(limit).
		Find(&knowledge).Error; err != nil {
		logger.WithError(err).Error("Failed to get recent knowledge entries")
		return nil, fmt.Errorf("failed to get recent knowledge entries: %w", err)
	}
	return knowledge, nil
}

// FindBestMatch finds the best matching knowledge base entry for a query
func (s *KnowledgeService) FindBestMatch(query string) (*models.KnowledgeBase, error) {
	// This is a simple implementation that looks for exact matches in tags
	// In a real implementation, this would use more sophisticated algorithms like TF-IDF or embeddings

	var knowledge models.KnowledgeBase
	if err := s.db.Where("active = ? AND tags ILIKE ?", true, "%"+query+"%").
		Order("priority DESC, view_count DESC, helpful DESC").
		First(&knowledge).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			// Try to find a match in the question
			if err := s.db.Where("active = ? AND question ILIKE ?", true, "%"+query+"%").
				Order("priority DESC, view_count DESC, helpful DESC").
				First(&knowledge).Error; err != nil {
				if err == gorm.ErrRecordNotFound {
					// Try to find a match in the answer
					if err := s.db.Where("active = ? AND answer ILIKE ?", true, "%"+query+"%").
						Order("priority DESC, view_count DESC, helpful DESC").
						First(&knowledge).Error; err != nil {
						if err == gorm.ErrRecordNotFound {
							return nil, fmt.Errorf("no matching knowledge entry found")
						}
						logger.WithError(err).WithField("query", query).Error("Failed to find best match in answer")
						return nil, fmt.Errorf("failed to find best match in answer: %w", err)
					}
				}
				logger.WithError(err).WithField("query", query).Error("Failed to find best match in question")
				return nil, fmt.Errorf("failed to find best match in question: %w", err)
			}
		}
		logger.WithError(err).WithField("query", query).Error("Failed to find best match in tags")
		return nil, fmt.Errorf("failed to find best match in tags: %w", err)
	}

	return &knowledge, nil
}

// GetKnowledgeStats retrieves statistics for the knowledge base
func (s *KnowledgeService) GetKnowledgeStats() (*models.KnowledgeBaseStats, error) {
	var stats models.KnowledgeBaseStats

	// Get total entries
	if err := s.db.Model(&models.KnowledgeBase{}).Where("active = ?", true).Count(&stats.TotalEntries).Error; err != nil {
		logger.WithError(err).Error("Failed to count knowledge entries")
		return nil, fmt.Errorf("failed to count knowledge entries: %w", err)
	}

	// Get total views
	var totalViews sql.NullInt64
	if err := s.db.Model(&models.KnowledgeBase{}).Where("active = ?", true).Select("SUM(view_count)").Scan(&totalViews).Error; err != nil {
		logger.WithError(err).Error("Failed to calculate total views")
		return nil, fmt.Errorf("failed to calculate total views: %w", err)
	}
	if totalViews.Valid {
		stats.TotalViews = totalViews.Int64
	}

	// Get average helpful percentage
	var avgHelpful sql.NullFloat64
	if err := s.db.Model(&models.KnowledgeBase{}).Where("active = ? AND helpful + not_helpful > 0", true).
		Select("AVG(helpful::float / (helpful + not_helpful)::float * 100)").Scan(&avgHelpful).Error; err != nil {
		logger.WithError(err).Error("Failed to calculate average helpful percentage")
		return nil, fmt.Errorf("failed to calculate average helpful percentage: %w", err)
	}
	if avgHelpful.Valid {
		stats.AverageHelpful = avgHelpful.Float64
	}

	// Get top categories
	var categoryStats []models.CategoryStat
	if err := s.db.Model(&models.KnowledgeBase{}).
		Where("active = ?", true).
		Select("category, COUNT(*) as count").
		Group("category").
		Order("count DESC").
		Limit(5).
		Scan(&categoryStats).Error; err != nil {
		logger.WithError(err).Error("Failed to get category statistics")
		return nil, fmt.Errorf("failed to get category statistics: %w", err)
	}
	stats.TopCategories = categoryStats

	// Get top tags
	var tagStats []models.TagStat
	if err := s.db.Model(&models.KnowledgeBase{}).
		Where("active = ? AND tags != ''", true).
		Select("unnest(string_to_array(tags, ',')) as tag, COUNT(*) as count").
		Group("tag").
		Order("count DESC").
		Limit(10).
		Scan(&tagStats).Error; err != nil {
		logger.WithError(err).Error("Failed to get tag statistics")
		return nil, fmt.Errorf("failed to get tag statistics: %w", err)
	}
	stats.TopTags = tagStats

	return &stats, nil
}

// calculateRelevanceScore calculates a relevance score for a knowledge base entry against a query
func (s *KnowledgeService) calculateRelevanceScore(query string, entry models.KnowledgeBase) float64 {
	if query == "" {
		return 0
	}

	query = strings.ToLower(query)
	score := 0.0

	// Check for exact match in question
	if strings.Contains(strings.ToLower(entry.Question), query) {
		score += 10.0
	}

	// Check for exact match in answer
	if strings.Contains(strings.ToLower(entry.Answer), query) {
		score += 5.0
	}

	// Check for exact match in tags
	if strings.Contains(strings.ToLower(entry.Tags), query) {
		score += 7.0
	}

	// Check for word matches
	queryWords := strings.Fields(query)
	questionWords := strings.Fields(strings.ToLower(entry.Question))
	answerWords := strings.Fields(strings.ToLower(entry.Answer))
	tagsWords := strings.Fields(strings.ToLower(entry.Tags))

	for _, word := range queryWords {
		// Check question words
		for _, qWord := range questionWords {
			if strings.EqualFold(word, qWord) {
				score += 2.0
			}
		}

		// Check answer words
		for _, aWord := range answerWords {
			if strings.EqualFold(word, aWord) {
				score += 1.0
			}
		}

		// Check tag words
		for _, tWord := range tagsWords {
			if strings.EqualFold(word, tWord) {
				score += 3.0
			}
		}
	}

	// Boost score based on priority
	score += float64(entry.Priority) * 0.5

	// Boost score based on helpfulness
	if entry.Helpful+entry.NotHelpful > 0 {
		helpfulness := float64(entry.Helpful) / float64(entry.Helpful+entry.NotHelpful)
		score += helpfulness * 2.0
	}

	return score
}

// getMatchedFields returns the fields that matched the query
func (s *KnowledgeService) getMatchedFields(query string, entry models.KnowledgeBase) []string {
	if query == "" {
		return []string{}
	}

	query = strings.ToLower(query)
	matchedFields := []string{}

	// Check question
	if strings.Contains(strings.ToLower(entry.Question), query) {
		matchedFields = append(matchedFields, "question")
	}

	// Check answer
	if strings.Contains(strings.ToLower(entry.Answer), query) {
		matchedFields = append(matchedFields, "answer")
	}

	// Check tags
	if strings.Contains(strings.ToLower(entry.Tags), query) {
		matchedFields = append(matchedFields, "tags")
	}

	return matchedFields
}
