package FikoYTDosya

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"log"
	"mime/multipart"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	fiko_mongo "server/sistem/fikoyt_mongo"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/gofiber/fiber/v3"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/gridfs"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// Event türleri
type EventType string

const (
	EventStarted          EventType = "STARTED"
	EventProgress         EventType = "PROGRESS"
	EventCompleted        EventType = "COMPLETED"
	EventError            EventType = "ERROR"
	EventWarning          EventType = "WARNING"
	EventFileDetected     EventType = "FILE_DETECTED"
	EventCompressionStart EventType = "COMPRESSION_START"
	EventCompressionEnd   EventType = "COMPRESSION_END"
	EventThumbnailStart   EventType = "THUMBNAIL_START"
	EventThumbnailEnd     EventType = "THUMBNAIL_END"
	EventGridFSSave       EventType = "GRIDFS_SAVE"
	EventCleanup          EventType = "CLEANUP"
	EventPaused           EventType = "PAUSED"
	EventResumed          EventType = "RESUMED"
	EventCancelled        EventType = "CANCELLED"
)

// Process durumları
type ProcessStatus string

const (
	StatusRunning   ProcessStatus = "RUNNING"
	StatusPaused    ProcessStatus = "PAUSED"
	StatusCompleted ProcessStatus = "COMPLETED"
	StatusError     ProcessStatus = "ERROR"
	StatusCancelled ProcessStatus = "CANCELLED"
)

// Event yapısı
type ProcessEvent struct {
	ID          string                 `json:"id"`
	Type        EventType              `json:"type"`
	Message     string                 `json:"message"`
	Progress    float64                `json:"progress"`
	Timestamp   time.Time              `json:"timestamp"`
	Data        map[string]interface{} `json:"data,omitempty"`
	Error       error                  `json:"error,omitempty"`
	FileID      primitive.ObjectID     `json:"file_id"`
	ProcessStep string                 `json:"process_step"`
}

// Event handler fonksiyon tipi
type EventHandler func(event *ProcessEvent) error

// Process kontrolör
type ProcessController struct {
	mu             sync.RWMutex
	status         ProcessStatus
	ctx            context.Context
	cancel         context.CancelFunc
	eventHandlers  []EventHandler
	currentProcess *exec.Cmd
	events         []*ProcessEvent
	maxEvents      int
	pauseChan      chan bool
}

// Yeni ProcessController oluştur
func NewProcessController() *ProcessController {
	ctx, cancel := context.WithCancel(context.Background())
	return &ProcessController{
		status:        StatusRunning,
		ctx:           ctx,
		cancel:        cancel,
		eventHandlers: make([]EventHandler, 0),
		events:        make([]*ProcessEvent, 0),
		maxEvents:     1000,
		pauseChan:     make(chan bool, 1),
	}
}

// Orijinal FikoYTDosya struct'ınıza event sistemi ekleniyor
type FikoYTDosya struct {
	File           *multipart.FileHeader
	Id             primitive.ObjectID
	ThumbId        primitive.ObjectID
	Path           string
	ProcessedPath  string
	ThumbPath      string
	MimeType       string
	Saved          bool
	Processed      bool
	ThumbGenerated bool

	// Event sistemi (opsiyonel)
	controller   *ProcessController
	eventEnabled bool
}

// Event sistemi aktifleştir
func (f *FikoYTDosya) EnableEvents() *FikoYTDosya {
	if f.controller == nil {
		f.controller = NewProcessController()
	}
	f.eventEnabled = true
	return f
}

// Event sistemi deaktif et (normal mod)
func (f *FikoYTDosya) DisableEvents() *FikoYTDosya {
	f.eventEnabled = false
	return f
}

// Event handler ekle
func (f *FikoYTDosya) AddEventHandler(handler EventHandler) *FikoYTDosya {
	if f.controller == nil {
		f.controller = NewProcessController()
		f.eventEnabled = true
	}
	f.controller.eventHandlers = append(f.controller.eventHandlers, handler)
	return f
}

// Event yayınla (internal)
func (f *FikoYTDosya) emitEvent(eventType EventType, message string, progress float64, processStep string, data map[string]interface{}, err error) {
	if !f.eventEnabled || f.controller == nil {
		return
	}

	event := &ProcessEvent{
		ID:          fmt.Sprintf("%d", time.Now().UnixNano()),
		Type:        eventType,
		Message:     message,
		Progress:    progress,
		Timestamp:   time.Now(),
		Data:        data,
		Error:       err,
		FileID:      f.Id,
		ProcessStep: processStep,
	}

	f.controller.mu.RLock()
	defer f.controller.mu.RUnlock()

	// Event'i saklayalım
	if len(f.controller.events) >= f.controller.maxEvents {
		f.controller.events = f.controller.events[1:]
	}
	f.controller.events = append(f.controller.events, event)

	// Event handler'ları çağır
	for _, handler := range f.controller.eventHandlers {
		go func(h EventHandler, e *ProcessEvent) {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("Event handler panic: %v", r)
				}
			}()
			if handlerErr := h(e); handlerErr != nil {
				log.Printf("Event handler error: %v", handlerErr)
			}
		}(handler, event)
	}
}

// Kontrolcü işlemler
func (f *FikoYTDosya) Pause() *FikoYTDosya {
	if f.controller != nil {
		f.controller.mu.Lock()
		defer f.controller.mu.Unlock()

		if f.controller.status == StatusRunning {
			f.controller.status = StatusPaused
			f.emitEvent(EventPaused, "Process paused by user", 0, "CONTROL", nil, nil)
		}
	}
	return f
}

func (f *FikoYTDosya) Resume() *FikoYTDosya {
	if f.controller != nil {
		f.controller.mu.Lock()
		defer f.controller.mu.Unlock()

		if f.controller.status == StatusPaused {
			f.controller.status = StatusRunning
			f.emitEvent(EventResumed, "Process resumed by user", 0, "CONTROL", nil, nil)
		}
	}
	return f
}

