// internal/services/claim_service.go
package services

import (
	"context"
	"errors"
	"fmt"
	"lost-and-found/internal/models"
	"lost-and-found/internal/repositories"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)


type CloseCaseRequest struct {
	BeritaAcaraNo     string `json:"berita_acara_no" binding:"required"`
	BuktiSerahTerima  string `json:"bukti_serah_terima"` // Optional file URL
	Notes             string `json:"notes"`
}

type ReopenCaseRequest struct {
	Reason string `json:"reason" binding:"required"`
}

type ClaimService struct {
	db               *gorm.DB
	claimRepo        *repositories.ClaimRepository
	itemRepo         *repositories.ItemRepository
	verificationRepo *repositories.ClaimVerificationRepository
	notificationRepo *repositories.NotificationRepository
	auditLogRepo     *repositories.AuditLogRepository
}

func NewClaimService(db *gorm.DB) *ClaimService {
	return &ClaimService{
		db:               db,
		claimRepo:        repositories.NewClaimRepository(db),
		itemRepo:         repositories.NewItemRepository(db),
		verificationRepo: repositories.NewClaimVerificationRepository(db),
		notificationRepo: repositories.NewNotificationRepository(db),
		auditLogRepo:     repositories.NewAuditLogRepository(db),
	}
}

// CreateClaimRequest represents claim creation data
type CreateClaimRequest struct {
	ItemID      uint   `json:"item_id" binding:"required"`
	UserID      *uint  `json:"user_id"` // Optional: if admin creates for specific user
	Description string `json:"description" binding:"required"`
	Contact     string `json:"contact" binding:"required"`
	ProofURL    string `json:"proof_url"`
	AdminNotes  string `json:"admin_notes"` // Optional: admin's internal notes
}

// VerifyClaimRequest represents claim verification data
type VerifyClaimRequest struct {
	Status string `json:"status" binding:"required"` // approved or rejected
	Approved *bool  `json:"approved"`
	Notes  string `json:"notes"`
}

type UpdateClaimRequest struct {
	Description string `json:"description" binding:"required"`
	Contact     string `json:"contact" binding:"required"`
	Reason      string `json:"reason" binding:"required"` // Why admin is updating
}

// ✅ FIXED: UpdateClaim with correct types and fields
func (s *ClaimService) UpdateClaim(adminID, claimID uint, req UpdateClaimRequest, ipAddress, userAgent string) (*models.ClaimResponse, error) {
	// Get existing claim
	var claim models.Claim
	if err := s.db.Preload("Item").Preload("User").First(&claim, claimID).Error; err != nil {
		return nil, errors.New("claim not found")
	}

	// Only allow updating pending claims
	if claim.Status != models.ClaimStatusPending {
		return nil, errors.New("can only update pending claims")
	}

	// Store old values for audit
	oldDescription := claim.Description
	oldContact := claim.Contact

	// Update claim
	claim.Description = req.Description
	claim.Contact = req.Contact

	if err := s.db.Save(&claim).Error; err != nil {
		return nil, err
	}

	// ✅ FIX: Create audit log with correct field
	adminIDPtr := &adminID
	claimIDPtr := &claimID
	audit := models.AuditLog{
		UserID:     adminIDPtr,
		Action:     "update_claim",
		EntityType: "claims",
		EntityID:   claimIDPtr,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Details:    fmt.Sprintf("Updated claim #%d. Reason: %s. Changes: description='%s'->'%s', contact='%s'->'%s'", claimID, req.Reason, oldDescription, req.Description, oldContact, req.Contact),
	}
	s.db.Create(&audit)

	// Get updated claim with all relations
	var updatedClaim models.Claim
	if err := s.db.
		Preload("Item").
		Preload("User").
		Preload("VerifiedBy").
		First(&updatedClaim, claimID).Error; err != nil {
		return nil, err
	}

	// ✅ FIX: Return pointer to ClaimResponse
	response := updatedClaim.ToResponse()
	return &response, nil
}

