package services

import (
	"errors"
	"lost-and-found/internal/models"
	"lost-and-found/internal/repositories"
	"time"
	"fmt"
	"gorm.io/gorm"
)

type LostItemService struct {
	db               *gorm.DB
	lostItemRepo     *repositories.LostItemRepository
	categoryRepo     *repositories.CategoryRepository
	auditLogRepo     *repositories.AuditLogRepository
	userRepo         *repositories.UserRepository
	notificationRepo *repositories.NotificationRepository
	claimRepo        *repositories.ClaimRepository
}

func NewLostItemService(db *gorm.DB) *LostItemService {
	return &LostItemService{
		db:               db,
		lostItemRepo:     repositories.NewLostItemRepository(db),
		categoryRepo:     repositories.NewCategoryRepository(db),
		auditLogRepo:     repositories.NewAuditLogRepository(db),
		userRepo:         repositories.NewUserRepository(db),
		notificationRepo: repositories.NewNotificationRepository(db),
		claimRepo:        repositories.NewClaimRepository(db),
	}
}

type CreateLostItemRequest struct {
	Name        string    `json:"name" binding:"required"`
	CategoryID  uint      `json:"category_id" binding:"required"`
	Color       string    `json:"color"`
	Location    string    `json:"location"`
	Description string    `json:"description" binding:"required"`
	DateLost    time.Time `json:"date_lost" binding:"required"`
}

type UpdateLostItemRequest struct {
	Name        string    `json:"name"`
	CategoryID  uint      `json:"category_id"`
	Color       string    `json:"color"`
	Location    string    `json:"location"`
	Description string    `json:"description"`
	DateLost    time.Time `json:"date_lost"`
}

type CreateLostItemClaimRequest struct {
	Description string `json:"description" binding:"required"`
	Contact     string `json:"contact" binding:"required"`
	ProofURL    string `json:"proof_url"`
}

func (s *LostItemService) GetAllLostItems(page, limit int, status, category, search string, userID *uint) ([]models.LostItemResponse, int64, error) {
	lostItems, total, err := s.lostItemRepo.FindAll(page, limit, status, category, search, userID)
	if err != nil {
		return nil, 0, err
	}

	var responses []models.LostItemResponse
	for _, lostItem := range lostItems {
		response := lostItem.ToResponse()
		
		if lostItem.DirectClaimID != nil {
			claim, err := s.claimRepo.FindByID(*lostItem.DirectClaimID)
			if err == nil {
				response.DirectClaimStatus = claim.Status
			}
		}
		
		responses = append(responses, response)
	}
	return responses, total, nil
}

func (s *LostItemService) GetLostItemByID(id uint) (*models.LostItem, error) {
	lostItem, err := s.lostItemRepo.FindByID(id)
	if err != nil {
		return nil, err
	}
	
	if lostItem.DirectClaimID != nil {
		claim, err := s.claimRepo.FindByID(*lostItem.DirectClaimID)
		if err == nil {
			lostItem.DirectClaim = claim
		}
	}
	
	return lostItem, nil
}

func (s *LostItemService) CreateLostItem(userID uint, req CreateLostItemRequest, ipAddress, userAgent string) (*models.LostItem, error) {
	if _, err := s.categoryRepo.FindByID(req.CategoryID); err != nil {
		return nil, errors.New("invalid category")
	}

	lostItem := &models.LostItem{
		UserID:      userID,
		Name:        req.Name,
		CategoryID:  req.CategoryID,
		Color:       req.Color,
		Location:    req.Location,
		Description: req.Description,
		DateLost:    req.DateLost,
		Status:      models.LostItemStatusActive,
	}

	if err := s.lostItemRepo.Create(lostItem); err != nil {
		return nil, errors.New("failed to create lost item report")
	}

	s.auditLogRepo.Log(&userID, models.ActionCreate, models.EntityLostItem, &lostItem.ID,
		"Lost item report created: "+lostItem.Name, ipAddress, userAgent)

	return s.lostItemRepo.FindByID(lostItem.ID)
}

