package controllers

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"fotbal-club/internal/models"
	"fotbal-club/internal/services"
	"fotbal-club/pkg/logger"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type FilesController struct {
	DB *gorm.DB
}

// FileInfo represents detailed file information with usage tracking
type FileInfo struct {
	ID           uint                    `json:"id"`
	Filename     string                  `json:"filename"`
	FilePath     string                  `json:"file_path"`
	FileURL      string                  `json:"file_url"`
	FileSize     int64                   `json:"file_size"`
	MimeType     string                  `json:"mime_type"`
	UploadedBy   *models.User            `json:"uploaded_by,omitempty"`
	CreatedAt    string                  `json:"created_at"`
	Usages       []models.FileUsage      `json:"usages,omitempty"`
	UsageCount   int                     `json:"usage_count"`
	MD5Hash      string                  `json:"md5_hash,omitempty"`
}

// GetAllFiles returns all uploaded files with usage information
func (fc *FilesController) GetAllFiles(c *gin.Context) {
	var files []models.UploadedFile
	
	query := fc.DB.Preload("UploadedBy").Preload("Usages")
	
	// Optional filtering
	if search := c.Query("search"); search != "" {
		query = query.Where("filename ILIKE ?", "%"+search+"%")
	}
	
	if mimeType := c.Query("mime_type"); mimeType != "" {
		query = query.Where("mime_type LIKE ?", mimeType+"%")
	}
	
	// Sorting
	sortBy := c.DefaultQuery("sort_by", "created_at")
	sortOrder := c.DefaultQuery("sort_order", "desc")
	query = query.Order(fmt.Sprintf("%s %s", sortBy, sortOrder))
	
	if err := query.Find(&files).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch files"})
		return
	}
	
	// Convert to FileInfo format
	fileInfos := make([]FileInfo, len(files))
	for i, file := range files {
		fileInfos[i] = FileInfo{
			ID:         file.ID,
			Filename:   file.Filename,
			FilePath:   file.FilePath,
			FileURL:    file.FileURL,
			FileSize:   file.FileSize,
			MimeType:   file.MimeType,
			UploadedBy: file.UploadedBy,
			CreatedAt:  file.CreatedAt.Format("2006-01-02 15:04:05"),
			Usages:     file.Usages,
			UsageCount: len(file.Usages),
		}
	}
	
	c.JSON(http.StatusOK, fileInfos)
}

// GetUnusedFiles returns files that have no usage records
func (fc *FilesController) GetUnusedFiles(c *gin.Context) {
	var files []models.UploadedFile
	
	// Find files with no usages
	if err := fc.DB.Preload("UploadedBy").
		Preload("Usages").
		Joins("LEFT JOIN file_usages ON file_usages.file_id = uploaded_files.id").
		Where("file_usages.id IS NULL").
		Group("uploaded_files.id").
		Order("uploaded_files.created_at DESC").
		Find(&files).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch unused files"})
		return
	}
	
	fileInfos := make([]FileInfo, len(files))
	for i, file := range files {
		fileInfos[i] = FileInfo{
			ID:         file.ID,
			Filename:   file.Filename,
			FilePath:   file.FilePath,
			FileURL:    file.FileURL,
			FileSize:   file.FileSize,
			MimeType:   file.MimeType,
			UploadedBy: file.UploadedBy,
			CreatedAt:  file.CreatedAt.Format("2006-01-02 15:04:05"),
			Usages:     file.Usages,
			UsageCount: 0,
		}
	}
	
	c.JSON(http.StatusOK, fileInfos)
}