func (s *ClaimService) ProcessUserDecision(userID uint, claimID uint, action string) error {
	var claim models.Claim
	// Preload LostItem untuk memastikan relasi benar
	if err := s.db.Preload("Item").Preload("LostItem").First(&claim, claimID).Error; err != nil {
		return errors.New("klaim tidak ditemukan")
	}
	
	// Validasi: Apakah ini Direct Claim?
	if claim.LostItemID == nil {
		return errors.New("ini bukan direct claim, user tidak bisa approve manual (harus manager)")
	}

	// Cek apakah User yang me-request adalah pemilik Lost Item
	if claim.LostItem.UserID != userID {
		return errors.New("anda tidak memiliki akses ke laporan kehilangan ini")
	}
	
	// Cek Status Klaim
	if claim.Status != models.ClaimStatusWaitingOwner {
		return errors.New("klaim ini tidak dalam status menunggu persetujuan anda")
	}
	
	tx := s.db.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()
	
	if action == "approve" {
		// Owner approve -> status jadi VERIFIED (Agar muncul tombol Case Closed)
		claim.Status = models.ClaimStatusVerified 
		claim.Notes = "Disetujui langsung oleh pemilik (Direct Match)"
		now := time.Now()
		claim.VerifiedAt = &now
		claim.VerifiedBy = &userID // Pemilik bertindak sebagai verifikator
		
		// Update lost item status to found
		if err := tx.Model(&models.LostItem{}).Where("id = ?", *claim.LostItemID).
			Updates(map[string]interface{}{
				"status": models.LostItemStatusFound,
				"matched_at": now,
			}).Error; err != nil {
			tx.Rollback()
			return err
		}
		
		// Notifikasi ke Penemu
		s.notificationRepo.Notify(
			claim.UserID,
			"direct_claim_approved",
			"Klaim Disetujui Pemilik!",
			fmt.Sprintf("Pemilik telah menyetujui bahwa Anda menemukan barang '%s'. Silakan hubungi untuk koordinasi pengambilan.", claim.LostItem.Name),
			"claim",
			&claim.ID,
		)
		
		s.auditLogRepo.Log(&userID, "approve_direct_claim", "claim", &claim.ID, "Owner approved direct claim", "", "")
		
	} else if action == "reject" {
		// Owner reject
		claim.Status = models.ClaimStatusRejected
		claim.Notes = "Ditolak oleh pemilik (Direct Match)"
		now := time.Now()
		claim.VerifiedAt = &now
		claim.VerifiedBy = &userID
		
		// Reset lost item status back to active agar bisa dicari lagi
		if err := tx.Model(&models.LostItem{}).Where("id = ?", *claim.LostItemID).
			Updates(map[string]interface{}{
				"status": models.LostItemStatusActive,
				"direct_claim_id": gorm.Expr("NULL"), // Hapus link ke claim ini
			}).Error; err != nil {
			tx.Rollback()
			return err
		}
		
		// Notifikasi ke Penemu
		s.notificationRepo.Notify(
			claim.UserID,
			"direct_claim_rejected",
			"Klaim Ditolak Pemilik",
			fmt.Sprintf("Pemilik menolak klaim Anda untuk '%s'. Barang kembali dalam status pencarian.", claim.LostItem.Name),
			"claim",
			&claim.ID,
		)
		
		s.auditLogRepo.Log(&userID, "reject_direct_claim", "claim", &claim.ID, "Owner rejected direct claim", "", "")
	}
	
	if err := tx.Save(&claim).Error; err != nil {
		tx.Rollback()
		return err
	}
	
	return tx.Commit().Error
}


