package education

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"
)

// Tutorial représente un tutoriel vidéo
type Tutorial struct {
	ID          string        `json:"id"`
	Title       string        `json:"title"`
	Description string        `json:"description"`
	Author      string        `json:"author"`
	Category    string        `json:"category"`
	Tags        []string      `json:"tags"`
	VideoURL    string        `json:"video_url"`
	Thumbnail   string        `json:"thumbnail"`
	Duration    time.Duration `json:"duration"`
	Quality     VideoQuality  `json:"quality"`
	Language    string        `json:"language"`
	IsFree      bool          `json:"is_free"`
	IsPublished bool          `json:"is_published"`
	Views       int64         `json:"views"`
	Likes       int64         `json:"likes"`
	Dislikes    int64         `json:"dislikes"`
	Rating      float64       `json:"rating"`
	CreatedAt   time.Time     `json:"created_at"`
	UpdatedAt   time.Time     `json:"updated_at"`
	mu          sync.RWMutex
}

// VideoQuality définit la qualité de la vidéo
type VideoQuality string

const (
	VideoQualityHD VideoQuality = "hd"
	VideoQuality4K VideoQuality = "4k"
	VideoQuality8K VideoQuality = "8k"
)

// TutorialStep représente une étape dans un tutoriel
type TutorialStep struct {
	ID          string        `json:"id"`
	TutorialID  string        `json:"tutorial_id"`
	Title       string        `json:"title"`
	Description string        `json:"description"`
	Content     string        `json:"content"`
	Order       int           `json:"order"`
	Timestamp   time.Duration `json:"timestamp"`
	IsFree      bool          `json:"is_free"`
	CreatedAt   time.Time     `json:"created_at"`
	UpdatedAt   time.Time     `json:"updated_at"`
}

// TutorialComment représente un commentaire sur un tutoriel
type TutorialComment struct {
	ID         string    `json:"id"`
	TutorialID string    `json:"tutorial_id"`
	UserID     string    `json:"user_id"`
	Username   string    `json:"username"`
	Content    string    `json:"content"`
	Rating     int       `json:"rating"` // 1-5 étoiles
	IsHelpful  bool      `json:"is_helpful"`
	CreatedAt  time.Time `json:"created_at"`
	UpdatedAt  time.Time `json:"updated_at"`
}

// TutorialManager gère les tutoriels vidéo
type TutorialManager struct {
	tutorials map[string]*Tutorial
	steps     map[string][]*TutorialStep
	comments  map[string][]*TutorialComment
	logger    *zap.Logger
	mu        sync.RWMutex
}

// NewTutorialManager crée un nouveau gestionnaire de tutoriels
func NewTutorialManager(logger *zap.Logger) *TutorialManager {
	return &TutorialManager{
		tutorials: make(map[string]*Tutorial),
		steps:     make(map[string][]*TutorialStep),
		comments:  make(map[string][]*TutorialComment),
		logger:    logger,
	}
}