func (s *LostItemService) UpdateLostItem(userID, lostItemID uint, req UpdateLostItemRequest, ipAddress, userAgent string) (*models.LostItem, error) {
	lostItem, err := s.lostItemRepo.FindByID(lostItemID)
	if err != nil {
		return nil, err
	}

	// Cek authorization
	user, err := s.userRepo.FindByID(userID)
	if err != nil {
		return nil, errors.New("user not found")
	}

	isOwner := lostItem.UserID == userID
	isAdminOrManager := user.Role.Name == models.RoleAdmin || user.Role.Name == models.RoleManager
	
	if !isOwner && !isAdminOrManager {
		return nil, errors.New("unauthorized to update this lost item report")
	}

	if !isAdminOrManager && !lostItem.IsActive() {
		return nil, errors.New("cannot update non-active lost item report")
	}

	// ✅ UPDATE: Prioritaskan category_id, update terlebih dahulu
	if req.CategoryID != 0 {
		if _, err := s.categoryRepo.FindByID(req.CategoryID); err != nil {
			return nil, errors.New("invalid category")
		}
		// LOG untuk debug
		fmt.Printf("📝 Updating category from %d to %d\n", lostItem.CategoryID, req.CategoryID)
		lostItem.CategoryID = req.CategoryID
	}

	if req.Name != "" {
		lostItem.Name = req.Name
	}
	if req.Color != "" {
		lostItem.Color = req.Color
	}
	if req.Location != "" {
		lostItem.Location = req.Location
	}
	if req.Description != "" {
		lostItem.Description = req.Description
	}
	if !req.DateLost.IsZero() {
		lostItem.DateLost = req.DateLost
	}

	// ✅ PASTIKAN update tersimpan
	if err := s.lostItemRepo.Update(lostItem); err != nil {
		fmt.Printf("❌ Error updating lost item: %v\n", err)
		return nil, errors.New("failed to update lost item report")
	}

	actionDesc := fmt.Sprintf("Lost item report updated: %s", lostItem.Name)
	if !isOwner {
		actionDesc = fmt.Sprintf("Lost item report updated by %s: %s", user.Role.Name, lostItem.Name)
	}
	
	s.auditLogRepo.Log(&userID, models.ActionUpdate, models.EntityLostItem, &lostItemID,
		actionDesc, ipAddress, userAgent)

	// ✅ RELOAD dari database untuk memastikan data terbaru
	return s.lostItemRepo.FindByID(lostItem.ID)
}

func (s *LostItemService) UpdateLostItemStatus(userID, lostItemID uint, status string, ipAddress, userAgent string) error {
	lostItem, err := s.lostItemRepo.FindByID(lostItemID)
	if err != nil {
		return err
	}

	// TAMBAHKAN: Cek admin/manager permission
	user, err := s.userRepo.FindByID(userID)
	if err != nil {
		return errors.New("user not found")
	}

	isOwner := lostItem.UserID == userID
	isAdminOrManager := user.Role.Name == models.RoleAdmin || user.Role.Name == models.RoleManager
	
	if !isOwner && !isAdminOrManager {
		return errors.New("unauthorized to update this lost item report")
	}

	if err := s.lostItemRepo.UpdateStatus(lostItemID, status); err != nil {
		return errors.New("failed to update lost item status")
	}

	s.auditLogRepo.Log(&userID, models.ActionUpdate, models.EntityLostItem, &lostItemID,
		"Lost item status updated to: "+status, ipAddress, userAgent)

	return nil
}

func (s *LostItemService) DeleteLostItem(userID, lostItemID uint, ipAddress, userAgent string) error {
	lostItem, err := s.lostItemRepo.FindByID(lostItemID)
	if err != nil {
		return err
	}

	user, err := s.userRepo.FindByID(userID)
	if err != nil {
		return errors.New("user not found")
	}

	isOwner := lostItem.UserID == userID
	isManagerOrAdmin := user.Role.Name == models.RoleAdmin || user.Role.Name == models.RoleManager

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

	if err := s.lostItemRepo.Delete(lostItemID); err != nil {
		return errors.New("failed to delete lost item report")
	}

	s.auditLogRepo.Log(&userID, models.ActionDelete, models.EntityLostItem, &lostItemID, "Lost item report deleted: "+lostItem.Name, ipAddress, userAgent)

	return nil
}