func (s *ClaimService) UserConfirmCompletion(userID uint, claimID uint) error {
	return s.db.Transaction(func(tx *gorm.DB) error {
		var claim models.Claim
		if err := tx.Preload("LostItem").Preload("Item").First(&claim, claimID).Error; err != nil {
			return err
		}

		// 🔥 PERBAIKAN: Cek apakah ini direct claim (dari lost_item) atau regular claim (dari item)
		if claim.LostItemID != nil {
			// ✅ Ini direct claim - update lost_item
			var lostItem models.LostItem
			if err := tx.First(&lostItem, *claim.LostItemID).Error; err != nil {
				return errors.New("lost item tidak ditemukan")
			}
			
			if lostItem.UserID != userID {
				return errors.New("unauthorized: anda bukan pemilik barang hilang ini")
			}
			
			now := time.Now()
			lostItem.Status = models.LostItemStatusCompleted
			lostItem.MatchedAt = &now
			
			if err := tx.Save(&lostItem).Error; err != nil {
				return err
			}

			// ✅ TAMBAHAN: Update claim status jadi "completed"
			claim.Status = "completed"
			claim.Notes = claim.Notes + " [Case Closed by Owner Confirmation]"
			if err := tx.Save(&claim).Error; err != nil {
				return err
			}

		} else if claim.ItemID != nil {
			// ✅ PERBAIKAN BARU: Ini regular claim dari "Barang yang Saya Temukan"
			// User yang MENEMUKAN barang mengonfirmasi sudah menyerahkan ke pemilik
			
			var item models.Item
			if err := tx.First(&item, *claim.ItemID).Error; err != nil {
				return errors.New("item tidak ditemukan")
			}

			// Cek: Apakah user ini adalah PENEMU (reporter)?
			if item.ReporterID != userID {
				return errors.New("unauthorized: anda bukan penemu barang ini")
			}

			// ✅ Update item status menjadi "case_closed" atau "completed"
			now := time.Now()
			item.Status = models.ItemStatusCaseClosed
			item.CaseClosedAt = &now
			item.CaseClosedBy = &userID
			item.CaseClosedNotes = "Barang telah diserahkan ke pemilik (dikonfirmasi oleh penemu)"

			if err := tx.Save(&item).Error; err != nil {
				return err
			}

			// ✅ Update claim status
			claim.Status = "completed"
			claim.Notes = claim.Notes + " [Confirmed: Item handed to owner]"
			if err := tx.Save(&claim).Error; err != nil {
				return err
			}

			// ✅ Arsipkan item
			archive := &models.Archive{
				ItemID:          item.ID,
				Name:            item.Name,
				CategoryID:      item.CategoryID,
				PhotoURL:        item.PhotoURL,
				Location:        item.Location,
				Description:     item.Description,
				DateFound:       item.DateFound,
				Status:          models.ItemStatusCaseClosed,
				ReporterName:    item.ReporterName,
				ReporterContact: item.ReporterContact,
				ArchivedReason:  "completed_by_finder",
				ClaimedBy:       &claim.UserID, // pemilik yang mengklaim
				ArchivedAt:      now,
			}
			if err := tx.Create(archive).Error; err != nil {
				return err
			}

		} else {
			return errors.New("invalid claim: no item or lost_item attached")
		}

		// Notifikasi
		s.notificationRepo.Notify(
			claim.UserID,
			"case_completed",
			"Kasus Selesai!",
			"Barang telah dikembalikan. Terima kasih atas partisipasi Anda!",
			"claim",
			&claim.ID,
		)

		// Audit log
		s.auditLogRepo.Log(
			&userID,
			"complete_case",
			"claim",
			&claim.ID,
			"User confirmed case completion",
			"",
			"",
		)

		return nil
	})
}
// ✅ [MANUAL TRANSACTION EXAMPLE]
// VerifyClaimManual adalah versi manual dari VerifyClaim menggunakan Begin, Commit, Rollback
func (s *ClaimService) VerifyClaimManual(managerID, claimID uint, req VerifyClaimRequest, similarityScore float64, matchedKeywords string, ipAddress, userAgent string) error {
	// 1. BEGIN TRANSACTION
	// Memulai transaksi dan mendapatkan object 'tx' (database handle khusus transaksi ini)
	tx := s.db.Begin()
	
	// Cek apakah begin berhasil
	if tx.Error != nil {
		return tx.Error
	}

	// 2. DEFER ROLLBACK
	// Pastikan Rollback dipanggil jika fungsi berhenti tiba-tiba (panic atau return error sebelum commit)
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	// --- Mulai Logic Bisnis (Gunakan 'tx' bukan 's.db') ---

	// Lock claim
	var claim models.Claim
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
		Preload("Item").
		First(&claim, claimID).Error; err != nil {
		tx.Rollback() // ❌ Manual Rollback
		return errors.New("claim not found or locked")
	}

	if !claim.IsPending() {
		tx.Rollback() // ❌ Manual Rollback
		return errors.New("claim is not pending")
	}

	// Update Verification Data
	var verification models.ClaimVerification
	if err := tx.Where("claim_id = ?", claimID).First(&verification).Error; err == nil {
		// Update existing
		verification.VerificationNotes = req.Notes
		if err := tx.Save(&verification).Error; err != nil {
			tx.Rollback() // ❌ Manual Rollback
			return err
		}
	} else {
		// Create new
		newVerification := models.ClaimVerification{
			ClaimID:           claimID,
			SimilarityScore:   similarityScore,
			VerificationNotes: req.Notes,
		}
		if err := tx.Create(&newVerification).Error; err != nil {
			tx.Rollback() // ❌ Manual Rollback
			return err
		}
	}

	// Update Claim & Item Status
	now := time.Now()
	if req.Status == models.ClaimStatusApproved {
		// Update Claim
		claim.Status = models.ClaimStatusApproved
		claim.VerifiedBy = &managerID
		claim.VerifiedAt = &now
		
		if err := tx.Save(&claim).Error; err != nil {
			tx.Rollback() // ❌ Manual Rollback
			return err
		}

		// Update Item
		if err := tx.Model(&models.Item{}).
			Where("id = ?", claim.ItemID).
			Update("status", models.ItemStatusVerified).Error; err != nil {
			tx.Rollback() // ❌ Manual Rollback
			return err
		}

	} else if req.Status == models.ClaimStatusRejected {
		claim.Status = models.ClaimStatusRejected
		claim.VerifiedBy = &managerID
		claim.VerifiedAt = &now
		
		if err := tx.Save(&claim).Error; err != nil {
			tx.Rollback() // ❌ Manual Rollback
			return err
		}
	}

	// Log Audit
	auditLog := &models.AuditLog{
		UserID:     &managerID,
		Action:     "verify_manual",
		EntityType: "claim",
		EntityID:   &claimID,
		Details:    "Verified using manual transaction",
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
	}
	if err := tx.Create(auditLog).Error; err != nil {
		tx.Rollback() // ❌ Manual Rollback
		return err
	}

	// 3. COMMIT TRANSACTION
	// Jika semua logic di atas sukses tanpa return error, lakukan Commit permanen
	if err := tx.Commit().Error; err != nil {
		return err
	}

	return nil
}