func (f *FikoYTDosya) Cancel() *FikoYTDosya {
	if f.controller != nil {
		f.controller.mu.Lock()
		defer f.controller.mu.Unlock()

		f.controller.status = StatusCancelled
		f.controller.cancel()
		if f.controller.currentProcess != nil && f.controller.currentProcess.Process != nil {
			f.controller.currentProcess.Process.Kill()
		}
		f.emitEvent(EventCancelled, "Process cancelled by user", 0, "CONTROL", nil, nil)
	}
	return f
}

func (f *FikoYTDosya) GetStatus() ProcessStatus {
	if f.controller == nil {
		return StatusRunning
	}
	f.controller.mu.RLock()
	defer f.controller.mu.RUnlock()
	return f.controller.status
}

func (f *FikoYTDosya) GetEvents() []*ProcessEvent {
	if f.controller == nil {
		return []*ProcessEvent{}
	}
	f.controller.mu.RLock()
	defer f.controller.mu.RUnlock()

	events := make([]*ProcessEvent, len(f.controller.events))
	copy(events, f.controller.events)
	return events
}

func (f *FikoYTDosya) GetLastEvents(count int) []*ProcessEvent {
	if f.controller == nil {
		return []*ProcessEvent{}
	}
	f.controller.mu.RLock()
	defer f.controller.mu.RUnlock()

	if count <= 0 || len(f.controller.events) == 0 {
		return []*ProcessEvent{}
	}

	start := len(f.controller.events) - count
	if start < 0 {
		start = 0
	}

	events := make([]*ProcessEvent, len(f.controller.events)-start)
	copy(events, f.controller.events[start:])
	return events
}

// Yardımcı kontrol fonksiyonları
func (f *FikoYTDosya) checkContinue() error {
	if f.controller == nil {
		return nil
	}

	// Context iptal kontrolü
	select {
	case <-f.controller.ctx.Done():
		return fmt.Errorf("operation cancelled")
	default:
	}

	// Pause kontrolü
	for {
		f.controller.mu.RLock()
		status := f.controller.status
		f.controller.mu.RUnlock()

		if status != StatusPaused {
			break
		}
		time.Sleep(100 * time.Millisecond)
	}

	// İptal edilmiş mi kontrol et
	f.controller.mu.RLock()
	status := f.controller.status
	f.controller.mu.RUnlock()

	if status == StatusCancelled {
		return fmt.Errorf("operation cancelled")
	}

	return nil
}

// ================== DUCK TYPING FUNCTIONS ==================

// Event'lı kaydetme - chain olarak kullanılabilir
func (f *FikoYTDosya) KaydetEventli() *FikoYTDosya {
	f.emitEvent(EventStarted, "File save process started", 0, "SAVE", nil, nil)

	defer func() {
		if r := recover(); r != nil {
			f.emitEvent(EventError, fmt.Sprintf("Panic during save: %v", r), 0, "SAVE", nil, fmt.Errorf("%v", r))
		}
	}()

	// Orijinal Kaydet() fonksiyonunuzu çağır ama event'lerle takip et
	if err := f.kaydetInternal(); err != nil {
		f.emitEvent(EventError, "Save failed, continuing...", 100, "SAVE", nil, err)
		return f // Hata olsa bile devam et
	}

	f.emitEvent(EventCompleted, "File save completed", 100, "SAVE", nil, nil)
	return f
}

// Event'lı işleme
func (f *FikoYTDosya) IsleEventli() *FikoYTDosya {
	f.emitEvent(EventStarted, "File processing started", 0, "PROCESS", nil, nil)

	defer func() {
		if r := recover(); r != nil {
			f.emitEvent(EventError, fmt.Sprintf("Panic during processing: %v", r), 0, "PROCESS", nil, fmt.Errorf("%v", r))
		}
	}()

	// Compression
	f.emitEvent(EventCompressionStart, "Starting compression", 10, "PROCESS", nil, nil)

	outputFile := f.Path + filepath.Ext(f.File.Filename)
	mimeType, err := f.compressWithFFmpegEventli(f.Path, outputFile)
	if err != nil {
		f.emitEvent(EventWarning, "Compression failed, continuing...", 50, "PROCESS", nil, err)
		f.ProcessedPath = f.Path
		f.MimeType = "application/octet-stream"
	} else {
		f.Processed = true
		f.ProcessedPath = outputFile
		f.MimeType = mimeType
		f.emitEvent(EventCompressionEnd, "Compression completed", 60, "PROCESS", map[string]interface{}{
			"output_file": outputFile,
			"mime_type":   mimeType,
		}, nil)
	}

	// Thumbnail generation
	if f.isVideo() && f.ThumbId.IsZero() {
		f.emitEvent(EventThumbnailStart, "Starting thumbnail generation", 70, "PROCESS", nil, nil)

		if err := f.generateThumbnailEventli(); err != nil {
			f.emitEvent(EventWarning, "Thumbnail generation failed, continuing...", 80, "PROCESS", nil, err)
		} else {
			f.emitEvent(EventThumbnailEnd, "Thumbnail generation completed", 90, "PROCESS", map[string]interface{}{
				"thumb_id":   f.ThumbId.Hex(),
				"thumb_path": f.ThumbPath,
			}, nil)
		}
	}

	f.emitEvent(EventCompleted, "File processing completed", 100, "PROCESS", nil, nil)
	return f
}

// Event'lı GridFS kaydetme
func (f *FikoYTDosya) KaydetGridFsEventli(metadata fiber.Map) *FikoYTDosya {
	f.emitEvent(EventGridFSSave, "Starting GridFS save", 0, "GRIDFS_SAVE", nil, nil)

	defer func() {
		if r := recover(); r != nil {
			f.emitEvent(EventError, fmt.Sprintf("Panic during GridFS save: %v", r), 0, "GRIDFS_SAVE", nil, fmt.Errorf("%v", r))
		}
	}()

	fileOid := f.Id
	_, err := saveToGridFS(fileOid, f.ProcessedPath, &metadata)
	if err != nil {
		f.emitEvent(EventError, "Error saving to GridFS", 100, "GRIDFS_SAVE", nil, err)
	} else {
		f.emitEvent(EventCompleted, "GridFS save completed", 100, "GRIDFS_SAVE", map[string]interface{}{
			"file_id": fileOid.Hex(),
		}, nil)
	}

	return f
}