// GetDuplicateFiles finds files with the same content (based on MD5 hash)
func (fc *FilesController) GetDuplicateFiles(c *gin.Context) {
	var files []models.UploadedFile
	
	if err := fc.DB.Preload("UploadedBy").Preload("Usages").Find(&files).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch files"})
		return
	}
	
	// Calculate MD5 hashes and group by hash
	hashMap := make(map[string][]FileInfo)
	
	for _, file := range files {
		// Calculate MD5 hash of file content
		hash, err := calculateFileMD5(file.FilePath)
		if err != nil {
			logger.Warn("Failed to calculate hash for %s: %v", file.FilePath, err)
			continue
		}
		
		fileInfo := FileInfo{
			ID:         file.ID,
			Filename:   file.Filename,
			FilePath:   file.FilePath,
			FileURL:    file.FileURL,
			FileSize:   file.FileSize,
			MimeType:   file.MimeType,
			UploadedBy: file.UploadedBy,
			CreatedAt:  file.CreatedAt.Format("2006-01-02 15:04:05"),
			Usages:     file.Usages,
			UsageCount: len(file.Usages),
			MD5Hash:    hash,
		}
		
		hashMap[hash] = append(hashMap[hash], fileInfo)
	}
	
	// Filter only duplicates (groups with more than one file)
	duplicates := make(map[string][]FileInfo)
	for hash, files := range hashMap {
		if len(files) > 1 {
			duplicates[hash] = files
		}
	}
	
	c.JSON(http.StatusOK, duplicates)
}

// DeleteFile deletes a file and its usage records
func (fc *FilesController) DeleteFile(c *gin.Context) {
	fileID := c.Param("id")
	
	var file models.UploadedFile
	if err := fc.DB.Preload("Usages").First(&file, fileID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "File not found"})
		} else {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch file"})
		}
		return
	}
	
	// Return usage information if file is being used and force flag is not set
	force := c.Query("force") == "true"
	if len(file.Usages) > 0 && !force {
		c.JSON(http.StatusConflict, gin.H{
			"error":   "File is in use",
			"usages":  file.Usages,
			"message": "This file is being used. Use force=true to delete anyway.",
		})
		return
	}
	
	// Delete physical file
	if err := os.Remove(file.FilePath); err != nil {
		logger.Warn("Failed to delete physical file %s: %v", file.FilePath, err)
		// Continue with database deletion even if physical file deletion fails
	}
	
	// Delete database record (cascades to usages)
	if err := fc.DB.Delete(&file).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete file record"})
		return
	}
	
	c.JSON(http.StatusOK, gin.H{"message": "File deleted successfully"})
}

// GetFileUsages returns where a specific file is being used
func (fc *FilesController) GetFileUsages(c *gin.Context) {
	fileID := c.Param("id")
	
	var usages []models.FileUsage
	if err := fc.DB.Where("file_id = ?", fileID).Find(&usages).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch file usages"})
		return
	}
	
	// Enrich usage data with entity information
	enrichedUsages := make([]map[string]interface{}, len(usages))
	for i, usage := range usages {
		entityInfo := getEntityInfo(fc.DB, usage.EntityType, usage.EntityID)
		enrichedUsages[i] = map[string]interface{}{
			"id":          usage.ID,
			"entity_type": usage.EntityType,
			"entity_id":   usage.EntityID,
			"field_name":  usage.FieldName,
			"entity_info": entityInfo,
		}
	}
	
	c.JSON(http.StatusOK, enrichedUsages)
}

