package fikoyt_multiPart

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"server/sistem/fiko_mongo"
	"strings"

	"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"
)

func ProcessMediaStreamMiddleware(c fiber.Ctx) error {
	// MongoDB bağlantısı
	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())
	}

	// Multipart form bilgilerini al
	form, err := c.MultipartForm()
	if err != nil {
		return c.Status(fiber.StatusBadRequest).SendString("Multipart form okunamadı")
	}

	var processedFiles []string
	var tempFiles []string // Hata durumunda temizlenecek dosyalar

	for _, files := range form.File {
		for _, fileHeader := range files {
			// Dosyayı stream olarak aç
			file, err := fileHeader.Open()
			if err != nil {
				cleanupFiles(tempFiles)
				return fiber.NewError(fiber.StatusInternalServerError, "Error opening file: "+err.Error())
			}
			defer file.Close()

			// Geçici dosya oluştur
			tempFilePath := filepath.Join("./ffmpeg/files", fileHeader.Filename)
			tempFile, err := os.Create(tempFilePath)
			if err != nil {
				cleanupFiles(tempFiles)
				return fiber.NewError(fiber.StatusInternalServerError, "Error creating temp file: "+err.Error())
			}
			tempFiles = append(tempFiles, tempFilePath)

			// Stream ile dosyayı kopyala
			if _, err := io.Copy(tempFile, file); err != nil {
				tempFile.Close()
				cleanupFiles(tempFiles)
				return fiber.NewError(fiber.StatusInternalServerError, "Error saving file: "+err.Error())
			}
			tempFile.Close()

			// Sıkıştırma işlemi
			outputFile := tempFilePath + "-processed" + filepath.Ext(fileHeader.Filename)
			if _, err := compressWithFFmpeg(tempFilePath, outputFile); err != nil {
				cleanupFiles(tempFiles)
				return fiber.NewError(fiber.StatusInternalServerError, "Error compressing file: "+err.Error())
			}

			tempFiles = append(tempFiles, outputFile)

			// İşlenen dosyayı GridFS'ye kaydet
			fileOid, _ := primitive.ObjectIDFromHex(fileHeader.Filename)
			metadata := fiber.Map{"test": "deneme"}
			fileID, err := SaveToGridFS(bucket, fileOid, outputFile, &metadata)
			if err != nil {
				cleanupFiles(tempFiles)
				return fiber.NewError(fiber.StatusInternalServerError, "Error saving to GridFS: "+err.Error())
			}
			processedFiles = append(processedFiles, fileID.Hex())
		}
	}

	// Geçici dosyaları temizle
	cleanupFiles(tempFiles)

	// Middleware sonucu context'e ekle
	c.Locals("processedFiles", processedFiles)
	/**/
	// jsonData := c.FormValue("req_body")

	var jsonData fiber.Map
	if err := json.Unmarshal([]byte(c.FormValue("req_body", "{}")), &jsonData); err != nil {
		return c.JSON(fiber.Map{"error": "Error unmarshalling JSON"})
	}
	c.Locals("json", jsonData)

	return c.Next() // Sonraki handler'ı çağır
}