// Event'lı thumbnail GridFS kaydetme
func (f *FikoYTDosya) KaydetThumbGridFsEventli(metadata fiber.Map) *FikoYTDosya {
	if !f.ThumbGenerated || f.ThumbPath == "" {
		f.emitEvent(EventWarning, "No thumbnail to save", 0, "THUMB_GRIDFS_SAVE", nil, nil)
		return f
	}

	f.emitEvent(EventGridFSSave, "Starting thumbnail GridFS save", 0, "THUMB_GRIDFS_SAVE", nil, nil)

	defer func() {
		if r := recover(); r != nil {
			f.emitEvent(EventError, fmt.Sprintf("Panic during thumbnail GridFS save: %v", r), 0, "THUMB_GRIDFS_SAVE", nil, fmt.Errorf("%v", r))
		}
	}()

	if metadata == nil {
		metadata = fiber.Map{}
	}
	metadata["type"] = "thumbnail"
	metadata["parent_file_id"] = f.Id
	metadata["mime_type"] = "image/jpeg"

	_, err := saveToGridFS(f.ThumbId, f.ThumbPath, &metadata)
	if err != nil {
		f.emitEvent(EventWarning, "Error saving thumbnail to GridFS", 100, "THUMB_GRIDFS_SAVE", nil, err)
		f.ThumbId = primitive.NilObjectID
		f.ThumbGenerated = false
	} else {
		f.emitEvent(EventCompleted, "Thumbnail GridFS save completed", 100, "THUMB_GRIDFS_SAVE", map[string]interface{}{
			"thumb_id": f.ThumbId,
		}, nil)
	}

	return f
}

// Event'lı temizlik
func (f *FikoYTDosya) TemizleEventli() *FikoYTDosya {
	f.emitEvent(EventCleanup, "Starting file cleanup", 0, "CLEANUP", nil, nil)

	defer func() {
		if r := recover(); r != nil {
			f.emitEvent(EventError, fmt.Sprintf("Panic during cleanup: %v", r), 0, "CLEANUP", nil, fmt.Errorf("%v", r))
		}
	}()

	cleanupCount := 0
	totalFiles := 0

	// Ana dosyayı sil
	if f.Path != "" {
		totalFiles++
		if _, err := os.Stat(f.Path); err == nil {
			if removeErr := os.Remove(f.Path); removeErr == nil {
				cleanupCount++
				f.emitEvent(EventProgress, "Original file cleaned", 33, "CLEANUP", map[string]interface{}{
					"file": f.Path,
				}, nil)
			} else {
				f.emitEvent(EventWarning, "Failed to remove original file", 33, "CLEANUP", nil, removeErr)
			}
		}
	}

	// İşlenmiş dosyayı sil
	if f.ProcessedPath != "" && f.ProcessedPath != f.Path {
		totalFiles++
		if _, err := os.Stat(f.ProcessedPath); err == nil {
			if removeErr := os.Remove(f.ProcessedPath); removeErr == nil {
				cleanupCount++
				f.emitEvent(EventProgress, "Processed file cleaned", 66, "CLEANUP", map[string]interface{}{
					"file": f.ProcessedPath,
				}, nil)
			} else {
				f.emitEvent(EventWarning, "Failed to remove processed file", 66, "CLEANUP", nil, removeErr)
			}
		}
	}

	// Thumbnail dosyasını sil
	if f.ThumbPath != "" {
		totalFiles++
		if _, err := os.Stat(f.ThumbPath); err == nil {
			if removeErr := os.Remove(f.ThumbPath); removeErr == nil {
				cleanupCount++
				f.emitEvent(EventProgress, "Thumbnail file cleaned", 100, "CLEANUP", map[string]interface{}{
					"file": f.ThumbPath,
				}, nil)
			} else {
				f.emitEvent(EventWarning, "Failed to remove thumbnail file", 100, "CLEANUP", nil, removeErr)
			}
		}
	}

	f.emitEvent(EventCompleted, fmt.Sprintf("Cleanup completed: %d/%d files cleaned", cleanupCount, totalFiles), 100, "CLEANUP", map[string]interface{}{
		"cleaned_files": cleanupCount,
		"total_files":   totalFiles,
	}, nil)

	return f
}

// Chain olarak tüm işlemleri yap
func (f *FikoYTDosya) TumIslemleriYapEventli(metadata fiber.Map, thumbMetaData fiber.Map) *FikoYTDosya {
	f.emitEvent(EventStarted, "Starting full processing pipeline", 0, "FULL_PIPELINE", nil, nil)

	return f.
		KaydetEventli().
		IsleEventli().
		KaydetGridFsEventli(metadata).
		KaydetThumbGridFsEventli(thumbMetaData).
		TemizleEventli()
}

// ================== INTERNAL FUNCTIONS ==================

