package utils

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/config"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"

	"go.uber.org/zap"
)

// CleanFiles Clean files, including local log files and database files
// Only delete database files from two days ago or earlier
func CleanFiles(orgId string) {
	if config.IsDev() {
		return
	}

	// Delete local log files
	cleanLocalLogFiles(orgId)

	// Delete DB files from two days ago or earlier
	cleanOldDbFiles(orgId)
}

// cleanLocalLogFiles Clean local log files
func cleanLocalLogFiles(orgId string) {
	// logfile directory
	// logFileDir := fmt.Sprintf("%s/%s", config.AppConfig.InputDir, day)
	logFileDir := config.GetOrgS3FileDir(orgId)

	if err := os.RemoveAll(logFileDir); err != nil {
		log.PlainError("Warning: Failed to remove local directory", zap.Error(err))
	}
	log.PlainInfo("remove local logfile directory: " + logFileDir)
}

// cleanOldDbFiles Clean database files from two days ago or earlier
func cleanOldDbFiles(orgId string) {
	// Get current date
	currentDay := config.AppConfig.Day

	// Parse current date
	currentTime, err := time.Parse("20060102", currentDay)
	if err != nil {
		log.PlainError("Failed to parse current day", zap.Error(err))
		return
	}

	// Calculate the date two days ago
	for i := 0; i <= 2; i++ {
		twoDaysAgo := currentTime.AddDate(0, 0, -i)
		deleteDay := twoDaysAgo.Format("20060102")
		dbDir := config.GetOrgDbDir(orgId, deleteDay)

		// Delete the database directory for this organization on that date
		if err := os.RemoveAll(dbDir); err != nil {
			log.PlainError("Warning: Failed to remove database file", zap.String("dir", dbDir), zap.Error(err))
		} else {
			log.PlainInfo("Removed database directory: " + dbDir)

			// Check if parent directory is empty, delete if empty
			parentDir := fmt.Sprintf("%s/%s", config.AppConfig.Database.DbPath, deleteDay)
			removeEmptyDir(parentDir)
		}
	}
}

// removeEmptyDir Delete empty directory
func removeEmptyDir(dirPath string) {
	// Check if directory exists
	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
		return
	}

	// Read directory contents
	entries, err := os.ReadDir(dirPath)
	if err != nil {
		log.PlainError("Failed to read directory", zap.String("dir", dirPath), zap.Error(err))
		return
	}

	// Delete if directory is empty
	if len(entries) == 0 {
		if err := os.Remove(dirPath); err != nil {
			log.PlainError("Failed to remove empty directory", zap.String("dir", dirPath), zap.Error(err))
		} else {
			log.PlainInfo("Removed empty parent directory: " + dirPath)
		}
	}
}

// CleanOldDayDbFiles Clean database files from two days ago or earlier
func CleanOldDayDbFiles() {
	// Get current date
	currentDay := config.AppConfig.Day

	// Parse current date
	currentTime, err := time.Parse("20060102", currentDay)
	if err != nil {
		log.PlainError("Failed to parse current day", zap.Error(err))
		return
	}

	// Calculate the date two days ago
	for i := 1; i <= 2; i++ {
		twoDaysAgo := currentTime.AddDate(0, 0, -i)
		deleteDay := twoDaysAgo.Format("20060102")
		dbDir := fmt.Sprintf("%s/%s", config.AppConfig.Database.DbPath, deleteDay)

		// Delete the database directory for this organization on that date
		if err := os.RemoveAll(dbDir); err != nil {
			log.PlainError("Warning: Failed to remove database file", zap.String("dir", dbDir), zap.Error(err))
		} else {
			log.PlainInfo("Removed database directory: " + dbDir)

			// Since the entire date directory has been deleted, no need to check if empty
		}
	}
}

// EnsureDir checks if a directory exists, and creates it if it does not.
func EnsureDir(path string) error {
	// Check if the directory exists
	if _, err := os.Stat(path); os.IsNotExist(err) {
		// Create the directory and all parent directories
		if err := os.MkdirAll(path, 0755); err != nil {
			return fmt.Errorf("failed to create directory %s: %w", path, err)
		}
		log.PlainInfo("Directory created: " + path)
	} else if err != nil {
		return fmt.Errorf("failed to check directory %s: %w", path, err)
	} else {
		log.PlainInfo("Directory already exists: " + path)
	}
	return nil
}

func FileExists(path string) bool {
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		// File does not exist
		return false
	}
	// Return false if it's a directory
	if err == nil && info.IsDir() {
		return false
	}
	// File exists
	return err == nil
}

func DeleteOldFiles(root string, maxAge time.Duration) error {
	now := time.Now()
	var emptyDirs []string // Used to collect empty directory paths

	err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
		if err != nil {
			log.PlainError("Failed to walk path:", zap.String("path", path), zap.Error(err))
			return nil // Ignore this error and continue traversal
		}

		if d.IsDir() {
			entries, err := os.ReadDir(path)
			if err != nil {
				log.PlainError("Failed to read directory:", zap.String("path", path), zap.Error(err))
				return nil
			}
			// If directory is empty, record it and delete later
			if len(entries) == 0 && path != root {
				emptyDirs = append(emptyDirs, path)
			}
			return nil
		}

		// File processing logic
		info, err := d.Info()
		if err != nil {
			log.PlainError("Failed to get file info:", zap.String("path", path), zap.Error(err))
			return nil
		}

		if now.Sub(info.ModTime()) > maxAge {
			log.PlainInfo("Deleting old file:", zap.String("path", path))
			if err := os.Remove(path); err != nil {
				log.PlainError("Failed to delete old file:", zap.String("path", path), zap.Error(err))
			}
		}
		return nil
	})

	if err != nil {
		return err
	}

	// After traversal completed, delete empty directories in reverse order (delete children first, then parents)
	for i := len(emptyDirs) - 1; i >= 0; i-- {
		path := emptyDirs[i]
		entries, _ := os.ReadDir(path)
		if len(entries) == 0 {
			log.PlainInfo("Deleting empty folder:", zap.String("path", path))
			if err := os.Remove(path); err != nil {
				log.PlainError("Failed to delete empty folder:", zap.String("path", path), zap.Error(err))
			}
		}
	}

	return nil
}

// DeleteFilesInDir deletes all files inside the given directory (excluding subdirectories).
// dir: directory path to clean.
func DeleteFilesInDir(dir string) error {
	// Read directory entries
	entries, err := os.ReadDir(dir)
	if err != nil {
		return fmt.Errorf("failed to read directory: %w", err)
	}

	// Iterate directory entries
	for _, entry := range entries {
		if entry.IsDir() {
			// Skip subdirectories
			continue
		}

		// Build full file path
		filePath := filepath.Join(dir, entry.Name())

		// Remove the file
		if err := os.Remove(filePath); err != nil {
			return fmt.Errorf("failed to remove file %s: %w", filePath, err)
		}
	}

	return nil
}