// ✅ FIXED: CreateClaim with proper error handling
func (s *ClaimService) CreateClaim(userID uint, req CreateClaimRequest, ipAddress, userAgent string) (*models.Claim, error) {
	// If admin provides a different user_id, use that
	actualUserID := userID
	if req.UserID != nil && *req.UserID > 0 {
		// Verify user exists
		var targetUser models.User
		if err := s.db.First(&targetUser, *req.UserID).Error; err != nil {
			return nil, errors.New("target user not found")
		}
		actualUserID = *req.UserID
	}

	// Get item
	var item models.Item
	if err := s.db.First(&item, req.ItemID).Error; err != nil {
		return nil, errors.New("item not found")
	}

	// Check if item can be claimed
	if item.Status != models.ItemStatusUnclaimed && item.Status != models.ItemStatusPendingClaim {
		return nil, errors.New("item cannot be claimed")
	}

	// Prevent claiming own reported items
	if item.ReporterID == actualUserID {
		return nil, errors.New("you cannot claim your own reported item")
	}

	// Check for duplicate claims
	var existingClaim models.Claim
	err := s.db.Where("item_id = ? AND user_id = ? AND status = ?",
		req.ItemID, actualUserID, models.ClaimStatusPending).
		First(&existingClaim).Error

	if err == nil {
		return nil, errors.New("you already have a pending claim for this item")
	}

	// ✅ FIX: Create claim without MatchPercentage if it doesn't exist
	// Check if your Claim model has MatchPercentage field
	itemID := req.ItemID
	claim := models.Claim{
		ItemID:      &itemID, // Set ItemID pointer
		UserID:      actualUserID,
		Description: req.Description,
		Contact:     req.Contact,
		ProofURL:    req.ProofURL,
		Status:      models.ClaimStatusPending,
	}

	if err := s.db.Create(&claim).Error; err != nil {
		return nil, err
	}

	// Update item status
	item.Status = models.ItemStatusPendingClaim
	s.db.Save(&item)

	// ✅ FIX: Create audit log with correct fields
	userIDPtr := &userID
	claimIDPtr := &claim.ID
	description := fmt.Sprintf("Created claim for item #%d", req.ItemID)
	if req.AdminNotes != "" {
		description += fmt.Sprintf(" (Admin notes: %s)", req.AdminNotes)
	}

	audit := models.AuditLog{
		UserID:     userIDPtr, // The creator (might be admin)
		Action:     "create_claim",
		EntityType: "claims",
		EntityID:   claimIDPtr,
		IPAddress:  ipAddress,
		UserAgent:  userAgent,
		Details:    description,
	}
	s.db.Create(&audit)

	// Reload with relations
	s.db.Preload("Item").Preload("User").First(&claim, claim.ID)

	return &claim, nil
}

// GetAllClaims gets all claims with CONTEXT TIMEOUT
func (s *ClaimService) GetAllClaims(page, limit int, status string, itemID, userID *uint) ([]models.ClaimResponse, int64, error) {
	// ✅ 1. Definisikan context dan timeout di sini
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	// ✅ 2. Gunakan ctx saat inisialisasi repository
	txRepo := repositories.NewClaimRepository(s.db.WithContext(ctx))

	// 3. Panggil repository
	claims, total, err := txRepo.FindAll(page, limit, status, itemID, userID)
	if err != nil {
		return nil, 0, err
	}

	// 4. Konversi ke response
	responses := make([]models.ClaimResponse, 0)
	for _, claim := range claims {
		responses = append(responses, claim.ToResponse())
	}

	return responses, total, nil
}

// GetClaimByID gets claim by ID with CONTEXT
func (s *ClaimService) GetClaimByID(id uint, isManager bool) (interface{}, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	var claim models.Claim
	query := s.db.WithContext(ctx).
		Preload("Item").Preload("Item.Category").
		Preload("LostItem").
		Preload("User").Preload("User.Role").
		Preload("Verifier").Preload("Verifier.Role").
		Preload("Verification")

	if err := query.First(&claim, id).Error; err != nil {
		if ctx.Err() == context.DeadlineExceeded {
			return nil, errors.New("request timeout")
		}
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("claim not found")
		}
		return nil, err
	}

	if isManager {
		return claim.ToDetailResponse(), nil
	}

	return claim.ToResponse(), nil
}

// GetClaimsByUser gets claims by user with CONTEXT
func (s *ClaimService) GetClaimsByUser(userID uint, page, limit int) ([]models.ClaimResponse, int64, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	txRepo := repositories.NewClaimRepository(s.db.WithContext(ctx))
	claims, total, err := txRepo.FindByUser(userID, page, limit)
	if err != nil {
		if ctx.Err() == context.DeadlineExceeded {
			return nil, 0, errors.New("request timeout")
		}
		return nil, 0, err
	}

	var responses []models.ClaimResponse
	for _, claim := range claims {
		responses = append(responses, claim.ToResponse())
	}

	return responses, total, nil
}