// Orijinal kaydet fonksiyonunuzun event'li versiyonu
func (f *FikoYTDosya) kaydetInternal() error {
	if f.File == nil {
		return fmt.Errorf("file is nil")
	}

	if f.Path == "" {
		return fmt.Errorf("path is empty")
	}

	f.emitEvent(EventProgress, "Opening file", 10, "SAVE", nil, nil)

	file, err := f.File.Open()
	if err != nil {
		return err
	}
	defer func() {
		if file != nil {
			file.Close()
		}
	}()

	f.emitEvent(EventProgress, "Creating temp directory", 20, "SAVE", nil, nil)

	tempDir := "./ffmpeg/files"
	if _, err := os.Stat(tempDir); os.IsNotExist(err) {
		if err := os.MkdirAll(tempDir, 0755); err != nil {
			tempDir = "./temp"
			os.MkdirAll(tempDir, 0755)
		}
	}

	f.emitEvent(EventProgress, "Creating temp file", 30, "SAVE", nil, nil)

	f.Path = filepath.Join(tempDir, f.Id.Hex()+f.Path)
	tempFile, err := os.Create(f.Path)
	if err != nil {
		return err
	}
	defer func() {
		if tempFile != nil {
			tempFile.Close()
		}
	}()

	f.emitEvent(EventProgress, "Copying file data", 40, "SAVE", nil, nil)

	buffer := make([]byte, 5*1024*1024)
	totalCopied := int64(0)
	expectedSize := f.File.Size

	for {
		if err := f.checkContinue(); err != nil {
			f.emitEvent(EventCancelled, "File copy cancelled", float64(totalCopied)/float64(expectedSize)*40+40, "SAVE", nil, err)
			return err
		}

		n, readErr := file.Read(buffer)
		if n > 0 {
			if _, writeErr := tempFile.Write(buffer[:n]); writeErr != nil {
				f.emitEvent(EventWarning, "Error writing to temp file", 0, "SAVE", nil, writeErr)
			} else {
				totalCopied += int64(n)
				progress := float64(totalCopied)/float64(expectedSize)*40 + 40
				f.emitEvent(EventProgress, fmt.Sprintf("Copied %d/%d bytes", totalCopied, expectedSize), progress, "SAVE", map[string]interface{}{
					"bytes_copied": totalCopied,
					"total_bytes":  expectedSize,
				}, nil)
			}
		}
		if readErr != nil {
			if readErr == io.EOF {
				break
			}
			f.emitEvent(EventWarning, "Error reading file", 0, "SAVE", nil, readErr)
			break
		}
	}

	f.emitEvent(EventProgress, "Detecting MIME type", 85, "SAVE", nil, nil)

	mimeType, err := detectMimeType(f.Path)
	if err != nil {
		f.emitEvent(EventWarning, "Error detecting MIME type", 85, "SAVE", nil, err)
		mimeType = "application/octet-stream"
	}

	f.MimeType = mimeType
	f.Saved = true

	f.emitEvent(EventFileDetected, fmt.Sprintf("File detected as %s", mimeType), 90, "SAVE", map[string]interface{}{
		"mime_type": mimeType,
		"file_size": totalCopied,
	}, nil)

	return nil
}

// Event'li sıkıştırma
func (f *FikoYTDosya) compressWithFFmpegEventli(inputPath, outputPath string) (string, error) {
	mimeType, err := detectMimeType(inputPath)
	if err != nil {
		f.emitEvent(EventWarning, "MIME type detection failed", 0, "COMPRESSION", nil, err)
		mimeType = "application/octet-stream"
	}

	var ffmpegArgs []string
	ffmpegPath := "ffmpeg"
	ffprobePath := "ffprobe"

	if strings.HasPrefix(mimeType, "image") {
		ffmpegArgs = []string{
			"-i", inputPath,
			"-vcodec", "mjpeg",
			"-q:v", "5",
			"-vf", "scale='min(1920,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease",
			"-an",
			"-f", "image2",
			"-hide_banner",
			"-nostats",
			"-loglevel", "quiet",
			outputPath,
		}
	} else if strings.HasPrefix(mimeType, "video") {
		ffmpegArgs = []string{
			"-i", inputPath,
			"-c:v", "h264_nvenc",
			"-preset", "p3",
			"-rc", "vbr_hq",
			"-cq", "30",
			"-b:v", "0",
			"-maxrate", "3000k",
			"-bufsize", "6000k",
			"-profile:v", "high",
			"-level:v", "4.1",
			"-vf", "scale=w=854:h=480:force_original_aspect_ratio=decrease",
			"-c:a", "aac",
			"-b:a", "64k",
			"-f", "mp4",
			"-hide_banner",
			"-nostats",
			"-loglevel", "debug",
			outputPath,
		}
	} else if strings.HasPrefix(mimeType, "audio") {
		ffmpegArgs = []string{
			"-i", inputPath,
			"-vn",
			"-ar", "44100",
			"-ac", "2",
			"-b:a", "96k",
			"-threads", "8",
			"-hide_banner",
			"-nostats",
			"-loglevel", "quiet",
			outputPath,
		}
	} else {
		// Default video settings
		ffmpegArgs = []string{
			"-i", inputPath,
			"-c:v", "h264_nvenc",
			"-preset", "p3",
			"-rc", "vbr_hq",
			"-cq", "30",
			"-b:v", "0",
			"-maxrate", "3000k",
			"-bufsize", "6000k",
			"-profile:v", "high",
			"-level:v", "4.1",
			"-vf", "scale=w=854:h=480:force_original_aspect_ratio=decrease",
			"-c:a", "aac",
			"-b:a", "64k",
			"-f", "mp4",
			"-hide_banner",
			"-nostats",
			"-loglevel", "debug",
			outputPath,
		}
	}

	ffmpegArgs = append(ffmpegArgs, "-progress", "pipe:1")

	cmd := exec.Command(ffmpegPath, ffmpegArgs...)
	if f.controller != nil {
		f.controller.currentProcess = cmd
	}

	var stderrBuf bytes.Buffer
	cmd.Stderr = &stderrBuf

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		return "", fmt.Errorf("StdoutPipe oluşturulamadı: %v", err)
	}

	if err := cmd.Start(); err != nil {
		return "", fmt.Errorf("FFmpeg başlatılamadı: %v - %s", err, stderrBuf.String())
	}

	duration, err := getDuration(inputPath, ffprobePath)
	if err != nil {
		f.emitEvent(EventWarning, "Duration could not be determined", 0, "COMPRESSION", nil, err)
	}

	// Progress tracking goroutine
	go func() {
		defer func() {
			if r := recover(); r != nil {
				f.emitEvent(EventError, fmt.Sprintf("Progress tracking panic: %v", r), 0, "COMPRESSION", nil, fmt.Errorf("%v", r))
			}
		}()

		scanner := bufio.NewScanner(stdoutPipe)
		reTime := regexp.MustCompile(`time=(\d+:\d+:\d+\.\d+)`)
		reProgressEnd := regexp.MustCompile(`progress=end`)

		for scanner.Scan() {
			if err := f.checkContinue(); err != nil {
				return
			}

			line := scanner.Text()

			if reProgressEnd.MatchString(line) {
				f.emitEvent(EventProgress, "Compression completed", 100, "COMPRESSION", nil, nil)
				break
			} else {
				matchTime := reTime.FindStringSubmatch(line)
				if len(matchTime) > 1 && duration > 0 {
					currentTime, err := parseTime(matchTime[1])
					if err == nil {
						percentage := int(float64(currentTime) / float64(duration) * 100)
						if percentage > 100 {
							percentage = 100
						}
						f.emitEvent(EventProgress, fmt.Sprintf("Compression progress: %d%%", percentage), float64(float64(currentTime)/float64(duration)*100), "COMPRESSION", map[string]interface{}{
							"current_time": currentTime,
							"total_time":   duration,
						}, nil)
					}
				}
			}
		}

		if err := scanner.Err(); err != nil {
			f.emitEvent(EventError, "Error reading FFmpeg stdout", 0, "COMPRESSION", nil, err)
		}
	}()

	if err := cmd.Wait(); err != nil {
		f.emitEvent(EventWarning, "FFmpeg process finished with error", 0, "COMPRESSION", nil, err)
		return mimeType, err
	}

	return mimeType, nil
}