// ScanAndSyncFiles scans the uploads directory and syncs with database
func (fc *FilesController) ScanAndSyncFiles(c *gin.Context) {
	uploadsDir := "uploads"
	
	var filesInDB []models.UploadedFile
	if err := fc.DB.Find(&filesInDB).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch database files"})
		return
	}
	
	dbFileMap := make(map[string]models.UploadedFile)
	for _, file := range filesInDB {
		dbFileMap[file.FilePath] = file
	}
	
	foundFiles := 0
	newFiles := 0
	orphanedFiles := 0
	skippedFiles := 0
	newFilesList := []string{}
	orphanedFilesList := []string{}
	
	// Walk through uploads directory
	err := filepath.Walk(uploadsDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			logger.Warn("Error accessing path %s: %v", path, err)
			return nil // Continue with other files
		}
		
		if info.IsDir() {
			return nil
		}
		
		// Skip .gitkeep and hidden files
		filename := filepath.Base(path)
		if filename == ".gitkeep" || strings.HasPrefix(filename, ".") {
			skippedFiles++
			return nil
		}
		
		foundFiles++
		
		// Normalize path separators for cross-platform compatibility
		normalizedPath := filepath.ToSlash(path)
		if filepath.Separator == '\\' {
			// On Windows, convert backslashes to forward slashes
			normalizedPath = strings.ReplaceAll(path, "\\", "/")
		}
		
		// Check both original path and normalized path
		_, existsOriginal := dbFileMap[path]
		_, existsNormalized := dbFileMap[normalizedPath]
		
		if !existsOriginal && !existsNormalized {
			// File exists on disk but not in database - add it
			mimeType := detectMimeType(path)
			fileURL := "/" + filepath.ToSlash(path)
			
			newFile := models.UploadedFile{
				Filename: filename,
				FilePath: normalizedPath,
				FileURL:  fileURL,
				FileSize: info.Size(),
				MimeType: mimeType,
			}
			
			if err := fc.DB.Create(&newFile).Error; err != nil {
				logger.Warn("Failed to create database record for %s: %v", path, err)
			} else {
				newFiles++
				newFilesList = append(newFilesList, filename)
				logger.Info("Added new file to database: %s", filename)
			}
		}
		
		return nil
	})
	
	if err != nil {
		logger.Error("Failed to scan directory: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to scan directory"})
		return
	}
	
	// Check for orphaned database records (files in DB but not on disk)
	for _, dbFile := range filesInDB {
		// Try both original path and with backslashes (for Windows)
		paths := []string{dbFile.FilePath, strings.ReplaceAll(dbFile.FilePath, "/", "\\")}
		found := false
		for _, p := range paths {
			if _, err := os.Stat(p); err == nil {
				found = true
				break
			}
		}
		if !found {
			orphanedFiles++
			orphanedFilesList = append(orphanedFilesList, dbFile.Filename)
			// Optionally delete orphaned records
			// fc.DB.Delete(&dbFile)
		}
	}
	
	logger.Info("Scan completed: %d found, %d new, %d orphaned, %d skipped", foundFiles, newFiles, orphanedFiles, skippedFiles)
	
	c.JSON(http.StatusOK, gin.H{
		"message":         "Scan completed",
		"found_files":     foundFiles,
		"new_files":       newFiles,
		"orphaned_files":  orphanedFiles,
		"skipped_files":   skippedFiles,
		"new_files_list":  newFilesList,
		"orphaned_list":   orphanedFilesList,
	})
}

// Helper function to calculate MD5 hash of a file
func calculateFileMD5(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return "", err
	}
	defer file.Close()
	
	hash := md5.New()
	if _, err := io.Copy(hash, file); err != nil {
		return "", err
	}
	
	return hex.EncodeToString(hash.Sum(nil)), nil
}

// Helper function to detect MIME type from file extension
func detectMimeType(filePath string) string {
	ext := strings.ToLower(filepath.Ext(filePath))
	mimeTypes := map[string]string{
		// Images
		".jpg":  "image/jpeg",
		".jpeg": "image/jpeg",
		".png":  "image/png",
		".gif":  "image/gif",
		".svg":  "image/svg+xml",
		".webp": "image/webp",
		".bmp":  "image/bmp",
		".ico":  "image/x-icon",
		// Documents
		".pdf":  "application/pdf",
		".doc":  "application/msword",
		".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
		".xls":  "application/vnd.ms-excel",
		".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
		".ppt":  "application/vnd.ms-powerpoint",
		".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
		".txt":  "text/plain",
		".csv":  "text/csv",
		// Archives
		".zip":  "application/zip",
		".rar":  "application/x-rar-compressed",
		".7z":   "application/x-7z-compressed",
		".tar":  "application/x-tar",
		".gz":   "application/gzip",
		// Media
		".mp4":  "video/mp4",
		".avi":  "video/x-msvideo",
		".mov":  "video/quicktime",
		".mp3":  "audio/mpeg",
		".wav":  "audio/wav",
	}
	
	if mime, ok := mimeTypes[ext]; ok {
		return mime
	}
	return "application/octet-stream"
}

