// internal/services/match_service.go
package services

import (
	"encoding/json"
	"lost-and-found/internal/models"
	"lost-and-found/internal/repositories"
	"lost-and-found/internal/utils"

	"gorm.io/gorm"
)

type MatchService struct {
	db               *gorm.DB // Tambahkan ini
	matchRepo        *repositories.MatchResultRepository
	itemRepo         *repositories.ItemRepository
	lostItemRepo     *repositories.LostItemRepository
	notificationRepo *repositories.NotificationRepository
}

func NewMatchService(db *gorm.DB) *MatchService {
	return &MatchService{
		db:               db, // Tambahkan ini
		matchRepo:        repositories.NewMatchResultRepository(db),
		itemRepo:         repositories.NewItemRepository(db),
		lostItemRepo:     repositories.NewLostItemRepository(db),
		notificationRepo: repositories.NewNotificationRepository(db),
	}
}

// MatchedField represents a matched field between items
type MatchedField struct {
	Field      string  `json:"field"`
	LostValue  string  `json:"lost_value"`
	FoundValue string  `json:"found_value"`
	Score      float64 `json:"score"`
}

// FindSimilarItems finds similar items for a lost item report
func (s *MatchService) FindSimilarItems(lostItemID uint) ([]models.MatchResultResponse, error) {
	lostItem, err := s.lostItemRepo.FindByID(lostItemID)
	if err != nil {
		return nil, err
	}

	// Search for items in same category
	items, err := s.itemRepo.SearchForMatching(lostItem.CategoryID, lostItem.Name, lostItem.Color)
	if err != nil {
		return nil, err
	}

	var results []models.MatchResultResponse
	for _, item := range items {
		// Calculate similarity
		score, matchedFields := s.calculateSimilarity(lostItem, &item)
		
		// Only include if score is reasonable (>= 30%)
		if score >= 30.0 {
			// Check if match already exists
			exists, _ := s.matchRepo.CheckExistingMatch(lostItemID, item.ID)
			if !exists {
				// Create match result
				matchedFieldsJSON, _ := json.Marshal(matchedFields)
				match := &models.MatchResult{
					LostItemID:      lostItemID,
					ItemID:          item.ID,
					SimilarityScore: score,
					MatchedFields:   string(matchedFieldsJSON),
					IsNotified:      false,
				}
				s.matchRepo.Create(match)
				
				// Reload with relations
				match, _ = s.matchRepo.FindByID(match.ID)
				results = append(results, match.ToResponse())
			}
		}
	}

	return results, nil
}

// GetMatchesForLostItem gets all matches for a lost item
func (s *MatchService) GetMatchesForLostItem(lostItemID uint) ([]models.MatchResultResponse, error) {
	matches, err := s.matchRepo.FindByLostItem(lostItemID)
	if err != nil {
		return nil, err
	}

	var responses []models.MatchResultResponse
	for _, match := range matches {
		responses = append(responses, match.ToResponse())
	}

	return responses, nil
}

// GetMatchesForItem gets all matches for an item
func (s *MatchService) GetMatchesForItem(itemID uint) ([]models.MatchResultResponse, error) {
	matches, err := s.matchRepo.FindByItem(itemID)
	if err != nil {
		return nil, err
	}

	var responses []models.MatchResultResponse
	for _, match := range matches {
		responses = append(responses, match.ToResponse())
	}

	return responses, nil
}

// AutoMatchNewItem automatically matches a new item with lost items
func (s *MatchService) AutoMatchNewItem(itemID uint) error {
	item, err := s.itemRepo.FindByID(itemID)
	if err != nil {
		return err
	}

	// Find active lost items in same category
	lostItems, err := s.lostItemRepo.FindActiveForMatching(item.CategoryID)
	if err != nil {
		return err
	}

	for _, lostItem := range lostItems {
		// Calculate similarity
		score, matchedFields := s.calculateSimilarity(&lostItem, item)
		
		// Create match if score is high enough (>= 50% for auto-match)
		if score >= 50.0 {
			// Check if match already exists
			exists, _ := s.matchRepo.CheckExistingMatch(lostItem.ID, itemID)
			if !exists {
				matchedFieldsJSON, _ := json.Marshal(matchedFields)
				match := &models.MatchResult{
					LostItemID:      lostItem.ID,
					ItemID:          itemID,
					SimilarityScore: score,
					MatchedFields:   string(matchedFieldsJSON),
					IsNotified:      false,
				}
				s.matchRepo.Create(match)
				
				// Send notification to lost item owner - PERBAIKAN DI SINI
				models.CreateMatchNotification(s.db, lostItem.UserID, item.Name, match.ID)
				s.matchRepo.MarkAsNotified(match.ID)
			}
		}
	}

	return nil
}

// calculateSimilarity calculates similarity between lost item and found item
func (s *MatchService) calculateSimilarity(lostItem *models.LostItem, item *models.Item) (float64, []MatchedField) {
	var matchedFields []MatchedField

	// 1. Hard Filter: Kategori HARUS sama.
	if lostItem.CategoryID != item.CategoryID {
		return 0.0, matchedFields
	}

	// --- A. Hitung Kecocokan Nama (50%) ---
	nameSim := utils.CalculateStringSimilarity(lostItem.Name, item.Name)
	nameScore := nameSim * 100.0

	if nameScore > 40.0 {
		matchedFields = append(matchedFields, MatchedField{
			Field:      "name",
			LostValue:  lostItem.Name,
			FoundValue: item.Name,
			Score:      nameScore,
		})
	}

	// --- B. Hitung Kecocokan Secret Details / Deskripsi (50%) ---
	// Target: Utamakan Secret Details penemu, fallback ke Description
	targetText := item.SecretDetails
	foundValueType := "Secret Details"
	
	if targetText == "" {
		targetText = item.Description
		foundValueType = "Description"
	}

	// Bandingkan Deskripsi User vs Target Penemu
	descSim := utils.CalculateStringSimilarity(lostItem.Description, targetText)
	descScore := descSim * 100.0

	if descScore > 40.0 {
		matchedFields = append(matchedFields, MatchedField{
			Field:      "secret_details",
			LostValue:  "User Description",
			FoundValue: foundValueType,
			Score:      descScore,
		})
	}

	// --- C. Hitung Skor Akhir ---
	// Rumus: (Skor Nama * 0.5) + (Skor Deskripsi * 0.5)
	finalScore := (nameScore * 0.5) + (descScore * 0.5)

	return finalScore, matchedFields
}