// internal/services/item_service.go - FIXED VERSION
package services

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

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

type ItemService struct {
	itemRepo     *repositories.ItemRepository
	categoryRepo *repositories.CategoryRepository
	auditLogRepo *repositories.AuditLogRepository
	revisionRepo *repositories.RevisionLogRepository
	db           *gorm.DB
	lostItemRepo     *repositories.LostItemRepository  // FIXED: pointer type
	notificationRepo *repositories.NotificationRepository  // FIXED: pointer type
	matchRepo        *repositories.MatchResultRepository       // TAMBAHAN
}

func NewItemService(db *gorm.DB) *ItemService {
	itemRepo := repositories.NewItemRepository(db)
	lostItemRepo := repositories.NewLostItemRepository(db)
	notifRepo := repositories.NewNotificationRepository(db)
	matchRepo := repositories.NewMatchResultRepository(db)
	
	return &ItemService{
		db:               db,
		itemRepo:         itemRepo,
		categoryRepo:     repositories.NewCategoryRepository(db),
		auditLogRepo:     repositories.NewAuditLogRepository(db),
		revisionRepo:     repositories.NewRevisionLogRepository(db),
		lostItemRepo:     lostItemRepo,
		notificationRepo: notifRepo,
		matchRepo:        matchRepo,
	}
}

func (s *ItemService) CreateFoundItemLinked(reporterID uint, req CreateFoundItemLinkedRequest, ipAddress, userAgent string) (*models.Item, error) {
    tx := s.db.Begin()
    if tx.Error != nil {
        return nil, tx.Error
    }

    // 1. Buat Item Awal (Status Default: unclaimed)
    item := &models.Item{
        Name:            req.Name,
        CategoryID:      req.CategoryID,
        Location:        req.Location,
        DateFound:       time.Now(),
        Description:     req.Description,
        ReporterID:      reporterID,
        ReporterName:    req.ReporterName,
        ReporterContact: req.ReporterContact,
        Status:          models.ItemStatusUnclaimed, // Default
    }

    if req.PhotoURL != "" {
        item.PhotoURL = req.PhotoURL
    }

    if err := tx.Create(item).Error; err != nil {
        tx.Rollback()
        return nil, err
    }

    // 2. Logika "Langsung ke Pemilik"
    // Pastikan req.LostItemID & req.IsDirectToOwner terisi (Cek tag JSON struct!)
    if req.IsDirectToOwner && req.LostItemID != 0 {
        var lostItem models.LostItem
        // Gunakan Preload agar data User tidak nil saat dipakai nanti
        if err := tx.Preload("User").First(&lostItem, req.LostItemID).Error; err != nil {
            tx.Rollback()
            return nil, errors.New("Laporan kehilangan tidak ditemukan")
        }

        // A. Update Status Lost Item jadi "claimed"
        if err := tx.Model(&lostItem).Update("status", "claimed").Error; err != nil {
            tx.Rollback()
            return nil, err
        }

        // B. Update Status Item Temuan jadi "waiting_owner"
        if err := tx.Model(item).Update("status", "waiting_owner").Error; err != nil {
            tx.Rollback()
            return nil, err
        }
        
        // ✅ PENTING: Update struct di memori supaya response API benar
        itemID := item.ID 
		
		newClaim := &models.Claim{
			ItemID:      &itemID, // PENTING: Harus pointer
			UserID:      lostItem.UserID,
			Description: "Auto-match: Ditemukan dan diserahkan langsung ke pemilik.",
			Contact:     lostItem.User.Phone,
			Status:      models.ClaimStatusWaitingOwner,
		}

        if err := tx.Create(newClaim).Error; err != nil {
            tx.Rollback()
            return nil, err
        }
    }

    if err := tx.Commit().Error; err != nil {
        return nil, err
    }

    return item, nil
}
type CreateItemRequest struct {
	Name            string    `json:"name" binding:"required"`
	CategoryID      uint      `json:"category_id" binding:"required"`
	PhotoURL        string    `json:"photo_url"`
	Location        string    `json:"location" binding:"required"`
	Description     string    `json:"description" binding:"required"`
	SecretDetails   string    `json:"secret_details" binding:"required"`
	DateFound       time.Time `json:"date_found" binding:"required"`
	ReporterName    string    `json:"reporter_name" binding:"required"`
	ReporterContact string    `json:"reporter_contact" binding:"required"`
}