// VerifyClaim verifies a claim with TRANSACTION + LOCKING
// VerifyClaim verifies a claim with TRANSACTION + LOCKING
func (s *ClaimService) VerifyClaim(managerID, claimID uint, req VerifyClaimRequest, similarityScore float64, matchedKeywords string, ipAddress, userAgent string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// Lock claim untuk prevent concurrent verification
		var claim models.Claim
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Preload("Item").Preload("LostItem"). // ✅ Load dua-duanya
			Preload("User").
			Where("id = ? AND deleted_at IS NULL", claimID).
			First(&claim).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errors.New("claim not found")
			}
			return fmt.Errorf("failed to lock claim: %w", err)
		}

		if !claim.IsPending() {
			return errors.New("claim is not pending (status: " + claim.Status + ")")
		}

		// 1. Create or update verification record
		var verification models.ClaimVerification
		err := tx.Where("claim_id = ?", claimID).First(&verification).Error
		
		if errors.Is(err, gorm.ErrRecordNotFound) {
			verification = models.ClaimVerification{
				ClaimID:           claimID,
				SimilarityScore:   similarityScore,
				MatchedKeywords:   matchedKeywords,
				VerificationNotes: req.Notes,
				IsAutoMatched:     false,
			}
			if err := tx.Create(&verification).Error; err != nil {
				return fmt.Errorf("failed to create verification: %w", err)
			}
		} else if err != nil {
			return fmt.Errorf("failed to check verification: %w", err)
		} else {
			verification.VerificationNotes = req.Notes
			verification.SimilarityScore = similarityScore
			verification.MatchedKeywords = matchedKeywords
			if err := tx.Save(&verification).Error; err != nil {
				return fmt.Errorf("failed to update verification: %w", err)
			}
		}

		// 2. Update claim status
		now := time.Now()
		if req.Status == models.ClaimStatusApproved {
			claim.Status = models.ClaimStatusApproved
			claim.VerifiedBy = &managerID
			claim.VerifiedAt = &now
			claim.Notes = req.Notes

			// ✅ NEW: Update item status to verified
			if err := tx.Model(&models.Item{}).
				Where("id = ?", claim.ItemID).
				Update("status", models.ItemStatusVerified).Error; err != nil {
				return fmt.Errorf("failed to update item status: %w", err)
			}

			result := tx.Model(&models.LostItem{}).
				Where("user_id = ? AND category_id = ? AND status = ?",
					claim.UserID, claim.Item.CategoryID, models.LostItemStatusActive).
				Updates(map[string]interface{}{
					"status":      models.LostItemStatusFound, // Sesuai request Anda
					"matched_at":  now,                        // Opsional: mencatat kapan match terjadi
				})

				if result.Error != nil {
				return fmt.Errorf("failed to update lost item status: %w", result.Error)
			}

			

			// ✅ NEW: Cari apakah ada lost_item yang match dengan item ini
			// Caranya: Cek di match_results apakah ada match antara item_id dan lost_item_id
			var matchResults []models.MatchResult
			if err := tx.Where("item_id = ? AND deleted_at IS NULL", claim.ItemID).
				Preload("LostItem").
				Find(&matchResults).Error; err != nil {
				return fmt.Errorf("failed to find match results: %w", err)
			}

			// ✅ NEW: Update semua lost_items yang match menjadi resolved
			for _, match := range matchResults {
				if match.LostItem.Status == models.LostItemStatusActive {
					if err := tx.Model(&models.LostItem{}).
						Where("id = ?", match.LostItemID).
						Updates(map[string]interface{}{
							"status":      models.LostItemStatusFound,
							"resolved_at": now,
						}).Error; err != nil {
						return fmt.Errorf("failed to update lost item status: %w", err)
					}

					// ✅ NEW: Kirim notifikasi ke pemilik lost_item
					notification := &models.Notification{
						UserID:     match.LostItem.UserID,
						Type:       "lost_item_resolved",
						Title:      "Barang Ditemukan!",
						Message:    fmt.Sprintf("Laporan kehilangan Anda untuk '%s' telah ditemukan dan diklaim oleh pemiliknya", match.LostItem.Name),
						EntityType: "lost_item",
						EntityID:   &match.LostItemID,
					}
					if err := tx.Create(notification).Error; err != nil {
						return fmt.Errorf("failed to create lost item notification: %w", err)
					}
				}
			}

			// Create approval notification untuk claimer
			notification := &models.Notification{
				UserID:     claim.UserID,
				Type:       models.NotificationClaimApproved,
				Title:      "Klaim Disetujui! Barang Ditemukan",
				Message:    fmt.Sprintf("Selamat! Klaim Anda untuk '%s' telah disetujui. Status laporan kehilangan Anda kini 'Ditemukan'. Silakan hubungi admin untuk pengambilan.", claim.Item.Name),
				EntityType: models.EntityClaim,
				EntityID:   &claimID,
			}
			if err := tx.Create(notification).Error; err != nil {
				return fmt.Errorf("failed to create notification: %w", err)
			}

		} else if req.Status == models.ClaimStatusRejected {
			claim.Status = models.ClaimStatusRejected
			claim.VerifiedBy = &managerID
			claim.VerifiedAt = &now
			claim.Notes = req.Notes

			// Check if there are other pending claims
			var otherPendingCount int64
			if err := tx.Model(&models.Claim{}).
				Where("item_id = ? AND id != ? AND status = ? AND deleted_at IS NULL",
					claim.ItemID, claimID, models.ClaimStatusPending).
				Count(&otherPendingCount).Error; err != nil {
				return fmt.Errorf("failed to check other claims: %w", err)
			}

			// If no other pending claims, set item back to unclaimed
			if otherPendingCount == 0 {
				if err := tx.Model(&models.Item{}).
					Where("id = ?", claim.ItemID).
					Update("status", models.ItemStatusUnclaimed).Error; err != nil {
					return fmt.Errorf("failed to update item status: %w", err)
				}
			}

			// Create rejection notification
			notification := &models.Notification{
				UserID:     claim.UserID,
				Type:       models.NotificationClaimRejected,
				Title:      "Klaim Ditolak",
				Message:    fmt.Sprintf("Klaim Anda untuk barang '%s' ditolak. Alasan: %s", claim.Item.Name, req.Notes),
				EntityType: models.EntityClaim,
				EntityID:   &claimID,
			}
			if err := tx.Create(notification).Error; err != nil {
				return fmt.Errorf("failed to create notification: %w", err)
			}

		} else {
			return errors.New("invalid status: must be 'approved' or 'rejected'")
		}

		// 3. Save claim
		if err := tx.Save(&claim).Error; err != nil {
			return fmt.Errorf("failed to update claim: %w", err)
		}

		// 4. Create audit log
		action := models.ActionApprove
		if req.Status == models.ClaimStatusRejected {
			action = models.ActionReject
		}

		auditLog := &models.AuditLog{
			UserID:     &managerID,
			Action:     action,
			EntityType: models.EntityClaim,
			EntityID:   &claimID,
			Details:    fmt.Sprintf("Claim %s: %s", req.Status, req.Notes),
			IPAddress:  ipAddress,
			UserAgent:  userAgent,
		}
		if err := tx.Create(auditLog).Error; err != nil {
			return fmt.Errorf("failed to create audit log: %w", err)
		}

		return nil
	})
}
// CloseClaim closes a claim and archives item with TRANSACTION