// CreateTutorial crée un nouveau tutoriel
func (tm *TutorialManager) CreateTutorial(ctx context.Context, title, description, author, category, videoURL, thumbnail, language string, duration time.Duration, quality VideoQuality, isFree bool, tags []string) (*Tutorial, error) {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	tutorialID := uuid.New().String()

	tutorial := &Tutorial{
		ID:          tutorialID,
		Title:       title,
		Description: description,
		Author:      author,
		Category:    category,
		Tags:        tags,
		VideoURL:    videoURL,
		Thumbnail:   thumbnail,
		Duration:    duration,
		Quality:     quality,
		Language:    language,
		IsFree:      isFree,
		IsPublished: false,
		Views:       0,
		Likes:       0,
		Dislikes:    0,
		Rating:      0.0,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	tm.tutorials[tutorialID] = tutorial

	tm.logger.Info("Tutoriel créé",
		zap.String("tutorial_id", tutorialID),
		zap.String("title", title),
		zap.String("author", author))

	return tutorial, nil
}

// GetTutorial récupère un tutoriel par son ID
func (tm *TutorialManager) GetTutorial(ctx context.Context, tutorialID string) (*Tutorial, error) {
	tm.mu.RLock()
	defer tm.mu.RUnlock()

	tutorial, exists := tm.tutorials[tutorialID]
	if !exists {
		return nil, fmt.Errorf("tutoriel non trouvé: %s", tutorialID)
	}

	return tutorial, nil
}

// ListTutorials liste tous les tutoriels disponibles
func (tm *TutorialManager) ListTutorials(ctx context.Context, filters map[string]interface{}) ([]*Tutorial, error) {
	tm.mu.RLock()
	defer tm.mu.RUnlock()

	var tutorials []*Tutorial
	for _, tutorial := range tm.tutorials {
		// Appliquer les filtres si fournis
		if filters != nil {
			if category, ok := filters["category"].(string); ok && tutorial.Category != category {
				continue
			}
			if isPublished, ok := filters["is_published"].(bool); ok && tutorial.IsPublished != isPublished {
				continue
			}
			if isFree, ok := filters["is_free"].(bool); ok && tutorial.IsFree != isFree {
				continue
			}
			if language, ok := filters["language"].(string); ok && tutorial.Language != language {
				continue
			}
			if author, ok := filters["author"].(string); ok && tutorial.Author != author {
				continue
			}
		}
		tutorials = append(tutorials, tutorial)
	}

	return tutorials, nil
}

// UpdateTutorial met à jour un tutoriel
func (tm *TutorialManager) UpdateTutorial(ctx context.Context, tutorialID string, updates map[string]interface{}) (*Tutorial, error) {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	tutorial, exists := tm.tutorials[tutorialID]
	if !exists {
		return nil, fmt.Errorf("tutoriel non trouvé: %s", tutorialID)
	}

	// Appliquer les mises à jour
	if title, ok := updates["title"].(string); ok {
		tutorial.Title = title
	}
	if description, ok := updates["description"].(string); ok {
		tutorial.Description = description
	}
	if author, ok := updates["author"].(string); ok {
		tutorial.Author = author
	}
	if category, ok := updates["category"].(string); ok {
		tutorial.Category = category
	}
	if videoURL, ok := updates["video_url"].(string); ok {
		tutorial.VideoURL = videoURL
	}
	if thumbnail, ok := updates["thumbnail"].(string); ok {
		tutorial.Thumbnail = thumbnail
	}
	if duration, ok := updates["duration"].(time.Duration); ok {
		tutorial.Duration = duration
	}
	if quality, ok := updates["quality"].(VideoQuality); ok {
		tutorial.Quality = quality
	}
	if isPublished, ok := updates["is_published"].(bool); ok {
		tutorial.IsPublished = isPublished
	}
	if tags, ok := updates["tags"].([]string); ok {
		tutorial.Tags = tags
	}

	tutorial.UpdatedAt = time.Now()

	tm.logger.Info("Tutoriel mis à jour",
		zap.String("tutorial_id", tutorialID),
		zap.String("title", tutorial.Title))

	return tutorial, nil
}

// DeleteTutorial supprime un tutoriel
func (tm *TutorialManager) DeleteTutorial(ctx context.Context, tutorialID string) error {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	if _, exists := tm.tutorials[tutorialID]; !exists {
		return fmt.Errorf("tutoriel non trouvé: %s", tutorialID)
	}

	delete(tm.tutorials, tutorialID)
	delete(tm.steps, tutorialID)
	delete(tm.comments, tutorialID)

	tm.logger.Info("Tutoriel supprimé",
		zap.String("tutorial_id", tutorialID))

	return nil
}

// AddTutorialStep ajoute une étape à un tutoriel
func (tm *TutorialManager) AddTutorialStep(ctx context.Context, tutorialID, title, description, content string, order int, timestamp time.Duration, isFree bool) (*TutorialStep, error) {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	stepID := uuid.New().String()
	step := &TutorialStep{
		ID:          stepID,
		TutorialID:  tutorialID,
		Title:       title,
		Description: description,
		Content:     content,
		Order:       order,
		Timestamp:   timestamp,
		IsFree:      isFree,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	tm.steps[tutorialID] = append(tm.steps[tutorialID], step)

	tm.logger.Info("Étape de tutoriel ajoutée",
		zap.String("tutorial_id", tutorialID),
		zap.String("step_id", stepID),
		zap.String("title", title))

	return step, nil
}

// GetTutorialSteps récupère toutes les étapes d'un tutoriel
func (tm *TutorialManager) GetTutorialSteps(ctx context.Context, tutorialID string) ([]*TutorialStep, error) {
	tm.mu.RLock()
	defer tm.mu.RUnlock()

	steps, exists := tm.steps[tutorialID]
	if !exists {
		return []*TutorialStep{}, nil
	}

	return steps, nil
}

// AddTutorialComment ajoute un commentaire à un tutoriel
func (tm *TutorialManager) AddTutorialComment(ctx context.Context, tutorialID, userID, username, content string, rating int) (*TutorialComment, error) {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	commentID := uuid.New().String()
	comment := &TutorialComment{
		ID:         commentID,
		TutorialID: tutorialID,
		UserID:     userID,
		Username:   username,
		Content:    content,
		Rating:     rating,
		IsHelpful:  false,
		CreatedAt:  time.Now(),
		UpdatedAt:  time.Now(),
	}

	tm.comments[tutorialID] = append(tm.comments[tutorialID], comment)

	// Mettre à jour la note moyenne du tutoriel
	tm.updateTutorialRating(tutorialID)

	tm.logger.Info("Commentaire ajouté",
		zap.String("tutorial_id", tutorialID),
		zap.String("comment_id", commentID),
		zap.String("username", username))

	return comment, nil
}

// GetTutorialComments récupère tous les commentaires d'un tutoriel
func (tm *TutorialManager) GetTutorialComments(ctx context.Context, tutorialID string) ([]*TutorialComment, error) {
	tm.mu.RLock()
	defer tm.mu.RUnlock()

	comments, exists := tm.comments[tutorialID]
	if !exists {
		return []*TutorialComment{}, nil
	}

	return comments, nil
}

// IncrementViews incrémente le nombre de vues d'un tutoriel
func (tm *TutorialManager) IncrementViews(ctx context.Context, tutorialID string) error {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	tutorial, exists := tm.tutorials[tutorialID]
	if !exists {
		return fmt.Errorf("tutoriel non trouvé: %s", tutorialID)
	}

	tutorial.Views++
	tutorial.UpdatedAt = time.Now()

	tm.logger.Debug("Vues incrémentées",
		zap.String("tutorial_id", tutorialID),
		zap.Int64("views", tutorial.Views))

	return nil
}

// LikeTutorial ajoute un like à un tutoriel
func (tm *TutorialManager) LikeTutorial(ctx context.Context, tutorialID string) error {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	tutorial, exists := tm.tutorials[tutorialID]
	if !exists {
		return fmt.Errorf("tutoriel non trouvé: %s", tutorialID)
	}

	tutorial.Likes++
	tutorial.UpdatedAt = time.Now()

	tm.logger.Debug("Like ajouté",
		zap.String("tutorial_id", tutorialID),
		zap.Int64("likes", tutorial.Likes))

	return nil
}

// DislikeTutorial ajoute un dislike à un tutoriel
func (tm *TutorialManager) DislikeTutorial(ctx context.Context, tutorialID string) error {
	tm.mu.Lock()
	defer tm.mu.Unlock()

	tutorial, exists := tm.tutorials[tutorialID]
	if !exists {
		return fmt.Errorf("tutoriel non trouvé: %s", tutorialID)
	}

	tutorial.Dislikes++
	tutorial.UpdatedAt = time.Now()

	tm.logger.Debug("Dislike ajouté",
		zap.String("tutorial_id", tutorialID),
		zap.Int64("dislikes", tutorial.Dislikes))

	return nil
}

// updateTutorialRating met à jour la note moyenne d'un tutoriel
func (tm *TutorialManager) updateTutorialRating(tutorialID string) {
	comments, exists := tm.comments[tutorialID]
	if !exists || len(comments) == 0 {
		return
	}

	var totalRating int
	var ratedComments int

	for _, comment := range comments {
		if comment.Rating > 0 {
			totalRating += comment.Rating
			ratedComments++
		}
	}

	if ratedComments > 0 {
		tutorial, exists := tm.tutorials[tutorialID]
		if exists {
			tutorial.Rating = float64(totalRating) / float64(ratedComments)
			tutorial.UpdatedAt = time.Now()
		}
	}
}

// SearchTutorials recherche des tutoriels par mots-clés
func (tm *TutorialManager) SearchTutorials(ctx context.Context, query string, filters map[string]interface{}) ([]*Tutorial, error) {
	tm.mu.RLock()
	defer tm.mu.RUnlock()

	var results []*Tutorial
	query = fmt.Sprintf("%%%s%%", query) // Recherche LIKE

	for _, tutorial := range tm.tutorials {
		// Vérifier si le tutoriel correspond à la recherche
		matches := false
		if contains(tutorial.Title, query) || contains(tutorial.Description, query) || contains(tutorial.Author, query) {
			matches = true
		}

		// Vérifier les tags
		for _, tag := range tutorial.Tags {
			if contains(tag, query) {
				matches = true
				break
			}
		}

		if !matches {
			continue
		}

		// Appliquer les filtres si fournis
		if filters != nil {
			if category, ok := filters["category"].(string); ok && tutorial.Category != category {
				continue
			}
			if isPublished, ok := filters["is_published"].(bool); ok && tutorial.IsPublished != isPublished {
				continue
			}
			if isFree, ok := filters["is_free"].(bool); ok && tutorial.IsFree != isFree {
				continue
			}
		}

		results = append(results, tutorial)
	}

	return results, nil
}

// contains vérifie si une chaîne contient une sous-chaîne (insensible à la casse)
func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr ||
		(len(s) > len(substr) && (s[:len(substr)] == substr ||
			s[len(s)-len(substr):] == substr ||
			containsSubstring(s, substr))))
}

// containsSubstring vérifie si une chaîne contient une sous-chaîne
func containsSubstring(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