type UpdateItemRequest struct {
	Name            string    `json:"name"`
	CategoryID      uint      `json:"category_id"`
	PhotoURL        string    `json:"photo_url"`
	Location        string    `json:"location"`
	Description     string    `json:"description"`
	SecretDetails   string    `json:"secret_details"`
	DateFound       time.Time `json:"date_found"`
	ReporterName    string    `json:"reporter_name"`
	ReporterContact string    `json:"reporter_contact"`
	Status          string    `json:"status"`
	Reason          string    `json:"reason"`
}

type CreateFoundItemLinkedRequest struct {
	CreateItemRequest
	LostItemID      uint `json:"lost_item_id" binding:"required"`
	IsDirectToOwner bool `json:"is_direct_to_owner"`
}

// GetAllItems gets all items with CONTEXT TIMEOUT
func (s *ItemService) GetAllItems(page, limit int, status, category, search string) ([]models.ItemPublicResponse, int64, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	txRepo := repositories.NewItemRepository(s.db.WithContext(ctx))
	items, total, err := txRepo.FindAll(page, limit, status, category, search)
	if err != nil {
		if ctx.Err() == context.DeadlineExceeded {
			return nil, 0, errors.New("request timeout: query took too long")
		}
		return nil, 0, err
	}

	var responses []models.ItemPublicResponse
	for _, item := range items {
		responses = append(responses, item.ToPublicResponse())
	}

	return responses, total, nil
}

func (s *ItemService) RunAutoArchive(ipAddress, userAgent string) (int, error) {
    // Panggil Repository
    count, err := s.itemRepo.CallArchiveExpiredProcedure()
    if err != nil {
        return 0, err
    }

    // Log Audit jika ada yang diarsip
    if count > 0 {
        details := fmt.Sprintf("Auto-archived %d expired items using Stored Procedure", count)
        // Gunakan ID 0 atau nil untuk system action
        s.auditLogRepo.Log(nil, "auto_archive", "system", nil, details, ipAddress, userAgent)
    }

    return count, nil
}

// ✅ FIXED: CreateItem - NOW INCLUDES SecretDetails
func (s *ItemService) CreateItem(reporterID uint, req CreateItemRequest, ipAddress, userAgent string) (*models.Item, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var item *models.Item

	// ✅ TRANSACTION untuk create item + audit log
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// 1. Verify category exists
		var category models.Category
		if err := tx.Where("id = ? AND deleted_at IS NULL", req.CategoryID).
			First(&category).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errors.New("invalid category")
			}
			return fmt.Errorf("failed to verify category: %w", err)
		}

		// 2. Create item - ✅ SEKARANG INCLUDE SecretDetails
		item = &models.Item{
			Name:            req.Name,
			CategoryID:      req.CategoryID,
			PhotoURL:        req.PhotoURL,
			Location:        req.Location,
			Description:     req.Description,
			SecretDetails:   req.SecretDetails, // ✅ TAMBAHKAN INI
			DateFound:       req.DateFound,
			Status:          models.ItemStatusUnclaimed,
			ReporterID:      reporterID,
			ReporterName:    req.ReporterName,
			ReporterContact: req.ReporterContact,
		}

		if err := tx.Create(item).Error; err != nil {
			return fmt.Errorf("failed to create item: %w", err)
		}

		// 3. Create audit log
		auditLog := &models.AuditLog{
			UserID:     &reporterID,
			Action:     models.ActionCreate,
			EntityType: models.EntityItem,
			EntityID:   &item.ID,
			Details:    fmt.Sprintf("Item created: %s", item.Name),
			IPAddress:  ipAddress,
			UserAgent:  userAgent,
		}
		if err := tx.Create(auditLog).Error; err != nil {
			return fmt.Errorf("failed to create audit log: %w", err)
		}

		return nil
	})

	if err != nil {
		return nil, err
	}

	return item, nil
}