// FFmpeg ile sıkıştırma
func compressWithFFmpeg(inputPath, outputPath string) (string, error) {
	mimeType, err := detectMimeType(inputPath)
	if err != nil {
		return "", err
	}

	var cmd *exec.Cmd
	if strings.HasPrefix(mimeType, "image") {
		cmd = exec.Command("./ffmpeg/ffmpeg.exe", "-i", inputPath,
			"-vcodec", "libwebp",
			"-lossless", "0",
			"-q:v", "75", // -q:v kullanarak -qscale yerine
			"-an",
			"-fps_mode", "auto",
			"-f", "webp",
			outputPath,
		)
	} else if strings.HasPrefix(mimeType, "video") {
		cmd = exec.Command("./ffmpeg/ffmpeg.exe", "-i", inputPath,
			"-quality", "good",
			"-cpu-used", "15",
			"-b:v", "460k",
			"-qmin", "10",
			"-qmax", "30",
			"-maxrate", "460k",
			"-bufsize", "640k",
			"-crf", "10",
			"-threads", "12",
			"-vf", "scale=w=1080:h=760:force_original_aspect_ratio=decrease",
			"-c:a", "libvorbis",
			"-b:a", "64k",
			"-c:v", "libvpx",
			"-f", "webm",
			outputPath,
		)
	} else if strings.HasPrefix(mimeType, "audio") {
		cmd = exec.Command("./ffmpeg/ffmpeg.exe", "-i", inputPath,
			"-vn", "-ar", "44100", "-ac", "2", "-b:a", "96k", "-threads", "8", "-cpu-used", "15",
			outputPath,
		)
	} else {
		return mimeType, nil
	}

	// FFmpeg'in stderr çıktısını yakala (ilerleme bilgisi burada bulunur)
	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	// Komutu çalıştır ve çıktıdaki ilerleme bilgisini yakala
	if err := executeCommand(cmd); err != nil {
		return "", fmt.Errorf("FFmpeg hatası: %v", err)
	}

	// FFmpeg'in stderr çıktısını yalnızca ilerleme satırlarını gösterecek şekilde işle
	output := stderr.String()
	for _, line := range strings.Split(output, "\n") {
		// İlerleme bilgilerini içeren satırlara odaklanalım (frame, fps, size, time)
		if strings.Contains(line, "frame=") || strings.Contains(line, "fps=") || strings.Contains(line, "size=") || strings.Contains(line, "time=") {
			fmt.Println(line) // Yalnızca ilgili ilerleme bilgisini yazdır
		}
	}

	return mimeType, nil
}

// Komut çalıştırma ve çıktıyı okuma
func executeCommand(cmd *exec.Cmd) error {
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return fmt.Errorf("Error creating stdout pipe: %v", err)
	}

	cmd.Stderr = os.Stderr

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("Error starting command: %v", err)
	}

	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		line := scanner.Text()
		if line != "" {
			fmt.Println(line) // Terminale ilerleme bilgisi yaz
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("Error reading command output: %v", err)
	}

	return cmd.Wait()
}

// Geçici dosyaları temizleme
func cleanupFiles(files []string) {
	for _, file := range files {
		if err := os.Remove(file); err != nil {
			fmt.Printf("Warning: Failed to delete file %s: %v\n", file, err)
		}
	}
}

// MIME tipi algılama
func detectMimeType(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", fmt.Errorf("Dosya açılamadı: %v", err)
	}
	defer file.Close()

	buf := make([]byte, 512)
	if _, err := file.Read(buf); err != nil {
		return "", fmt.Errorf("Dosya okunamadı: %v", err)
	}

	return http.DetectContentType(buf), nil
}

// İşlenen dosyayı GridFS'ye kaydetme
func SaveToGridFS(bucket *gridfs.Bucket, fileID primitive.ObjectID, filePath string, metaData *fiber.Map) (primitive.ObjectID, 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)
}

// isNilOrFalse fonksiyonu, bir değerin nil veya false olup olmadığını kontrol eder
func isNilOrFalse(i interface{}) bool {
	if i == nil {
		return true
	}

	// Type reflection kullanarak türü kontrol et
	v := reflect.ValueOf(i)

	// Eğer boş bir string, false, slice, map, pointer ya da struct ise
	switch v.Kind() {
	case reflect.String:
		return v.String() == "" // Eğer string boşsa
	case reflect.Bool:
		return !v.Bool() // Eğer bool değeri false ise
	case reflect.Ptr:
		return v.IsNil() // Eğer pointer nil ise
	case reflect.Slice, reflect.Map:
		return v.Len() == 0 // Eğer slice veya map boşsa
	}

	return false
}

// AllValuesValid fonksiyonu, bir interface{} içindeki herhangi bir değer boş, nil veya false ise false döner
func AllValuesValid(i interface{}) bool {
	// Eğer değer nil, boş veya false ise, hemen false dönebiliriz
	if isNilOrFalse(i) {
		return false
	}

	// Eğer değer bir slice, map, pointer ya da benzeri bir türse, o zaman elemanları kontrol edebiliriz
	v := reflect.ValueOf(i)

	// Eğer bir slice veya map ise, içindeki her elemanı kontrol et
	if v.Kind() == reflect.Slice || v.Kind() == reflect.Map {
		for _, key := range v.MapKeys() {
			value := v.MapIndex(key)
			if isNilOrFalse(value.Interface()) {
				return false // Eğer herhangi bir değer boş, nil ya da false ise false döner
			}
		}
	}

	// Eğer burada da hiçbir değer boş, nil ya da false değilse true döner
	return true
}
