// internal/repositories/item_repo.go
package repositories

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

	"gorm.io/gorm"
)

type ItemRepository struct {
	db *gorm.DB
}

func NewItemRepository(db *gorm.DB) *ItemRepository {
	return &ItemRepository{db: db}
}

// Create creates a new item
func (r *ItemRepository) Create(item *models.Item) error {
	return r.db.Create(item).Error
}

// FindByID finds item by ID
func (r *ItemRepository) FindByID(id uint) (*models.Item, error) {
	var item models.Item
	err := r.db.
		Preload("Category").
		Preload("Reporter").
		Preload("Reporter.Role").
		Preload("CaseClosedBy_User").
		Preload("CaseClosedBy_User.Role").
		Preload("Claims", "deleted_at IS NULL"). // ✅ TAMBAH INI!
		First(&item, id).Error
		
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("item not found")
		}
		return nil, err
	}

	return &item, nil
}

// ✅ IMPLEMENTASI PROCEDURE 1: Archive Expired Items
// CallArchiveExpiredProcedure memanggil SP sp_archive_expired_items
func (r *ItemRepository) CallArchiveExpiredProcedure() (int, error) {
	var archivedCount int

	// Menggunakan transaksi untuk eksekusi procedure
	err := r.db.Transaction(func(tx *gorm.DB) error {
		// 1. Eksekusi Procedure dengan variabel output session MySQL (@count)
		if err := tx.Exec("CALL sp_archive_expired_items(@count)").Error; err != nil {
			return err
		}

		// 2. Ambil nilai dari variabel output
		// Kita menggunakan Raw SQL karena GORM tidak support OUT param secara native di semua driver
		type Result struct {
			Count int
		}
		var res Result
		if err := tx.Raw("SELECT @count as count").Scan(&res).Error; err != nil {
			return err
		}
		archivedCount = res.Count
		return nil
	})

	return archivedCount, err
}

// ✅ IMPLEMENTASI PROCEDURE 2: Dashboard Stats
// GetDashboardStatsSP memanggil SP sp_get_dashboard_stats
func (r *ItemRepository) GetDashboardStatsSP() (map[string]int64, error) {
	stats := make(map[string]int64)

	err := r.db.Transaction(func(tx *gorm.DB) error {
		// 1. Eksekusi Procedure dengan 4 variabel output
		query := "CALL sp_get_dashboard_stats(@total, @unclaimed, @verified, @pending)"
		if err := tx.Exec(query).Error; err != nil {
			return err
		}

		// 2. Select nilai variabel tersebut
		type Result struct {
			Total     int64
			Unclaimed int64
			Verified  int64
			Pending   int64
		}
		var res Result
		querySelect := "SELECT @total as total, @unclaimed as unclaimed, @verified as verified, @pending as pending"
		
		if err := tx.Raw(querySelect).Scan(&res).Error; err != nil {
			return err
		}

		stats["total_items"] = res.Total
		stats["unclaimed_items"] = res.Unclaimed
		stats["verified_items"] = res.Verified
		stats["pending_claims"] = res.Pending
		
		return nil
	})

	return stats, err
}

// FindAll returns all items with filters
// internal/repositories/item_repo.go

func (r *ItemRepository) FindAll(page, limit int, status, category, search string) ([]models.Item, int64, error) {
	var items []models.Item
	var total int64

	query := r.db.Model(&models.Item{})

	if status != "" {
		if status == "!expired" {
			query = query.Where("status NOT IN ?", []string{models.ItemStatusExpired, models.ItemStatusCaseClosed})
		} else {
			query = query.Where("status = ?", status)
		}
	}

	if category != "" {
		query = query.Joins("JOIN categories ON categories.id = items.category_id").Where("categories.slug = ?", category)
	}

	if search != "" {
		query = query.Where("name LIKE ? OR description LIKE ?", "%"+search+"%", "%"+search+"%")
	}

	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	offset := (page - 1) * limit
	
	// ✅ FIX: Tambahkan Preload Claims untuk hitung status dinamis
	err := query.
		Preload("Category").
		Preload("Reporter").
		Preload("Reporter.Role").
		Preload("Claims", "deleted_at IS NULL"). // ✅ TAMBAH INI!
		Order("date_found DESC").
		Offset(offset).
		Limit(limit).
		Find(&items).Error

	if err != nil {
		return nil, 0, err
	}

	return items, total, nil
}

// Update updates item data
func (r *ItemRepository) Update(item *models.Item) error {
	return r.db.Save(item).Error
}

// UpdateStatus updates item status
func (r *ItemRepository) UpdateStatus(id uint, status string) error {
	return r.db.Model(&models.Item{}).Where("id = ?", id).Update("status", status).Error
}

// Delete soft deletes an item
func (r *ItemRepository) Delete(id uint) error {
	return r.db.Delete(&models.Item{}, id).Error
}

// FindExpired finds expired items
func (r *ItemRepository) FindExpired() ([]models.Item, error) {
	var items []models.Item
	now := time.Now()
	err := r.db.Where("expires_at <= ? AND status = ?", now, models.ItemStatusUnclaimed).
		Preload("Category").Find(&items).Error
	return items, err
}

// ArchiveItem moves item to archive
func (r *ItemRepository) ArchiveItem(item *models.Item, reason string, claimedBy *uint) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Create archive record
		archive := models.CreateFromItem(item, reason, claimedBy)
		if err := tx.Create(archive).Error; err != nil {
			return err
		}

		// Update item status
		if err := tx.Model(item).Updates(map[string]interface{}{
			"status": models.ItemStatusExpired,
		}).Error; err != nil {
			return err
		}

		return nil
	})
}

// CountByStatus counts items by status
func (r *ItemRepository) CountByStatus(status string) (int64, error) {
	var count int64
	err := r.db.Model(&models.Item{}).Where("status = ?", status).Count(&count).Error
	return count, err
}

// FindByReporter finds items by reporter ID
func (r *ItemRepository) FindByReporter(reporterID uint, page, limit int) ([]models.Item, int64, error) {
	var items []models.Item
	var total int64

	query := r.db.Model(&models.Item{}).Where("reporter_id = ?", reporterID)

	// Count total
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	// Get paginated results
	offset := (page - 1) * limit
	err := query.Preload("Category").Order("date_found DESC").
		Offset(offset).Limit(limit).Find(&items).Error
	if err != nil {
		return nil, 0, err
	}

	return items, total, nil
}

// SearchForMatching searches items for matching with lost items
func (r *ItemRepository) SearchForMatching(categoryID uint, name, color string) ([]models.Item, error) {
	var items []models.Item
	
	query := r.db.Where("status = ? AND category_id = ?", models.ItemStatusUnclaimed, categoryID)
	
	if name != "" {
		query = query.Where("name ILIKE ?", "%"+name+"%")
	}
	
	err := query.Preload("Category").Order("date_found DESC").Limit(10).Find(&items).Error
	return items, err
}

func (r *ItemRepository) CountAll() (int64, error) {
    var count int64
    err := r.db.Model(&models.Item{}).Count(&count).Error
    return count, err
}

