package FikoYTDosya

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	fiko_mongo "server/sistem/fikoyt_mongo"
	"strconv"
	"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"
)

// FikoYTDosya: Dosya ile ilgili bilgileri tutan struct
type FikoYTDosya struct {
	File          *multipart.FileHeader
	Path          string
	ProcessedPath string
	MimeType      string
	Saved         bool
	Processed     bool
}

// AraIsleyici: Dosyayı alıp FileInfo sınıfına ileten middleware
func AraIsleyici() fiber.Handler {
	return func(c fiber.Ctx) error {
		// Multipart form bilgilerini al
		form, err := c.MultipartForm()
		if err != nil {

			return c.Next()
		}

		var processedFiles []FikoYTDosya

		for _, files := range form.File {
			for _, fileHeader := range files {
				// FileInfo objesi oluştur
				fileInfo := &FikoYTDosya{
					File:          fileHeader,
					Path:          fileHeader.Filename, // Örnek dosya yolu
					ProcessedPath: "",                  // Örnek dosya yolu
					Saved:         false,
					Processed:     false,
				}

				processedFiles = append(processedFiles, *fileInfo)
			}
		}
		// fmt.Println(processedFiles)
		// FileInfo objesini context'e ilet
		c.Locals("files", processedFiles)
		// Ana handler'a yönlendir
		return c.Next()

	}
}

// Kaydet: Dosyayı kaydetme işlemi
func (f *FikoYTDosya) Kaydet() error {
	if f.File == nil {
		return fiber.NewError(fiber.StatusInternalServerError, "File is nil")
	}

	if f.Path == "" {
		return fiber.NewError(fiber.StatusInternalServerError, "Path is empty")
	}

	// Dosyayı aç
	file, err := f.File.Open()
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "Error opening file: "+err.Error())
	}
	defer func() {
		if file != nil {
			file.Close()
		}
	}()

	// Geçici dosya yolu
	tempDir := "./ffmpeg/files"
	if _, err := os.Stat(tempDir); os.IsNotExist(err) {
		if err := os.MkdirAll(tempDir, 0755); err != nil {
			return fiber.NewError(fiber.StatusInternalServerError, "Error creating temp directory: "+err.Error())
		}
	}

	// Geçici dosya oluştur
	f.Path = filepath.Join(tempDir, f.Path)
	tempFile, err := os.Create(f.Path)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "Error creating temp file: "+err.Error())
	}
	defer func() {
		if tempFile != nil {
			tempFile.Close()
		}
	}()

	// Veriyi buffer ile kopyala
	buffer := make([]byte, 1024*1024)
	for {
		n, readErr := file.Read(buffer)
		if n > 0 {
			if _, writeErr := tempFile.Write(buffer[:n]); writeErr != nil {
				return fiber.NewError(fiber.StatusInternalServerError, "Error writing to temp file: "+writeErr.Error())
			}
		}
		if readErr != nil {
			if readErr == io.EOF {
				break
			}
			return fiber.NewError(fiber.StatusInternalServerError, "Error reading file: "+readErr.Error())
		}
	}

	// MIME türünü tespit et
	mimeType, err := detectMimeType(f.Path)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "Error detecting MIME type: "+err.Error())
	}

	if mimeType == "" {
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to detect MIME type")
	}

	f.MimeType = mimeType
	f.Saved = true

	return nil
}

// Save: Dosyayı kaydetme işlemi
func (f *FikoYTDosya) Isle() error {

	// Sıkıştırma işlemi
	outputFile := f.Path + "-processed" + filepath.Ext(f.File.Filename)
	compressWithFFmpeg(f.Path, outputFile)
	f.Processed = true
	f.ProcessedPath = f.Path + "-processed" + filepath.Ext(f.File.Filename)
	return nil
}

// Save: Dosyayı kaydetme işlemi
func (f *FikoYTDosya) KaydetGridFs(metadata fiber.Map) error {

	// İşlenen dosyayı GridFS'ye kaydet
	fileOid, _ := primitive.ObjectIDFromHex(f.File.Filename)
	_, err := saveToGridFS(fileOid, f.Path+"-processed", &metadata)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "Error saving to GridFS: "+err.Error())
	}

	return nil
}

// Save: Dosyayı kaydetme işlemi
func (f *FikoYTDosya) SilGridFs() error {

	// İşlenen dosyayı GridFS'ye kaydet
	fileOid, _ := primitive.ObjectIDFromHex(f.File.Filename)
	err := deleteFromGridfs(fileOid)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "Error saving to GridFS: "+err.Error())
	}

	return nil
}