// ✅ FIXED: UpdateItem - NOW HANDLES SecretDetails
// internal/services/item_service.go

// UpdateItem updates an item with transaction, locking, and status change support
func (s *ItemService) UpdateItem(userID, itemID uint, req UpdateItemRequest, ipAddress, userAgent string) (*models.Item, error) {
	// ✅ Tambahkan logging
	log.Printf("🔍 UpdateItem Request:")
	log.Printf("   ItemID: %d", itemID)
	log.Printf("   Name: %s", req.Name)
	log.Printf("   CategoryID: %d", req.CategoryID)
	log.Printf("   Status: %s", req.Status)
	log.Printf("   Location: %s", req.Location)
	log.Printf("   Description: %s", req.Description)
	log.Printf("   SecretDetails: %s", req.SecretDetails)
	
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var updatedItem *models.Item

	// ✅ TRANSACTION + LOCKING untuk update item
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var item models.Item
		
		// 1. Ambil data User beserta Role-nya (FIXED: Preload Role)
		var user models.User
		if err := tx.Preload("Role").First(&user, userID).Error; err != nil {
			return fmt.Errorf("failed to get user: %w", err)
		}

		// 2. Lock item untuk mencegah race condition saat update
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Preload("Category").
			Where("id = ? AND deleted_at IS NULL", itemID).
			First(&item).Error; err != nil {
			return errors.New("item not found")
		}

		// 3. Cek Permission: Izinkan jika User adalah Owner ATAU Admin/Manager
		isOwner := item.ReporterID == userID
		isManagerOrAdmin := user.Role.Name == "admin" || user.Role.Name == "manager"

		if !isOwner && !isManagerOrAdmin {
			return errors.New("unauthorized to edit this item")
		}

		// 4. Validasi Status Item
		// - Case Closed tetap permanen (tidak bisa diedit siapapun)
		if item.Status == models.ItemStatusCaseClosed {
			return errors.New("cannot edit item with status: " + item.Status)
		}

		// - Expired hanya bisa diedit oleh Manager/Admin
		if !isManagerOrAdmin && item.IsExpired() {
			return errors.New("cannot edit expired item")
		}

		// Track changes for revision log
		revisionCreated := false

		// --- Update Fields Logics ---

		if req.Name != "" && req.Name != item.Name {
			revLog := &models.RevisionLog{
				ItemID:    itemID,
				UserID:    userID,
				FieldName: "name",
				OldValue:  item.Name,
				NewValue:  req.Name,
				Reason:    req.Reason,
			}
			if err := tx.Create(revLog).Error; err != nil {
				return fmt.Errorf("failed to create revision log: %w", err)
			}
			item.Name = req.Name
			revisionCreated = true
		}

		if req.CategoryID != 0 && req.CategoryID != item.CategoryID {
			// Verify new category exists
			var newCategory models.Category
			if err := tx.Where("id = ?", req.CategoryID).First(&newCategory).Error; err != nil {
				return errors.New("invalid category")
			}

			oldCatName := item.Category.Name
			revLog := &models.RevisionLog{
				ItemID:    itemID,
				UserID:    userID,
				FieldName: "category",
				OldValue:  oldCatName,
				NewValue:  newCategory.Name,
				Reason:    req.Reason,
			}
			if err := tx.Create(revLog).Error; err != nil {
				return fmt.Errorf("failed to create revision log: %w", err)
			}
			item.CategoryID = req.CategoryID
			revisionCreated = true
		}

		if req.Location != "" && req.Location != item.Location {
			revLog := &models.RevisionLog{
				ItemID:    itemID,
				UserID:    userID,
				FieldName: "location",
				OldValue:  item.Location,
				NewValue:  req.Location,
				Reason:    req.Reason,
			}
			if err := tx.Create(revLog).Error; err != nil {
				return fmt.Errorf("failed to create revision log: %w", err)
			}
			item.Location = req.Location
			revisionCreated = true
		}

		if req.Description != "" && req.Description != item.Description {
			revLog := &models.RevisionLog{
				ItemID:    itemID,
				UserID:    userID,
				FieldName: "description",
				OldValue:  item.Description,
				NewValue:  req.Description,
				Reason:    req.Reason,
			}
			if err := tx.Create(revLog).Error; err != nil {
				return fmt.Errorf("failed to create revision log: %w", err)
			}
			item.Description = req.Description
			revisionCreated = true
		}

		// Handle SecretDetails update
		if req.SecretDetails != "" && req.SecretDetails != item.SecretDetails {
			revLog := &models.RevisionLog{
				ItemID:    itemID,
				UserID:    userID,
				FieldName: "secret_details",
				OldValue:  item.SecretDetails,
				NewValue:  req.SecretDetails,
				Reason:    req.Reason,
			}
			if err := tx.Create(revLog).Error; err != nil {
				return fmt.Errorf("failed to create revision log: %w", err)
			}
			item.SecretDetails = req.SecretDetails
			revisionCreated = true
		}

		if req.PhotoURL != "" && req.PhotoURL != item.PhotoURL {
			revLog := &models.RevisionLog{
				ItemID:    itemID,
				UserID:    userID,
				FieldName: "photo_url",
				OldValue:  item.PhotoURL,
				NewValue:  req.PhotoURL,
				Reason:    req.Reason,
			}
			if err := tx.Create(revLog).Error; err != nil {
				return fmt.Errorf("failed to create revision log: %w", err)
			}
			item.PhotoURL = req.PhotoURL
			revisionCreated = true
		}

		// ✅ NEW: Handle Status Update (Khusus Manager/Admin)
		if req.Status != "" && req.Status != item.Status {
			// Validasi status yang diperbolehkan untuk di-set manual
			validStatuses := map[string]bool{
				models.ItemStatusUnclaimed:  true,
				models.ItemStatusVerified:   true,
				models.ItemStatusExpired:    true,
				models.ItemStatusCaseClosed: true,
			}

			if !validStatuses[req.Status] {
				return errors.New("invalid status value")
			}

			revLog := &models.RevisionLog{
				ItemID:    itemID,
				UserID:    userID,
				FieldName: "status",
				OldValue:  item.Status,
				NewValue:  req.Status,
				Reason:    req.Reason,
			}
			if err := tx.Create(revLog).Error; err != nil {
				return fmt.Errorf("failed to create revision log for status: %w", err)
			}
			item.Status = req.Status
			revisionCreated = true
		}

		if !revisionCreated {
			return errors.New("no changes detected")
		}

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

		// Create audit log
		auditLog := &models.AuditLog{
			UserID:     &userID,
			Action:     models.ActionUpdate,
			EntityType: models.EntityItem,
			EntityID:   &itemID,
			Details:    fmt.Sprintf("Item updated: %s (Reason: %s)", item.Name, req.Reason),
			IPAddress:  ipAddress,
			UserAgent:  userAgent,
		}
		if err := tx.Create(auditLog).Error; err != nil {
			return fmt.Errorf("failed to create audit log: %w", err)
		}

		updatedItem = &item
		return nil
	})

	if err != nil {
		return nil, err
	}

	return updatedItem, nil
}