// Ganti nama fungsi dari CloseClaim menjadi CloseCase
func (s *ClaimService) CloseCase(managerID, claimID uint, req CloseCaseRequest, ipAddress, userAgent string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// Lock claim
		var claim models.Claim
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Preload("Item").Preload("Item.Category").
			Where("id = ? AND deleted_at IS NULL", claimID).
			First(&claim).Error; err != nil {
			return fmt.Errorf("failed to lock claim: %w", err)
		}

		// Validasi Status
		if !claim.IsApproved() {
			return errors.New("only approved claims can be closed")
		}
		
		if claim.ItemID == nil {
			return errors.New("direct claims (lost items) should be closed via user confirmation, not admin close case")
		}

		if claim.Item.Status == models.ItemStatusCaseClosed {
			return errors.New("case already closed")
		}

		item := claim.Item

		// ✅ 1. Update item status (Barang Temuan selesai)
		if err := tx.Model(&item).Updates(map[string]interface{}{
			"status":             models.ItemStatusCaseClosed,
			"berita_acara_no":    req.BeritaAcaraNo,
			"bukti_serah_terima": req.BuktiSerahTerima,
			"case_closed_at":     time.Now(),
			"case_closed_by":     managerID,
			"case_closed_notes":  req.Notes,
		}).Error; err != nil {
			return fmt.Errorf("failed to close case: %w", err)
		}

		// ✅ 2. Update status LostItem (Laporan Kehilangan selesai)
		// Kita cari lost_item milik user ini, kategori sama, yang statusnya 'found'
		if err := tx.Model(&models.LostItem{}).
			Where("user_id = ? AND category_id = ? AND status = ?",
				claim.UserID, item.CategoryID, models.LostItemStatusFound).
			Update("status", models.LostItemStatusClosed).Error; err != nil { // Gunakan constant model jika ada
			return fmt.Errorf("failed to update lost item status to closed: %w", err)
		}

		// ✅ 3. Manage Archive (Create or Update)
		var existingArchive models.Archive
		err := tx.Where("item_id = ?", item.ID).First(&existingArchive).Error

		if err == nil {
			// Archive exists, UPDATE it
			if err := tx.Model(&existingArchive).Updates(map[string]interface{}{
				"status":             models.ItemStatusCaseClosed,
				"archived_reason":    models.ArchiveReasonCaseClosed,
				"claimed_by":         &claim.UserID,
				"berita_acara_no":    req.BeritaAcaraNo,
				"bukti_serah_terima": req.BuktiSerahTerima,
				"archived_at":        time.Now(),
				"name":               item.Name,
				"category_id":        item.CategoryID,
				"photo_url":          item.PhotoURL,
				"location":           item.Location,
				"description":        item.Description,
				"date_found":         item.DateFound,
				"reporter_name":      item.ReporterName,
				"reporter_contact":   item.ReporterContact,
			}).Error; err != nil {
				return fmt.Errorf("failed to update archive: %w", err)
			}
		} else if errors.Is(err, gorm.ErrRecordNotFound) {
			// Archive doesn't exist, CREATE new one
			archive := &models.Archive{
				ItemID:           item.ID,
				Name:             item.Name,
				CategoryID:       item.CategoryID,
				PhotoURL:         item.PhotoURL,
				Location:         item.Location,
				Description:      item.Description,
				DateFound:        item.DateFound,
				Status:           models.ItemStatusCaseClosed,
				ReporterName:     item.ReporterName,
				ReporterContact:  item.ReporterContact,
				ArchivedReason:   models.ArchiveReasonCaseClosed,
				ClaimedBy:        &claim.UserID,
				BeritaAcaraNo:    req.BeritaAcaraNo,
				BuktiSerahTerima: req.BuktiSerahTerima,
				ArchivedAt:       time.Now(),
			}
			if err := tx.Create(archive).Error; err != nil {
				return fmt.Errorf("failed to create archive: %w", err)
			}
		} else {
			return fmt.Errorf("failed to check archive: %w", err)
		}

		// ✅ 4. Create Revision Log
		revisionLog := &models.RevisionLog{
			ItemID:    item.ID,
			UserID:    managerID,
			FieldName: "status",
			OldValue:  models.ItemStatusVerified,
			NewValue:  models.ItemStatusCaseClosed,
			Reason:    fmt.Sprintf("Case closed with BA No: %s", req.BeritaAcaraNo),
		}
		if err := tx.Create(revisionLog).Error; err != nil {
			return fmt.Errorf("failed to create revision log: %w", err)
		}

		// ✅ 5. Create Audit Log
		auditLog := &models.AuditLog{
			UserID:     &managerID,
			Action:     "close_case", // Action name updated
			EntityType: models.EntityItem,
			EntityID:   &item.ID,
			Details:    fmt.Sprintf("Case closed (Claim ID: %d, BA No: %s)", claimID, req.BeritaAcaraNo),
			IPAddress:  ipAddress,
			UserAgent:  userAgent,
		}
		if err := tx.Create(auditLog).Error; err != nil {
			return fmt.Errorf("failed to create audit log: %w", err)
		}

		// ✅ 6. Send Notification
		notification := &models.Notification{
			UserID:     claim.UserID,
			Type:       "case_closed",
			Title:      "Kasus Selesai!",
			Message:    fmt.Sprintf("Kasus untuk barang '%s' telah selesai. Barang resmi diserahterimakan.", item.Name),
			EntityType: models.EntityClaim,
			EntityID:   &claimID,
		}
		if err := tx.Create(notification).Error; err != nil {
			return fmt.Errorf("failed to create notification: %w", err)
		}

		return nil
	})
}