// Event'li thumbnail üretimi
func (f *FikoYTDosya) generateThumbnailEventli() error {
	if !f.isVideo() {
		return fmt.Errorf("not a video file")
	}

	tempDir := "./ffmpeg/files"
	f.ThumbId = primitive.NewObjectID()
	f.ThumbPath = filepath.Join(tempDir, f.ThumbId.Hex()+"_thumb.jpg")

	ffmpegArgs := []string{
		"-i", f.ProcessedPath,
		"-ss", "00:00:01",
		"-vframes", "1",
		"-vcodec", "mjpeg",
		"-q:v", "5",
		"-vf", "scale='min(1920,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease",
		"-an",
		"-f", "image2",
		"-hide_banner",
		"-nostats",
		"-loglevel", "quiet",
		"-y",
		f.ThumbPath,
	}

	cmd := exec.Command("ffmpeg", ffmpegArgs...)

	var stderrBuf bytes.Buffer
	cmd.Stderr = &stderrBuf

	err := cmd.Run()
	if err != nil {
		f.emitEvent(EventWarning, "Thumbnail generation failed: "+stderrBuf.String(), 0, "THUMBNAIL", nil, err)
		f.ThumbId = primitive.NilObjectID
		f.ThumbPath = ""
		return err
	}

	if _, err := os.Stat(f.ThumbPath); os.IsNotExist(err) {
		f.ThumbId = primitive.NilObjectID
		f.ThumbPath = ""
		return fmt.Errorf("thumbnail file was not created")
	}

	f.ThumbGenerated = true
	return nil
}

// Video kontrolü
func (f *FikoYTDosya) isVideo() bool {
	return strings.HasPrefix(f.MimeType, "video/")
}

// ================== UTILITY FUNCTIONS ==================

// Default event handler
func DefaultEventHandler(event *ProcessEvent) error {
	logLevel := "INFO"
	if event.Type == EventError {
		logLevel = "ERROR"
	} else if event.Type == EventWarning {
		logLevel = "WARN"
	}

	log.Printf("[%s] [%s] [%s] %s (Progress: %.1f%%)",
		logLevel, event.FileID.Hex(), event.ProcessStep, event.Message, event.Progress)

	if event.Error != nil {
		log.Printf("[ERROR] %v", event.Error)
	}

	return nil
}

// JSON formatında durum al
func (f *FikoYTDosya) GetEventsJSON() map[string]interface{} {
	if f.controller == nil {
		return map[string]interface{}{
			"status":       StatusRunning,
			"events":       []*ProcessEvent{},
			"total_events": 0,
			"last_event":   nil,
		}
	}

	events := f.GetEvents()
	return map[string]interface{}{
		"status":       f.GetStatus(),
		"total_events": len(events),
		"events":       events,
		"last_event": func() *ProcessEvent {
			if len(events) > 0 {
				return events[len(events)-1]
			}
			return nil
		}(),
	}
}

// Durum özeti
func (f *FikoYTDosya) GetStatusSummary() map[string]interface{} {
	if f.controller == nil {
		return map[string]interface{}{
			"status":        StatusRunning,
			"progress":      0,
			"total_events":  0,
			"error_count":   0,
			"warning_count": 0,
			"last_error":    nil,
			"is_running":    true,
			"is_paused":     false,
			"is_completed":  false,
			"can_pause":     true,
			"can_resume":    false,
			"can_cancel":    true,
		}
	}

	f.controller.mu.RLock()
	defer f.controller.mu.RUnlock()

	events := f.controller.events
	var lastProgress float64
	var lastError *ProcessEvent
	var warningCount int
	var errorCount int

	for _, event := range events {
		if event.Progress > lastProgress {
			lastProgress = event.Progress
		}
		if event.Type == EventError {
			errorCount++
			lastError = event
		}
		if event.Type == EventWarning {
			warningCount++
		}
	}

	status := f.controller.status
	return map[string]interface{}{
		"status":        status,
		"progress":      lastProgress,
		"total_events":  len(events),
		"error_count":   errorCount,
		"warning_count": warningCount,
		"last_error":    lastError,
		"is_running":    status == StatusRunning,
		"is_paused":     status == StatusPaused,
		"is_completed":  status == StatusCompleted,
		"can_pause":     status == StatusRunning,
		"can_resume":    status == StatusPaused,
		"can_cancel":    status == StatusRunning || status == StatusPaused,
	}
}

// WebSocket streaming
// func (f *FikoYTDosya) StreamEventsWebSocket(c *websocket.Conn) {
// 	if f.controller == nil {
// 		return
// 	}

// 	eventChan := make(chan *ProcessEvent, 100)

// 	handler := func(event *ProcessEvent) error {
// 		select {
// 		case eventChan <- event:
// 		default:
// 			// Buffer full, skip event
// 		}
// 		return nil
// 	}

// 	f.controller.AddEventHandler(handler)

// 	go func() {
// 		defer close(eventChan)
// 		for event := range eventChan {
// 			if err := c.WriteJSON(event); err != nil {
// 				break
// 			}
// 		}
// 	}()
// }

// ================== BATCH PROCESSING ==================

type BatchProcessor struct {
	Files   []*FikoYTDosya
	BatchID string
	events  []*ProcessEvent
	mu      sync.RWMutex
}

func NewBatchProcessor(files []*FikoYTDosya) *BatchProcessor {
	return &BatchProcessor{
		Files:   files,
		BatchID: fmt.Sprintf("batch_%d", time.Now().UnixNano()),
		events:  make([]*ProcessEvent, 0),
	}
}

