package service

import (
	"compress/gzip"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"system-altrak/internal/domain"
	"system-altrak/pkg/utils"
	"time"

	"github.com/glebarez/sqlite"
	"github.com/robfig/cron/v3"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type SchedulerService struct {
	cron *cron.Cron
}

func NewSchedulerService() *SchedulerService {
	c := cron.New(cron.WithLocation(time.Local))
	return &SchedulerService{cron: c}
}

func (s *SchedulerService) Start(dbType string, dbDSN string, db *gorm.DB) {
	if dbType != "sqlite" && dbType != "mysql" {
		utils.Log.Info("⚙️ Scheduler cron dinonaktifkan: Tipe database tidak didukung.")
		return
	}

	// Jalankan setiap jam 23:55 malam
	if _, err := s.cron.AddFunc("55 23 * * *", func() {
		utils.Log.Info("⚙️ Memulai Otomasi Snapshot Database...")
		if _, err := s.BackupDatabase(dbType, dbDSN); err != nil {
			utils.Log.Error("Gagal menjalankan snapshot database terjadwal", zap.Error(err))
		}
	}); err != nil {
		utils.Log.Error("Gagal mendaftarkan cron backup harian", zap.Error(err))
		return
	}

	s.cron.Start()
	utils.Log.Info("⏰ Layanan CronJob (Scheduler) diinisialisasi: Snapshot Harian Database Aktif [23:55 Lokal]")

	// Log Cleanup Job (Daily at 01:00 AM)
	if _, err := s.cron.AddFunc("0 1 * * *", func() {
		utils.Log.Info("🧹 Memulai Auto-Cleanup Audit Logs...")
		if db != nil {
			oneYearAgo := time.Now().AddDate(-1, 0, 0)
			res := db.Unscoped().Where("created_at < ?", oneYearAgo).Delete(&domain.ActivityLog{})
			if res.Error != nil {
				utils.Log.Error("Gagal membersihkan Audit Logs", zap.Error(res.Error))
			} else {
				utils.Log.Info(fmt.Sprintf("✅ Auto-Cleanup Selesai: %d log lama dihapus", res.RowsAffected))
			}
		}
	}); err != nil {
		utils.Log.Error("Gagal mendaftarkan cron cleanup logs", zap.Error(err))
	}
}

func (s *SchedulerService) Stop() {
	s.cron.Stop()
}

func (s *SchedulerService) BackupDatabase(dbType string, dbDSN string) (string, error) {
	backupDir := "./backups"
	if err := os.MkdirAll(backupDir, os.ModePerm); err != nil {
		utils.Log.Error("Gagal membuat direktori backup", zap.Error(err))
		return "", err
	}

	timestamp := time.Now().Format("20060102-150405")

	switch dbType {
	case "sqlite":
		backupPath := filepath.Join(backupDir, fmt.Sprintf("snapshot_%s.sqlite", timestamp))
		if err := backupSQLiteSnapshot(dbDSN, backupPath); err != nil {
			utils.Log.Error("Backup Cron Gagal: Snapshot SQLite gagal dibuat", zap.Error(err))
			return "", err
		}
		utils.Log.Info("✅ Backup Cron Sukses: Snapshot SQLite Disimpan", zap.String("path", backupPath))
		return backupPath, nil

	case "mysql":
		user := os.Getenv("DB_USER")
		if user == "" {
			user = "root"
		}
		pass := os.Getenv("DB_PASSWORD")
		host := os.Getenv("DB_HOST")
		if host == "" {
			host = "127.0.0.1"
		}
		port := os.Getenv("DB_PORT")
		if port == "" {
			port = "3306"
		}
		dbName := os.Getenv("DB_NAME")
		if dbName == "" {
			dbName = "spareparts"
		}

		dumpCmd := "mysqldump"
		// Cek folder XAMPP di Windows secara manual untuk portabilitas penuh
		if _, err := os.Stat("C:\\xampp\\mysql\\bin\\mysqldump.exe"); err == nil {
			dumpCmd = "C:\\xampp\\mysql\\bin\\mysqldump.exe"
		}

		backupPath := filepath.Join(backupDir, fmt.Sprintf("snapshot_%s.sql.gz", timestamp))
		args := []string{
			fmt.Sprintf("--user=%s", user),
			fmt.Sprintf("--host=%s", host),
			fmt.Sprintf("--port=%s", port),
		}
		// KEAMANAN: Jangan pernah oper password sebagai argument CLI karena
		// akan terlihat oleh siapapun yang menjalankan `ps aux` di server.
		// Gunakan environment variable MYSQL_PWD yang tidak terekspos di process list.
		args = append(args, dbName)

		cmd := exec.Command(dumpCmd, args...)

		// Set MYSQL_PWD via environment — aman dari process list snooping
		if pass != "" {
			cmd.Env = append(os.Environ(), fmt.Sprintf("MYSQL_PWD=%s", pass))
		} else {
			cmd.Env = os.Environ()
		}

		// Open output file
		outFile, err := os.Create(backupPath)
		if err != nil {
			utils.Log.Error("Backup Cron Gagal: Gagal membuat file backup", zap.Error(err))
			return "", err
		}
		defer outFile.Close()

		gzipWriter := gzip.NewWriter(outFile)
		defer gzipWriter.Close()

		cmd.Stdout = gzipWriter

		var errBuf strings.Builder
		cmd.Stderr = &errBuf

		err = cmd.Run()
		if err != nil {
			// clean up file in case of error
			os.Remove(backupPath)
			utils.Log.Error("Backup Cron Gagal: mysqldump gagal", zap.Error(err), zap.String("stderr", errBuf.String()))
			return "", fmt.Errorf("mysqldump failed: %w (stderr: %s)", err, errBuf.String())
		}

		utils.Log.Info("✅ Backup Cron Sukses: Snapshot MySQL Disimpan (Gzipped)", zap.String("path", backupPath))
		return backupPath, nil

	default:
		utils.Log.Error("Tipe database tidak didukung untuk backup otomatis", zap.String("db_type", dbType))
		return "", fmt.Errorf("tipe database %s tidak didukung", dbType)
	}
}

func backupSQLiteSnapshot(dbDSN, backupPath string) error {
	if strings.HasPrefix(dbDSN, ":memory:") {
		return fmt.Errorf("sqlite in-memory database is not eligible for snapshot backup")
	}

	db, err := gorm.Open(sqlite.Open(dbDSN), &gorm.Config{})
	if err != nil {
		return fmt.Errorf("open sqlite: %w", err)
	}

	sqlDB, err := db.DB()
	if err != nil {
		return fmt.Errorf("open sql db handle: %w", err)
	}
	defer sqlDB.Close()

	if err := db.Exec("PRAGMA wal_checkpoint(FULL)").Error; err != nil {
		return fmt.Errorf("checkpoint wal: %w", err)
	}

	escapedPath := strings.ReplaceAll(filepath.Clean(backupPath), "'", "''")
	stmt := fmt.Sprintf("VACUUM INTO '%s'", escapedPath)
	if err := db.Exec(stmt).Error; err != nil {
		return fmt.Errorf("vacuum into snapshot: %w", err)
	}

	return nil
}