func (s *ClaimService) ReopenCase(managerID, claimID uint, req ReopenCaseRequest, ipAddress, userAgent string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// 1. Lock & Load Claim dengan Item DAN LostItem
		var claim models.Claim
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Preload("Item").
			Preload("LostItem"). // ✅ PENTING: Load LostItem juga
			Where("id = ? AND deleted_at IS NULL", claimID).
			First(&claim).Error; err != nil {
			return fmt.Errorf("failed to lock claim: %w", err)
		}

		// 2. Validasi & Revert Logic Berdasarkan Tipe Claim
		itemName := "Unknown Item"

		if claim.ItemID != nil {
			// --- OPSI A: REGULAR CLAIM (Barang Temuan) ---
			itemName = claim.Item.Name
			
			if claim.Item.Status != models.ItemStatusCaseClosed {
				return errors.New("only closed cases can be reopened")
			}

			// Revert Item Status ke 'Verified' (Karena Claim statusnya masih Approved)
			// Kita kembalikan ke Verified, bukan Pending, agar konsisten dengan Claim.Status = Approved
			if err := tx.Model(&claim.Item).Updates(map[string]interface{}{
				"status":             models.ItemStatusVerified,
				"case_closed_at":     nil,
				"case_closed_by":     nil,
				"case_closed_notes":  "",
				"berita_acara_no":    "",
				"bukti_serah_terima": "",
			}).Error; err != nil {
				return fmt.Errorf("failed to reopen item case: %w", err)
			}

			// Delete Archive (Karena kasus dibuka lagi, arsip harus dihapus)
			if err := tx.Unscoped().Where("item_id = ?", *claim.ItemID).Delete(&models.Archive{}).Error; err != nil {
				return fmt.Errorf("failed to delete archive: %w", err)
			}

			// Create Revision Log
			revisionLog := &models.RevisionLog{
				ItemID:    *claim.ItemID,
				UserID:    managerID,
				FieldName: "status",
				OldValue:  models.ItemStatusCaseClosed,
				NewValue:  models.ItemStatusVerified,
				Reason:    "Case reopened: " + req.Reason,
			}
			tx.Create(revisionLog)

		} else if claim.LostItemID != nil {
			// --- OPSI B: DIRECT CLAIM (Barang Hilang) ---
			itemName = claim.LostItem.Name

			// Cek apakah statusnya Closed?
			if claim.LostItem.Status != models.LostItemStatusClosed {
				return errors.New("only closed lost item cases can be reopened")
			}

			// Revert Lost Item Status ke 'Found' (Status sebelum Closed)
			if err := tx.Model(&claim.LostItem).Updates(map[string]interface{}{
				"status": models.LostItemStatusFound,
			}).Error; err != nil {
				return fmt.Errorf("failed to reopen lost item case: %w", err)
			}
		} else {
			return errors.New("invalid claim data: no item or lost_item attached")
		}

		// 3. Create Audit Log
		auditLog := &models.AuditLog{
			UserID:     &managerID,
			Action:     "reopen_case",
			EntityType: models.EntityClaim,
			EntityID:   &claimID,
			Details:    "Case reopened. Reason: " + req.Reason,
			IPAddress:  ipAddress,
			UserAgent:  userAgent,
		}
		if err := tx.Create(auditLog).Error; err != nil {
			return err
		}

		// 4. Send Notification
		notification := &models.Notification{
			UserID:     claim.UserID,
			Type:       "case_reopened",
			Title:      "Kasus Dibuka Kembali",
			Message:    fmt.Sprintf("Kasus untuk barang '%s' dibuka kembali oleh admin. Alasan: %s", itemName, req.Reason),
			EntityType: models.EntityClaim,
			EntityID:   &claimID,
		}
		tx.Create(notification)

		return nil
	})
}
// Atomicity & Consistency	
// Penggunaan db.Transaction (misalnya di VerifyClaim ) memastikan pembaruan status item dan klaim terjadi dalam satu unit kerja yang utuh.
func (s *ClaimService) CancelClaimApproval(managerID, claimID uint) error {
    return s.db.Transaction(func(tx *gorm.DB) error {
        var claim models.Claim
        if err := tx.Preload("Item").First(&claim, claimID).Error; err != nil {
            return err
        }

        // Hanya boleh cancel jika status Approved
        if claim.Status != models.ClaimStatusApproved {
            return errors.New("claim is not in approved status")
        }

        // Cek jika case sudah closed (ada BA), tidak boleh cancel lewat sini (pakai Reopen)
        if claim.Item.Status == models.ItemStatusCaseClosed {
            return errors.New("case is already closed, use Reopen instead")
        }

        // 1. Revert Claim Status to Pending
        claim.Status = models.ClaimStatusPending
        claim.VerifiedAt = nil
        claim.VerifiedBy = nil
        claim.Notes = claim.Notes + " [Approval Cancelled]"
        
        if err := tx.Save(&claim).Error; err != nil {
            return err
        }

        // 2. Revert Item Status to Unclaimed (or Pending Claim)
        // Karena klaim jadi pending, item harusnya pending_claim (dikunci oleh klaim ini)
        if err := tx.Model(&models.Item{}).
            Where("id = ?", claim.ItemID).
            Update("status", models.ItemStatusUnclaimed).Error; err != nil { // Atau pending_claim
            return err
        }

        // 3. Log Audit
        audit := models.AuditLog{
            UserID: &managerID,
            Action: "cancel_approval",
            EntityType: "claim",
            EntityID: &claimID,
            Details: "Manager cancelled approval, reverted to pending",
        }
        tx.Create(&audit)

        return nil
    })
}