func (bp *BatchProcessor) ProcessAllEventli(metadata fiber.Map, thumbMetaData fiber.Map, maxConcurrent int) *BatchProcessor {
	if maxConcurrent <= 0 {
		maxConcurrent = 3
	}

	// Batch event
	batchEvent := &ProcessEvent{
		ID:          fmt.Sprintf("batch_%d", time.Now().UnixNano()),
		Type:        EventStarted,
		Message:     fmt.Sprintf("Starting batch processing: %d files", len(bp.Files)),
		Progress:    0,
		Timestamp:   time.Now(),
		FileID:      primitive.NilObjectID,
		ProcessStep: "BATCH",
		Data: map[string]interface{}{
			"total_files": len(bp.Files),
			"batch_id":    bp.BatchID,
		},
	}

	bp.mu.Lock()
	bp.events = append(bp.events, batchEvent)
	bp.mu.Unlock()

	semaphore := make(chan struct{}, maxConcurrent)
	var wg sync.WaitGroup

	processedCount := 0
	var mu sync.Mutex

	for i, file := range bp.Files {
		wg.Add(1)
		go func(index int, f *FikoYTDosya) {
			defer wg.Done()

			semaphore <- struct{}{}
			defer func() { <-semaphore }()

			// Enable events for this file
			f.EnableEvents().AddEventHandler(func(event *ProcessEvent) error {
				// Update batch progress
				mu.Lock()
				if event.Type == EventCompleted && event.ProcessStep == "FULL_PIPELINE" {
					processedCount++
				}
				currentProgress := float64(processedCount) / float64(len(bp.Files)) * 100
				mu.Unlock()

				// Add to batch events
				batchProgressEvent := &ProcessEvent{
					ID:          fmt.Sprintf("batch_progress_%d", time.Now().UnixNano()),
					Type:        EventProgress,
					Message:     fmt.Sprintf("File %d/%d - %s", index+1, len(bp.Files), event.Message),
					Progress:    currentProgress,
					Timestamp:   time.Now(),
					FileID:      event.FileID,
					ProcessStep: "BATCH",
					Data: map[string]interface{}{
						"file_index":    index + 1,
						"total_files":   len(bp.Files),
						"current_file":  event.FileID.Hex(),
						"current_step":  event.ProcessStep,
						"file_progress": event.Progress,
					},
					Error: event.Error,
				}

				bp.mu.Lock()
				bp.events = append(bp.events, batchProgressEvent)
				bp.mu.Unlock()

				return nil
			})

			// Process file
			f.TumIslemleriYapEventli(metadata, thumbMetaData)

		}(i, file)
	}

	wg.Wait()

	// Final batch event
	finalEvent := &ProcessEvent{
		ID:          fmt.Sprintf("batch_complete_%d", time.Now().UnixNano()),
		Type:        EventCompleted,
		Message:     fmt.Sprintf("Batch processing completed: %d/%d files processed", processedCount, len(bp.Files)),
		Progress:    100,
		Timestamp:   time.Now(),
		FileID:      primitive.NilObjectID,
		ProcessStep: "BATCH",
		Data: map[string]interface{}{
			"processed_files": processedCount,
			"total_files":     len(bp.Files),
			"batch_id":        bp.BatchID,
		},
	}

	bp.mu.Lock()
	bp.events = append(bp.events, finalEvent)
	bp.mu.Unlock()

	return bp
}

func (bp *BatchProcessor) GetEvents() []*ProcessEvent {
	bp.mu.RLock()
	defer bp.mu.RUnlock()

	events := make([]*ProcessEvent, len(bp.events))
	copy(events, bp.events)
	return events
}

func (bp *BatchProcessor) GetEventsJSON() map[string]interface{} {
	events := bp.GetEvents()
	return map[string]interface{}{
		"batch_id":     bp.BatchID,
		"total_events": len(events),
		"events":       events,
		"last_event": func() *ProcessEvent {
			if len(events) > 0 {
				return events[len(events)-1]
			}
			return nil
		}(),
	}
}

// ================== ORIGINAL FUNCTIONS (unchanged) ==================

func getDuration(path, ffprobePath string) (float64, error) {
	cmd := exec.Command(ffprobePath, "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", path)
	output, err := cmd.Output()
	if err != nil {
		return 0, err
	}
	durationStr := strings.TrimSpace(string(output))
	duration, err := strconv.ParseFloat(durationStr, 64)
	if err != nil {
		return 0, err
	}
	return duration, nil
}

func parseTime(timeStr string) (float64, error) {
	parts := strings.Split(timeStr, ":")
	if len(parts) != 3 {
		return 0, fmt.Errorf("geçersiz zaman formatı")
	}
	hours, err := strconv.Atoi(parts[0])
	if err != nil {
		return 0, err
	}
	minutes, err := strconv.Atoi(parts[1])
	if err != nil {
		return 0, err
	}
	secondsStr := strings.Replace(parts[2], ",", ".", 1)
	seconds, err := strconv.ParseFloat(secondsStr, 64)
	if err != nil {
		return 0, err
	}
	return float64(hours*3600+minutes*60) + seconds, nil
}

func saveToGridFS(fileID primitive.ObjectID, filePath string, metaData *fiber.Map) (primitive.ObjectID, error) {
	db := fiko_mongo.GetMongoDBClient().Database("fikoyt")
	fsName := "go_files"
	bucket, err := gridfs.NewBucket(db, &options.BucketOptions{Name: &fsName})
	if err != nil {
		return primitive.NilObjectID, fiber.NewError(fiber.StatusInternalServerError, "GridFS Bucket oluşturulamadı: "+err.Error())
	}

	file, err := os.Open(filePath)
	if err != nil {
		return primitive.NilObjectID, fmt.Errorf("dosya açılamadı: %v", err)
	}
	defer file.Close()

	uploadStream, err := bucket.OpenUploadStreamWithID(fileID, strings.ReplaceAll(filepath.Base(filePath), "-processed", ""), &options.UploadOptions{Metadata: metaData})
	if err != nil {
		return primitive.NilObjectID, fmt.Errorf("upload stream oluşturulamadı: %v", err)
	}
	defer uploadStream.Close()

	if _, err := io.Copy(uploadStream, file); err != nil {
		return primitive.NilObjectID, fmt.Errorf("dosya GridFS'ye yazılamadı: %v", err)
	}

	return fileID, nil
}