func (f *FikoYTDosya) TemizleDosyalar() error {
	_, err := os.Stat(f.Path)
	if os.IsNotExist(err) {

	}

	// Dosya varsa, sil
	err = os.Remove(f.Path)
	if err != nil {
	}

	_, err1 := os.Stat(f.ProcessedPath)
	if os.IsNotExist(err1) {

	}

	// Dosya varsa, sil
	err1 = os.Remove(f.ProcessedPath)
	if err1 != nil {
	}
	return nil
}

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
}

/**/
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/ffmpeg.exe"
	ffprobePath := "./ffmpeg/ffprobe.exe"

	if strings.HasPrefix(mimeType, "image") {
		ffmpegArgs = []string{
			"-i", inputPath, // Girdi dosyasını belirtir
			"-vcodec", "mjpeg", // MJPEG formatını kullanır (JPEG için uygun)
			"-q:v", "5", // Kaliteyi belirler (0 en iyi kalite, 31 en kötü kalite)
			"-vf", "scale='min(1920,iw)':'min(1080,ih)':force_original_aspect_ratio=decrease", // Çözünürlüğü orantılı şekilde sınırlar
			"-an",          // Ses çıkışı yok
			"-f", "image2", // Çıktı formatını belirtir (image2, resim için)
			"-hide_banner",       // Banner bilgisini gizler
			"-nostats",           // İstatistik bilgilerini gizler
			"-loglevel", "quiet", // Log seviyesini sessiz yapar
			outputPath, // Çıktı dosya yolu
		}
		// ffmpegArgs = []string{
		// 	"-i", inputPath,
		// 	"-vcodec", "libwebp",
		// 	"-lossless", "0",
		// 	"-q:v", "75",
		// 	"-an",
		// 	"-fps_mode", "auto",
		// 	"-f", "webp",
		// 	"-hide_banner",
		// 	"-nostats",
		// 	"-loglevel", "quiet",
		// 	outputPath,
		// }
	} else if strings.HasPrefix(mimeType, "video") {
		ffmpegArgs = []string{
			"-i", inputPath,
			"-c:v", "h264_nvenc", // NVENC kullan
			"-preset", "p3", // Daha hızlı kodlama (kalitede biraz düşüş)
			"-rc", "vbr_hq", // Kaliteli değişken bit hızı
			"-cq", "30", // Daha yüksek CQ, daha küçük boyut (kalitede biraz düşüş)
			"-b:v", "0", // CQ ile birlikte kullanılır
			"-maxrate", "3000k", // Maksimum bit hızı düşürüldü
			"-bufsize", "6000k", // Tampon boyutu düşürüldü
			"-profile:v", "high",
			"-level:v", "4.1",
			"-vf", "scale=w=854:h=480:force_original_aspect_ratio=decrease", // Çözünürlük düşürüldü (480p)
			"-c:a", "aac", // AAC ses
			"-b:a", "64k", // Ses bit hızı düşürüldü
			"-f", "mp4", // MP4 çıktı
			"-hide_banner",       // Banner gizle
			"-nostats",           // İstatistikleri gizle
			"-loglevel", "debug", // Hata ayıklama seviyesi
			outputPath,
		}
		// // Düşük Kalite
		// ffmpegArgs = []string{
		// 	"-i", inputPath,
		// 	"-c:v", "h264_nvenc",
		// 	"-preset", "ultrafast", // En hızlı ön ayar
		// 	"-rc", "vbr_hq", // Kaliteli değişken bit hızı
		// 	"-cq", "35", // En yüksek CQ (en küçük boyut)
		// 	"-b:v", "0", // CQ ile birlikte kullanılır
		// 	"-maxrate", "1000k", // Maksimum bit hızı çok düşürüldü
		// 	"-bufsize", "2000k", // Tampon boyutu çok düşürüldü
		// 	"-profile:v", "baseline", // Profil düşürüldü
		// 	"-level:v", "3.1", // Level düşürüldü
		// 	"-vf", "scale=w=640:h=360:force_original_aspect_ratio=decrease", // Çözünürlük çok düşürüldü (360p)
		// 	"-c:a", "aac", // AAC ses
		// 	"-b:a", "32k", // Ses bit hızı çok düşürüldü
		// 	"-f", "mp4", // MP4 çıktı
		// 	"-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 {
		return "", fmt.Errorf("Desteklenmeyen MIME türü: %s", mimeType)
	}

	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
}

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
}

/**/

/**/

// 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()
}

// İşlenen dosyayı GridFS'ye kaydetme
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)
}