// DeleteClaim handles deletion safely for both Regular and Direct claims
func (s *ClaimService) DeleteClaim(userID, claimID uint, ipAddress, userAgent string) error {
    var claim models.Claim
    if err := s.db.First(&claim, claimID).Error; err != nil {
        return errors.New("claim not found")
    }
    
    var user models.User
    if err := s.db.Preload("Role").First(&user, userID).Error; err != nil {
        return errors.New("user not found")
    }
    
    // Check permissions
    if !user.IsAdmin() && !user.IsManager() && claim.UserID != userID {
        return errors.New("you don't have permission to delete this claim")
    }
    
    // ===== FIX: Tambahkan completed ke daftar status yang bisa dihapus =====
    // Admin/Manager bisa hapus klaim dengan status: pending, waiting_owner, completed
    // User biasa hanya bisa hapus klaim miliknya yang pending atau waiting_owner
    
    isAdminOrManager := user.IsAdmin() || user.IsManager()
    
    if isAdminOrManager {
        // Admin/Manager bisa hapus klaim dengan status: pending, waiting_owner, approved, completed
        // Hanya tidak bisa hapus yang rejected (untuk audit trail)
        if claim.Status == models.ClaimStatusRejected {
            return errors.New("cannot delete rejected claims (audit purposes)")
        }
    } else {
        // User biasa hanya bisa hapus klaim miliknya yang pending atau waiting_owner
        if claim.Status != models.ClaimStatusPending && 
           claim.Status != models.ClaimStatusWaitingOwner {
            return errors.New("you can only delete your own pending or waiting_owner claims")
        }
    }
    
    logDetailID := uint(0)
    logDetailType := "unknown"
    
    // Handle regular claim (item-based)
    if claim.ItemID != nil {
        logDetailID = *claim.ItemID
        logDetailType = "item"
        
        // Only reset item status if this is the last pending claim
        var otherClaimsCount int64
        s.db.Model(&models.Claim{}).
            Where("item_id = ? AND id != ? AND status = ?", *claim.ItemID, claimID, models.ClaimStatusPending).
            Count(&otherClaimsCount)
        
        if otherClaimsCount == 0 {
            if err := s.db.Model(&models.Item{}).
                Where("id = ?", *claim.ItemID).
                Update("status", models.ItemStatusUnclaimed).Error; err != nil {
                return err
            }
        }
    } else if claim.LostItemID != nil {
        // Handle direct claim (lost item-based)
        logDetailID = *claim.LostItemID
        logDetailType = "lost_item"
        
        // Reset lost item status back to active
        if err := s.db.Model(&models.LostItem{}).
            Where("id = ?", *claim.LostItemID).
            Updates(map[string]interface{}{
                "status": models.LostItemStatusActive,
                "direct_claim_id": nil,
            }).Error; err != nil {
            return err
        }
    }
    
    // Delete the claim
    if err := s.db.Delete(&claim).Error; err != nil {
        return err
    }
    
    // Create audit log
    userIDPtr := &userID
    claimIDPtr := &claimID
    audit := models.AuditLog{
        UserID:     userIDPtr,
        Action:     "delete_claim",
        EntityType: "claims",
        EntityID:   claimIDPtr,
        IPAddress:  ipAddress,
        UserAgent:  userAgent,
        Details:    fmt.Sprintf("Deleted claim #%d (status: %s) for %s #%d", claimID, claim.Status, logDetailType, logDetailID),
    }
    s.db.Create(&audit)
    
    return nil
}