// UpdateItemStatus updates item status with TRANSACTION
func (s *ItemService) UpdateItemStatus(userID, itemID uint, status string, ipAddress, userAgent string) error {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

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

		// Update status
		if err := tx.Model(&item).Update("status", status).Error; err != nil {
			return fmt.Errorf("failed to update status: %w", err)
		}

		// Create audit log
		auditLog := &models.AuditLog{
			UserID:     &userID,
			Action:     models.ActionUpdate,
			EntityType: models.EntityItem,
			EntityID:   &itemID,
			Details:    fmt.Sprintf("Item status updated to: %s", status),
			IPAddress:  ipAddress,
			UserAgent:  userAgent,
		}
		if err := tx.Create(auditLog).Error; err != nil {
			return fmt.Errorf("failed to create audit log: %w", err)
		}

		return nil
	})
}

// DeleteItem deletes an item with TRANSACTION
func (s *ItemService) DeleteItem(userID, itemID uint, ipAddress, userAgent string) error {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
        // 1. Ambil data User yang sedang request (untuk cek Role)
        var user models.User
        if err := tx.Preload("Role").First(&user, userID).Error; err != nil {
            return errors.New("user not found")
        }

        // 2. Lock item
        var item models.Item
        if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
            Where("id = ?", itemID).
            First(&item).Error; err != nil {
            return fmt.Errorf("failed to lock item: %w", err)
        }

        // 3. PERMISSION CHECK: Izinkan jika Owner ATAU Manager/Admin
        isOwner := item.ReporterID == userID
        isManagerOrAdmin := user.Role.Name == "admin" || user.Role.Name == "manager"

        if !isOwner && !isManagerOrAdmin {
            return errors.New("unauthorized to delete this item")
        }

        // 4. ✅ (BARU) Validasi Active Claims - PENGGANTI TRIGGER trg_items_before_delete
        // Cek apakah ada claims dengan status 'pending' untuk item ini
        var activeClaims int64
					if err := tx.Model(&models.Claim{}).
						Where("item_id = ? AND status IN ? AND deleted_at IS NULL", 
							itemID, []string{models.ClaimStatusPending, models.ClaimStatusWaitingOwner}).
						Count(&activeClaims).Error; err != nil {
						return fmt.Errorf("failed to check active claims: %w", err)
					}

					if activeClaims > 0 {
						return errors.New("cannot delete item with active claims (pending or waiting owner)")
					}

					if item.Status == models.ItemStatusVerified || item.Status == models.ItemStatusCaseClosed {
						return errors.New("cannot delete item with status: " + item.Status)
					}

					if err := tx.Delete(&item).Error; err != nil {
						return fmt.Errorf("failed to delete item: %w", err)
					}

        // 7. Create audit log
        auditLog := &models.AuditLog{
            UserID:     &userID,
            Action:     models.ActionDelete,
            EntityType: models.EntityItem,
            EntityID:   &itemID,
            Details:    fmt.Sprintf("Item deleted: %s", item.Name),
            IPAddress:  ipAddress,
            UserAgent:  userAgent,
        }
        if err := tx.Create(auditLog).Error; err != nil {
            return fmt.Errorf("failed to create audit log: %w", err)
        }

        return nil
    })
}

// GetItemsByReporter gets items by reporter with CONTEXT
func (s *ItemService) GetItemsByReporter(reporterID uint, page, limit int) ([]models.Item, int64, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	txRepo := repositories.NewItemRepository(s.db.WithContext(ctx))
	return txRepo.FindByReporter(reporterID, page, limit)
}

// GetItemRevisionHistory gets revision history with CONTEXT
func (s *ItemService) GetItemRevisionHistory(itemID uint, page, limit int) ([]models.RevisionLogResponse, int64, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	txRepo := repositories.NewRevisionLogRepository(s.db.WithContext(ctx))
	logs, total, err := txRepo.FindByItem(itemID, page, limit)
	if err != nil {
		if ctx.Err() == context.DeadlineExceeded {
			return nil, 0, errors.New("request timeout")
		}
		return nil, 0, err
	}

	var responses []models.RevisionLogResponse
	for _, log := range logs {
		responses = append(responses, log.ToResponse())
	}

	return responses, total, nil
}