func DeleteFromGridfs(fileID primitive.ObjectID) error {
	db := fiko_mongo.GetMongoDBClient().Database("fikoyt")
	fsName := "go_files"
	bucket, err := gridfs.NewBucket(db, &options.BucketOptions{Name: &fsName})
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "GridFS Bucket oluşturulamadı: "+err.Error())
	}

	return bucket.Delete(fileID)
}

func detectMimeType(filePath string) (string, error) {
	// 1️⃣ Dosya uzantısını kontrol et
	ext := strings.ToLower(filepath.Ext(filePath))
	switch ext {
	case ".jpg", ".jpeg":
		return "image/jpeg", nil
	case ".png":
		return "image/png", nil
	case ".webp":
		return "image/webp", nil
	case ".gif":
		return "image/gif", nil
	case ".mp4":
		return "video/mp4", nil
	case ".mov":
		return "video/quicktime", nil
	case ".mkv":
		return "video/x-matroska", nil
	case ".avi":
		return "video/x-msvideo", nil
	case ".webm":
		return "video/webm", nil
	case ".mp3":
		return "audio/mpeg", nil
	case ".wav":
		return "audio/wav", nil
	}

	// 2️⃣ ffprobe ile stream tiplerini kontrol et
	cmd := exec.Command("ffprobe",
		"-v", "error",
		"-show_entries", "stream=codec_type",
		"-of", "default=noprint_wrappers=1:nokey=1",
		filePath,
	)

	var out bytes.Buffer
	cmd.Stdout = &out

	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("ffprobe çalıştırılamadı: %v", err)
	}

	streams := strings.Split(strings.TrimSpace(out.String()), "\n")

	for _, stream := range streams {
		stream = strings.TrimSpace(stream)
		switch stream {
		case "video":
			return "video/mp4", nil
		case "audio":
			return "audio/mpeg", nil
		}
	}

	// 3️⃣ format_name ile fallback
	cmdFormat := exec.Command("ffprobe",
		"-v", "error",
		"-show_entries", "format=format_name",
		"-of", "default=noprint_wrappers=1:nokey=1",
		filePath,
	)
	out.Reset()
	cmdFormat.Stdout = &out
	if err := cmdFormat.Run(); err != nil {
		return "", fmt.Errorf("ffprobe format_name çalıştırılamadı: %v", err)
	}

	format := strings.TrimSpace(out.String())

	switch {
	case strings.Contains(format, "mp4"):
		return "video/mp4", nil
	case strings.Contains(format, "mov"):
		return "video/quicktime", nil
	case strings.Contains(format, "mkv"):
		return "video/x-matroska", nil
	case strings.Contains(format, "mp3"):
		return "audio/mpeg", nil
	case strings.Contains(format, "wav"):
		return "audio/wav", nil
	case strings.Contains(format, "webp"):
		return "image/webp", nil
	case strings.Contains(format, "png"):
		return "image/png", nil
	case strings.Contains(format, "jpeg"), strings.Contains(format, "jpg"):
		return "image/jpeg", nil
	default:
		return "application/octet-stream", nil
	}
}

// ================== ORIGINAL STRUCT METHODS (backwards compatibility) ==================

// Orijinal Kaydet fonksiyonu (event'siz)
func (f *FikoYTDosya) Kaydet() error {
	return f.kaydetInternal()
}

// Orijinal Isle fonksiyonu (event'siz)
func (f *FikoYTDosya) Isle() error {
	outputFile := f.Path + filepath.Ext(f.File.Filename)
	mimeType, err := compressWithFFmpeg(f.Path, outputFile)
	if err != nil {
		return err
	}
	f.Processed = true
	f.ProcessedPath = outputFile
	f.MimeType = mimeType

	if f.isVideo() && f.ThumbId.IsZero() {
		f.generateThumbnail()
	}

	return nil
}

// Orijinal generateThumbnail
func (f *FikoYTDosya) generateThumbnail() {
	if !f.isVideo() {
		return
	}

	tempDir := "./ffmpeg/files"
	f.ThumbId = primitive.NewObjectID()
	f.ThumbPath = filepath.Join(tempDir, f.ThumbId.Hex()+"_thumb.jpg")

	ffmpegArgs := []string{
		"-i", f.ProcessedPath,
		"-ss", "00:00:01",
		"-vframes", "1",
		"-vcodec", "mjpeg",
		"-q:v", "5",
		"-vf", "scale='min(1920,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease",
		"-an",
		"-f", "image2",
		"-hide_banner",
		"-nostats",
		"-loglevel", "quiet",
		"-y",
		f.ThumbPath,
	}

	cmd := exec.Command("ffmpeg", ffmpegArgs...)
	err := cmd.Run()
	if err != nil {
		f.ThumbId = primitive.NilObjectID
		f.ThumbPath = ""
		return
	}

	if _, err := os.Stat(f.ThumbPath); os.IsNotExist(err) {
		f.ThumbId = primitive.NilObjectID
		f.ThumbPath = ""
		return
	}

	f.ThumbGenerated = true
}

// Orijinal GridFS fonksiyonları
func (f *FikoYTDosya) KaydetGridFs(metadata fiber.Map) error {
	fileOid := f.Id
	_, err := saveToGridFS(fileOid, f.ProcessedPath, &metadata)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "Error saving to GridFS: "+err.Error())
	}
	return nil
}

func (f *FikoYTDosya) KaydetThumbGridFs(metadata fiber.Map) error {
	if !f.ThumbGenerated || f.ThumbPath == "" {
		return nil
	}

	if metadata == nil {
		metadata = fiber.Map{}
	}
	metadata["type"] = "thumbnail"
	metadata["parent_file_id"] = f.Id.Hex()
	metadata["mime_type"] = "image/jpeg"

	_, err := saveToGridFS(f.ThumbId, f.ThumbPath, &metadata)
	if err != nil {
		f.ThumbId = primitive.NilObjectID
		f.ThumbGenerated = false
		return nil
	}

	return nil
}

