package FikoYTDosya

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/gofiber/fiber/v3"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

// FikoYTDosya: Dosya ile ilgili bilgileri tutan struct
type FikoYTDosya struct {
	File           *multipart.FileHeader
	Id             primitive.ObjectID
	ThumbId        primitive.ObjectID
	Path           string
	ProcessedPath  string
	ThumbPath      string // Thumbnail dosyasının yolu
	MimeType       string
	Saved          bool
	Processed      bool
	ThumbGenerated bool // Thumbnail üretildi mi?
}

// 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.Id.Hex()+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, 5*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
}

// Isle: Dosyayı işleme fonksiyonu (sıkıştırma + thumbnail üretimi)
func (f *FikoYTDosya) Isle() error {
	// Sıkıştırma işlemi
	outputFile := f.Path + filepath.Ext(f.File.Filename)
	compressWithFFmpeg(f.Path, outputFile)
	f.Processed = true
	f.ProcessedPath = outputFile

	// Video ise ve ThumbId boş ise thumbnail üret (hata olsa bile devam et)
	if f.isVideo() && f.ThumbId.IsZero() {
		f.generateThumbnail() // Hata olsa bile devam et
	}

	return nil
}

// isVideo: Dosyanın video olup olmadığını kontrol eder
func (f *FikoYTDosya) isVideo() bool {
	return strings.HasPrefix(f.MimeType, "video/")
}

// generateThumbnail: Video için thumbnail üretir (hata olsa bile sessizce devam eder)
func (f *FikoYTDosya) generateThumbnail() {
	if !f.isVideo() {
		return // Video değilse sessizce çık
	}

	// Thumbnail için dosya yolu oluştur
	tempDir := "./ffmpeg/files"
	f.ThumbId = primitive.NewObjectID()
	f.ThumbPath = filepath.Join(tempDir, f.ThumbId.Hex()+"_thumb.jpg")

	// FFmpeg argümanları - sizin formatınıza uygun
	ffmpegArgs := []string{
		"-i", f.ProcessedPath, // Girdi dosyasını belirtir
		"-ss", "00:00:01", // 1. saniyeden frame al
		"-vframes", "1", // Sadece 1 frame al
		"-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
		"-y",        // Varolan dosyayı üzerine yaz
		f.ThumbPath, // Çıktı dosya yolu
	}

	// FFmpeg komutunu çalıştır
	cmd := exec.Command("ffmpeg", ffmpegArgs...)

	// Hata olsa bile sessizce devam et
	err := cmd.Run()
	if err != nil {
		// Thumbnail üretilemedi, ID'yi sıfırla ve devam et
		f.ThumbId = primitive.NilObjectID
		f.ThumbPath = ""
		return
	}

	// Thumbnail dosyasının oluşturulup oluşturulmadığını kontrol et
	if _, err := os.Stat(f.ThumbPath); os.IsNotExist(err) {
		// Dosya oluşmadı, ID'yi sıfırla ve devam et
		f.ThumbId = primitive.NilObjectID
		f.ThumbPath = ""
		return
	}

	f.ThumbGenerated = true
}

// KaydetGridFs: Ana dosyayı GridFS'ye kaydet
func (f *FikoYTDosya) KaydetGridFs(metadata fiber.Map) error {
	// İşlenen dosyayı GridFS'ye kaydet
	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
}

// KaydetThumbGridFs: Thumbnail'ı GridFS'ye kaydet (hata olsa bile sessizce devam eder)
func (f *FikoYTDosya) KaydetThumbGridFs(metadata fiber.Map) error {
	if !f.ThumbGenerated || f.ThumbPath == "" {
		// Thumbnail yoksa sessizce geç
		return nil
	}

	// Thumbnail metadata'sına özel bilgiler ekle
	if metadata == nil {
		metadata = fiber.Map{}
	}
	metadata["type"] = "thumbnail"
	metadata["parent_file_id"] = f.Id.Hex()
	metadata["mime_type"] = "image/jpeg"

	// Thumbnail'ı GridFS'ye kaydet - hata olsa bile ana işlemi etkilemesin
	_, err := saveToGridFS(f.ThumbId, f.ThumbPath, &metadata)
	if err != nil {
		// Thumbnail kaydedilemedi, ID'yi sıfırla ama hata döndürme
		f.ThumbId = primitive.NilObjectID
		f.ThumbGenerated = false
		return nil
	}

	return nil
}

// SilGridFs: Ana dosyayı GridFS'den sil
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
}

// SilThumbGridFs: Thumbnail'ı GridFS'den sil (hata olsa bile sessizce devam eder)
func (f *FikoYTDosya) SilThumbGridFs() error {
	if f.ThumbId.IsZero() {
		return nil // Thumbnail yoksa silmeye gerek yok
	}

	// Hata olsa bile sessizce devam et
	DeleteFromGridfs(f.ThumbId)
	return nil
}

// TemizleDosyalar: Geçici dosyaları temizle
func (f *FikoYTDosya) TemizleDosyalar() error {
	// Ana dosyayı sil
	if _, err := os.Stat(f.Path); err == nil {
		os.Remove(f.Path)
	}

	// İşlenmiş dosyayı sil
	if _, err := os.Stat(f.ProcessedPath); err == nil {
		os.Remove(f.ProcessedPath)
	}

	// Thumbnail dosyasını sil
	if f.ThumbPath != "" {
		if _, err := os.Stat(f.ThumbPath); err == nil {
			os.Remove(f.ThumbPath)
		}
	}

	return nil
}

// GetThumbnailId: Thumbnail ID'sini döndür
func (f *FikoYTDosya) GetThumbnailId() primitive.ObjectID {
	return f.ThumbId
}

// HasThumbnail: Thumbnail var mı kontrol et
func (f *FikoYTDosya) HasThumbnail() bool {
	return f.ThumbGenerated && !f.ThumbId.IsZero()
}

// detectMimeType dosyanın MIME türünü belirler.
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
	}
}

func detectMimeType1(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
}