func (s *LostItemService) GetLostItemsByUser(userID uint, page, limit int) ([]models.LostItemResponse, int64, error) {
	lostItems, total, err := s.lostItemRepo.FindByUser(userID, page, limit)
	if err != nil {
		return nil, 0, err
	}

	var responses []models.LostItemResponse
	for _, lostItem := range lostItems {
		response := lostItem.ToResponse()
		
		// ✅ PERBAIKAN: Load data claim lengkap jika ada direct claim
		if lostItem.DirectClaimID != nil {
			claim, err := s.claimRepo.FindByID(*lostItem.DirectClaimID)
			if err == nil {
                // Load relasi user penemu agar namanya muncul di frontend
				s.db.Preload("User").First(claim, claim.ID)
                
				response.DirectClaimStatus = claim.Status
                
                // Konversi claim ke response dan tempelkan ke lost item response
                claimResp := claim.ToResponse()
                response.DirectClaim = &claimResp // Pastikan struct LostItemResponse punya field ini
			}
		}
		
		responses = append(responses, response)
	}
	return responses, total, nil
}

func (s *LostItemService) DirectClaimToOwner(
    finderUserID uint, 
    lostItemID uint, 
    req CreateLostItemClaimRequest,
    ipAddress string, // Tambahkan ini agar audit log valid
    userAgent string, // Tambahkan ini agar audit log valid
) (*models.Claim, error) {
    var createdClaim *models.Claim
    
    err := s.db.Transaction(func(tx *gorm.DB) error {
        // 1. Get Lost Item
        var lostItem models.LostItem
        if err := tx.First(&lostItem, lostItemID).Error; err != nil {
            return errors.New("lost item not found")
        }

        // 2. Validasi
        if lostItem.Status != models.LostItemStatusActive {
            return errors.New("lost item is not active")
        }

        if lostItem.UserID == finderUserID {
            return errors.New("cannot claim your own lost item")
        }

        if lostItem.DirectClaimID != nil {
            return errors.New("this lost item already has a pending claim")
        }

        // 3. Create Claim (LOGIC OPSI A)
        // Kita set ItemID nil, tapi isi LostItemID
        claim := models.Claim{
            ItemID:      nil,          // PENTING: Set nil (jangan 0)
            LostItemID:  &lostItemID,  // PENTING: Link ke LostItem
            UserID:      finderUserID,
            Description: req.Description,
            Contact:     req.Contact,
            ProofURL:    req.ProofURL,
            Status:      models.ClaimStatusWaitingOwner,
            Notes:       fmt.Sprintf("Direct claim for lost item #%d", lostItemID),
        }

        if err := tx.Create(&claim).Error; err != nil {
            return err
        }
        
        createdClaim = &claim

        // 4. Update Lost Item Status
        // Status diganti agar tidak muncul di pencarian publik sementara waktu
        // Pastikan constant ini ada di models, atau gunakan string "pending_verification"
        lostItem.Status = "pending_verification" 
        lostItem.DirectClaimID = &claim.ID

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

        // 5. Notification to Owner
        s.notificationRepo.Notify(
            lostItem.UserID,
            "direct_claim_received",
            "🎯 Barang Anda Ditemukan?",
            fmt.Sprintf("Seseorang mengklaim menemukan '%s'. Silakan verifikasi klaim ini.", lostItem.Name),
            "claim",   // Entity Type arahkan ke claim
            &claim.ID, // Entity ID arahkan ke claim ID agar owner bisa klik detail claim
        )

        // 6. Audit Log
        s.auditLogRepo.Log(
            &finderUserID,
            "create_direct_claim",
            "claim",
            &claim.ID,
            fmt.Sprintf("Direct claim created for lost item #%d", lostItemID),
            ipAddress,
            userAgent,
        )

        return nil
    })
    
    if err != nil {
        return nil, err
    }
    
    return createdClaim, nil
}