package services

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/google/uuid"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"veza-backend-api/internal/models"
)

// TrackHistoryService gère l'historique des modifications de tracks
type TrackHistoryService struct {
	db     *gorm.DB
	logger *zap.Logger
}

// NewTrackHistoryService crée un nouveau service d'historique de tracks
func NewTrackHistoryService(db *gorm.DB, logger *zap.Logger) *TrackHistoryService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &TrackHistoryService{
		db:     db,
		logger: logger,
	}
}

// RecordHistoryParams représente les paramètres pour enregistrer un historique
// MIGRATION UUID: UserID et TrackID en UUID
type RecordHistoryParams struct {
	TrackID  uuid.UUID
	UserID   uuid.UUID
	Action   models.TrackHistoryAction
	OldValue interface{} // Peut être n'importe quel type, sera sérialisé en JSON
	NewValue interface{} // Peut être n'importe quel type, sera sérialisé en JSON
}

// RecordHistory enregistre une entrée dans l'historique d'un track
func (s *TrackHistoryService) RecordHistory(ctx context.Context, params RecordHistoryParams) (*models.TrackHistory, error) {
	// Vérifier que le track existe
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", params.TrackID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrTrackNotFound
		}
		return nil, fmt.Errorf("failed to get track: %w", err)
	}

	// Sérialiser old_value et new_value en JSON si nécessaire
	var oldValueStr string
	var newValueStr string

	if params.OldValue != nil {
		oldValueBytes, err := json.Marshal(params.OldValue)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal old_value: %w", err)
		}
		oldValueStr = string(oldValueBytes)
	}

	if params.NewValue != nil {
		newValueBytes, err := json.Marshal(params.NewValue)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal new_value: %w", err)
		}
		newValueStr = string(newValueBytes)
	}

	// Créer l'entrée d'historique
	// FIXME: models.TrackHistory needs UUID too if not updated
	history := &models.TrackHistory{
		TrackID:  params.TrackID, // Assuming UUID
		UserID:   params.UserID,
		Action:   params.Action,
		OldValue: oldValueStr,
		NewValue: newValueStr,
	}

	if err := s.db.WithContext(ctx).Create(history).Error; err != nil {
		return nil, fmt.Errorf("failed to create track history: %w", err)
	}

	s.logger.Info("Track history recorded",
		zap.String("track_id", params.TrackID.String()),
		zap.String("user_id", params.UserID.String()),
		zap.String("action", string(params.Action)),
		zap.String("history_id", history.ID.String()),
	)

	return history, nil
}

// GetHistory récupère l'historique d'un track
func (s *TrackHistoryService) GetHistory(ctx context.Context, trackID uuid.UUID, limit, offset int) ([]models.TrackHistory, int64, error) {
	// Vérifier que le track existe
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, 0, ErrTrackNotFound
		}
		return nil, 0, fmt.Errorf("failed to get track: %w", err)
	}

	// Compter le total d'entrées
	var total int64
	if err := s.db.WithContext(ctx).Model(&models.TrackHistory{}).
		Where("track_id = ?", trackID).
		Count(&total).Error; err != nil {
		return nil, 0, fmt.Errorf("failed to count track history: %w", err)
	}

	// Récupérer les entrées avec pagination
	var histories []models.TrackHistory
	query := s.db.WithContext(ctx).
		Where("track_id = ?", trackID).
		Order("created_at DESC")

	if limit > 0 {
		query = query.Limit(limit)
	}
	if offset > 0 {
		query = query.Offset(offset)
	}

	if err := query.Find(&histories).Error; err != nil {
		return nil, 0, fmt.Errorf("failed to get track history: %w", err)
	}

	return histories, total, nil
}

// GetHistoryByUser récupère l'historique des tracks modifiés par un utilisateur
func (s *TrackHistoryService) GetHistoryByUser(ctx context.Context, userID uuid.UUID, limit, offset int) ([]models.TrackHistory, int64, error) {
	// Compter le total d'entrées
	var total int64
	if err := s.db.WithContext(ctx).Model(&models.TrackHistory{}).
		Where("user_id = ?", userID).
		Count(&total).Error; err != nil {
		return nil, 0, fmt.Errorf("failed to count user track history: %w", err)
	}

	// Récupérer les entrées avec pagination
	var histories []models.TrackHistory
	query := s.db.WithContext(ctx).
		Where("user_id = ?", userID).
		Order("created_at DESC")

	if limit > 0 {
		query = query.Limit(limit)
	}
	if offset > 0 {
		query = query.Offset(offset)
	}

	if err := query.Find(&histories).Error; err != nil {
		return nil, 0, fmt.Errorf("failed to get user track history: %w", err)
	}

	return histories, total, nil
}

// GetHistoryByAction récupère l'historique filtré par action
func (s *TrackHistoryService) GetHistoryByAction(ctx context.Context, trackID uuid.UUID, action models.TrackHistoryAction, limit, offset int) ([]models.TrackHistory, int64, error) {
	// Vérifier que le track existe
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, "id = ?", trackID).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, 0, ErrTrackNotFound
		}
		return nil, 0, fmt.Errorf("failed to get track: %w", err)
	}

	// Compter le total d'entrées
	var total int64
	if err := s.db.WithContext(ctx).Model(&models.TrackHistory{}).
		Where("track_id = ? AND action = ?", trackID, action).
		Count(&total).Error; err != nil {
		return nil, 0, fmt.Errorf("failed to count track history by action: %w", err)
	}

	// Récupérer les entrées avec pagination
	var histories []models.TrackHistory
	query := s.db.WithContext(ctx).
		Where("track_id = ? AND action = ?", trackID, action).
		Order("created_at DESC")

	if limit > 0 {
		query = query.Limit(limit)
	}
	if offset > 0 {
		query = query.Offset(offset)
	}

	if err := query.Find(&histories).Error; err != nil {
		return nil, 0, fmt.Errorf("failed to get track history by action: %w", err)
	}

	return histories, total, nil
}