// Helper function to get entity information
func getEntityInfo(db *gorm.DB, entityType string, entityID uint) map[string]interface{} {
	info := map[string]interface{}{
		"type": entityType,
		"id":   entityID,
	}
	
	switch entityType {
	case "article":
		var article models.Article
		if err := db.Select("id", "title", "slug").First(&article, entityID).Error; err == nil {
			info["title"] = article.Title
			info["slug"] = article.Slug
			info["url"] = fmt.Sprintf("/news/%s", article.Slug)
		}
	case "player":
		var player models.Player
		if err := db.Select("id", "first_name", "last_name").First(&player, entityID).Error; err == nil {
			info["name"] = fmt.Sprintf("%s %s", player.FirstName, player.LastName)
			info["url"] = fmt.Sprintf("/hraci/%d", player.ID)
		}
	case "sponsor":
		var sponsor models.Sponsor
		if err := db.Select("id", "name").First(&sponsor, entityID).Error; err == nil {
			info["name"] = sponsor.Name
		}
	case "event":
		var event models.Event
		if err := db.Select("id", "title").First(&event, entityID).Error; err == nil {
			info["title"] = event.Title
			info["url"] = fmt.Sprintf("/aktivita/%d", event.ID)
		}
	case "contact":
		var contact models.Contact
		if err := db.Select("id", "name", "position").First(&contact, entityID).Error; err == nil {
			info["name"] = contact.Name
			info["position"] = contact.Position
		}
	}
	
	return info
}

// RefreshFileTracking re-scans all entities and updates file usage tracking
func (fc *FilesController) RefreshFileTracking(c *gin.Context) {
	entityType := c.Query("entity_type") // Optional: "article", "event", "player", etc.
	
	stats := map[string]int{
		"articles_scanned": 0,
		"events_scanned":   0,
		"players_scanned":  0,
		"sponsors_scanned": 0,
		"contacts_scanned": 0,
		"teams_scanned":    0,
		"settings_scanned": 0,
	}
	
	fileTracker := services.NewFileTracker(fc.DB)
	
	// Refresh articles
	if entityType == "" || entityType == "article" {
		var articles []models.Article
		if err := fc.DB.Find(&articles).Error; err == nil {
			for _, article := range articles {
				fileTracker.TrackArticleFiles(&article)
				stats["articles_scanned"]++
			}
			logger.Info("Refreshed file tracking for %d articles", len(articles))
		}
	}
	
	// Refresh events
	if entityType == "" || entityType == "event" {
		var events []models.Event
		if err := fc.DB.Preload("Attachments").Find(&events).Error; err == nil {
			for _, event := range events {
				fileTracker.TrackEventFiles(&event)
				stats["events_scanned"]++
			}
			logger.Info("Refreshed file tracking for %d events", len(events))
		}
	}
	
	// Refresh players
	if entityType == "" || entityType == "player" {
		var players []models.Player
		if err := fc.DB.Find(&players).Error; err == nil {
			for _, player := range players {
				fileTracker.TrackPlayerFiles(&player)
				stats["players_scanned"]++
			}
			logger.Info("Refreshed file tracking for %d players", len(players))
		}
	}
	
	// Refresh sponsors
	if entityType == "" || entityType == "sponsor" {
		var sponsors []models.Sponsor
		if err := fc.DB.Find(&sponsors).Error; err == nil {
			for _, sponsor := range sponsors {
				fileTracker.TrackSponsorFiles(&sponsor)
				stats["sponsors_scanned"]++
			}
			logger.Info("Refreshed file tracking for %d sponsors", len(sponsors))
		}
	}
	
	// Refresh contacts
	if entityType == "" || entityType == "contact" {
		var contacts []models.Contact
		if err := fc.DB.Find(&contacts).Error; err == nil {
			for _, contact := range contacts {
				fileTracker.TrackContactFiles(&contact)
				stats["contacts_scanned"]++
			}
			logger.Info("Refreshed file tracking for %d contacts", len(contacts))
		}
	}
	
	// Refresh teams
	if entityType == "" || entityType == "team" {
		var teams []models.Team
		if err := fc.DB.Find(&teams).Error; err == nil {
			for _, team := range teams {
				fileTracker.TrackTeamFiles(&team)
				stats["teams_scanned"]++
			}
			logger.Info("Refreshed file tracking for %d teams", len(teams))
		}
	}
	
	// Refresh settings
	if entityType == "" || entityType == "settings" {
		var settings models.Settings
		if err := fc.DB.First(&settings).Error; err == nil {
			fileTracker.TrackSettingsFiles(&settings)
			stats["settings_scanned"]++
			logger.Info("Refreshed file tracking for settings")
		}
	}
	
	c.JSON(http.StatusOK, gin.H{
		"message": "File tracking refreshed successfully",
		"stats":   stats,
	})
}