func (f *FikoYTDosya) SilGridFs() error {
	fileOid := f.Id
	err := DeleteFromGridfs(fileOid)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "Error deleting from GridFS: "+err.Error())
	}
	return nil
}

func (f *FikoYTDosya) SilThumbGridFs() error {
	if f.ThumbId.IsZero() {
		return nil
	}
	DeleteFromGridfs(f.ThumbId)
	return nil
}

func (f *FikoYTDosya) TemizleDosyalar() error {
	if _, err := os.Stat(f.Path); err == nil {
		os.Remove(f.Path)
	}

	if _, err := os.Stat(f.ProcessedPath); err == nil {
		os.Remove(f.ProcessedPath)
	}

	if f.ThumbPath != "" {
		if _, err := os.Stat(f.ThumbPath); err == nil {
			os.Remove(f.ThumbPath)
		}
	}

	return nil
}

func (f *FikoYTDosya) GetThumbnailId() primitive.ObjectID {
	return f.ThumbId
}

func (f *FikoYTDosya) HasThumbnail() bool {
	return f.ThumbGenerated && !f.ThumbId.IsZero()
}

// Orijinal compressWithFFmpeg (event'siz)
func compressWithFFmpeg(inputPath, outputPath string) (string, error) {
	mimeType, err := detectMimeType(inputPath)
	if err != nil {
		return "", fmt.Errorf("MIME türü tespit edilemedi: %v", err)
	}

	var cmd *exec.Cmd
	var ffmpegArgs []string
	ffmpegPath := "ffmpeg"
	ffprobePath := "ffprobe"

	if strings.HasPrefix(mimeType, "image") {
		ffmpegArgs = []string{
			"-i", inputPath,
			"-vcodec", "mjpeg",
			"-q:v", "5",
			"-vf", "scale='min(1920,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease",
			"-an",
			"-f", "image2",
			"-hide_banner",
			"-nostats",
			"-loglevel", "quiet",
			outputPath,
		}
	} else if strings.HasPrefix(mimeType, "video") {
		ffmpegArgs = []string{
			"-i", inputPath,
			"-c:v", "h264_nvenc",
			"-preset", "p3",
			"-rc", "vbr_hq",
			"-cq", "30",
			"-b:v", "0",
			"-maxrate", "3000k",
			"-bufsize", "6000k",
			"-profile:v", "high",
			"-level:v", "4.1",
			"-vf", "scale=w=854:h=480:force_original_aspect_ratio=decrease",
			"-c:a", "aac",
			"-b:a", "64k",
			"-f", "mp4",
			"-hide_banner",
			"-nostats",
			"-loglevel", "debug",
			outputPath,
		}
	} else if strings.HasPrefix(mimeType, "audio") {
		ffmpegArgs = []string{
			"-i", inputPath,
			"-vn",
			"-ar", "44100",
			"-ac", "2",
			"-b:a", "96k",
			"-threads", "8",
			"-hide_banner",
			"-nostats",
			"-loglevel", "quiet",
			outputPath,
		}
	} else {
		ffmpegArgs = []string{
			"-i", inputPath,
			"-c:v", "h264_nvenc",
			"-preset", "p3",
			"-rc", "vbr_hq",
			"-cq", "30",
			"-b:v", "0",
			"-maxrate", "3000k",
			"-bufsize", "6000k",
			"-profile:v", "high",
			"-level:v", "4.1",
			"-vf", "scale=w=854:h=480:force_original_aspect_ratio=decrease",
			"-c:a", "aac",
			"-b:a", "64k",
			"-f", "mp4",
			"-hide_banner",
			"-nostats",
			"-loglevel", "debug",
			outputPath,
		}
	}

	ffmpegArgs = append(ffmpegArgs, "-progress", "pipe:1")
	cmd = exec.Command(ffmpegPath, ffmpegArgs...)

	var stderrBuf bytes.Buffer
	cmd.Stderr = &stderrBuf

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		return "", fmt.Errorf("StdoutPipe oluşturulamadı: %v", err)
	}

	if err := cmd.Start(); err != nil {
		return "", fmt.Errorf("FFmpeg başlatılamadı: %v - %s", err, stderrBuf.String())
	}

	duration, err := getDuration(inputPath, ffprobePath)
	if err != nil {
		// fmt.Println("Uyarı: Süre alınamadı, ilerleme tahmini yapılamayacak:", err)
	}

	go func() {
		scanner := bufio.NewScanner(stdoutPipe)
		reTime := regexp.MustCompile(`time=(\d+:\d+:\d+\.\d+)`)
		reProgressEnd := regexp.MustCompile(`progress=end\.(\d+)%`)
		for scanner.Scan() {
			line := scanner.Text()

			matchProgressEnd := reProgressEnd.FindStringSubmatch(line)
			if len(matchProgressEnd) > 1 {
				percentage, _ := strconv.Atoi(matchProgressEnd[1])
				fmt.Printf("İlerleme: %d%%\n", percentage)
				break
			} else {
				matchTime := reTime.FindStringSubmatch(line)
				if len(matchTime) > 1 && duration > 0 {
					currentTime, err := parseTime(matchTime[1])
					if err == nil {
						percentage := int(float64(currentTime) / float64(duration) * 100)
						fmt.Printf("İlerleme: %d%%\r", percentage)
					}
				}
			}
		}
		if err := scanner.Err(); err != nil {
			fmt.Fprintln(os.Stderr, "Error reading FFmpeg stdout:", err)
		}
	}()

	go func() {
		scanner := bufio.NewScanner(&stderrBuf)
		for scanner.Scan() {
			// fmt.Fprintln(os.Stderr, "[FFmpeg Error/Warning] "+scanner.Text())
		}
		if err := scanner.Err(); err != nil {
			fmt.Fprintln(os.Stderr, "Error reading FFmpeg stderr:", err)
		}
	}()

	if err := cmd.Wait(); err != nil {
		return "", fmt.Errorf("FFmpeg işlem hatası: %v - %s", err, stderrBuf.String())
	}

	return mimeType, nil
}
