package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/config_reader/agent"
	"jobarranger2/src/libs/golibs/config_reader/conf"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const titleMessage = "Job Arranger Retry Purge"

var (
	confPathFlag = flag.String("conf", "", "Path to agent config file (required)")
	purgeAll     = flag.Bool("all", false, "Purge all retrying jobs")
	retryCount   = flag.Int("n", 0, "Purge jobs with SendRetry >= n")
	jobidFlag    = flag.String("j", "", "Job ID")
	helpFlag     = flag.Bool("h", false, "Show help")
	versionFlag  = flag.Bool("V", false, "Show version")
)

var (
	transDir string
	dataDir  string
	endDir   string
)

var (
	versionNo  = "1.0.0"
	revisionNo = "dev"
	buildDate  = "unknown"
)

func main() {
	flag.Parse()

	if *helpFlag {
		jobpurgeHelp()
		return
	}
	if *versionFlag {
		jobpurgeVersion()
		return
	}

	if *confPathFlag == "" {
		fmt.Fprintln(os.Stderr, "Error: -conf is required")
		jobpurgeHelp()
		os.Exit(1)
	}

	if *purgeAll && (*retryCount > 0 || strings.TrimSpace(*jobidFlag) != "") {
		fmt.Println("Error: -all cannot be combined with -n or -j")
		jobpurgeHelp()
		os.Exit(1)
	}

	// Determine if any purge filter is provided
	if !*purgeAll && *retryCount == 0 && strings.TrimSpace(*jobidFlag) == "" {
		fmt.Println("Error: Missing required parameters and below is help options")
		jobpurgeHelp()
		os.Exit(1)
	}

	if err := conf.Load(*confPathFlag, &agent.Options); err != nil {
		fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
		os.Exit(1)
	}

	// Use TmpDir from agent.Options
	tmpDir := agent.Options.TmpDir
	if tmpDir == "" {
		fmt.Fprintln(os.Stderr, "TmpDir not defined in config")
		os.Exit(1)
	}
	agentManagerDir := filepath.Join(tmpDir, "agentmanager")
	transDir = filepath.Join(agentManagerDir, "in")
	dataDir = filepath.Join(agentManagerDir, "data")
	endDir = filepath.Join(agentManagerDir, "end")

	fmt.Println("Using transaction directory:", agentManagerDir)

	if err := purgeRetryFiles(); err != nil {
		fmt.Fprintf(os.Stderr, "Error purging retry files: %v\n", err)
		os.Exit(1)
	}

	fmt.Println("Retry purge completed")
}

func purgeRetryFiles() error {
	files, err := os.ReadDir(transDir)
	if err != nil {
		return fmt.Errorf("failed to read transaction directory: %w", err)
	}

	deletedCount := 0
	deletedExecIDs := make(map[string]struct{}) // jobid_timestamp to delete

	for _, f := range files {
		if f.IsDir() || filepath.Ext(f.Name()) != ".json" {
			continue
		}

		fullPath := filepath.Join(transDir, f.Name())

		data, err := os.ReadFile(fullPath)
		if err != nil {
			fmt.Printf("Skip unreadable file: %s\n", fullPath)
			continue
		}

		var eventData common.EventData
		if err := json.Unmarshal(data, &eventData); err != nil {
			fmt.Printf("Skip invalid JSON: %s\n", fullPath)
			continue
		}

		if eventData.TCPMessage == nil || eventData.TCPMessage.Data == nil {
			fmt.Printf("Skip missing tcp_message or data: %s\n", fullPath)
			continue
		}

		jobid := ""
		rawBytes, _ := json.Marshal(eventData.TCPMessage.Data)
		var m map[string]any
		if err := json.Unmarshal(rawBytes, &m); err == nil {
			if v, ok := m["jobid"]; ok {
				switch val := v.(type) {
				case string:
					jobid = val
				case float64:
					jobid = fmt.Sprintf("%.0f", val)
				default:
					jobid = fmt.Sprintf("%v", val)
				}
			}
		}

		// Get SendRetry
		sendRetry := 0
		if eventData.TCPMessage.SendRetry != nil {
			sendRetry = *eventData.TCPMessage.SendRetry
		}

		// Trim spaces and compare jobid safely
		flagJobID := strings.TrimSpace(*jobidFlag)
		jobid = strings.TrimSpace(jobid)

		// Filter by jobid (AND logic)
		if flagJobID != "" && jobid != flagJobID {
			continue
		}

		if *retryCount > 0 && sendRetry < *retryCount {
			continue
		}

		// Delete the transaction file
		if err := os.Remove(fullPath); err != nil {
			fmt.Printf("Failed to delete file=%s: %v\n", fullPath, err)
			continue
		}

		fmt.Printf("Deleted transaction file=%s jobid=%s retry=%d\n", fullPath, jobid, sendRetry)
		deletedCount++

		fileBase := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
		deletedExecIDs[fileBase] = struct{}{}
	}

	// Delete related files in data/ and end/ based on execID
	purgeAgentFiles(dataDir, "data", deletedExecIDs)
	purgeAgentFiles(endDir, "end", deletedExecIDs)

	if deletedCount == 0 {
		fmt.Println("No retry transaction files found to delete")
	}

	return nil
}

func purgeAgentFiles(dir string, label string, execIDs map[string]struct{}) {
	if len(execIDs) == 0 {
		return
	}

	files, err := os.ReadDir(dir)
	if err != nil {
		fmt.Printf("Failed to read agentmanager %s dir: %v\n", label, err)
		return
	}

	for _, f := range files {
		if f.IsDir() {
			continue
		}
		for execID := range execIDs {
			if strings.HasPrefix(f.Name(), execID+"-") || strings.HasPrefix(f.Name(), execID+".") {
				fullPath := filepath.Join(dir, f.Name())
				if err := os.Remove(fullPath); err != nil {
					fmt.Printf("Failed to delete %s file=%s: %v\n", label, fullPath, err)
				} else {
					fmt.Printf("Deleted %s file=%s\n", label, fullPath)
				}
				break
			}
		}
	}
}

func jobpurgeHelp() {
	fmt.Println(titleMessage)
	fmt.Println()
	fmt.Println("Usage: jobretry_purge [options]")
	fmt.Println("Options:")
	fmt.Printf("  %-12s %s\n", "-conf", "Path to agent config file (required)")
	fmt.Printf("  %-12s %s\n", "-all", "Purge all retrying jobs")
	fmt.Printf("  %-12s %s\n", "-n <n>", "Purge jobs with SendRetry >= n")
	fmt.Printf("  %-12s %s\n", "-j <jobid>", "Purge only specified Job ID")
	fmt.Printf("  %-12s %s\n", "-h", "Show help")
	fmt.Printf("  %-12s %s\n", "-V", "Show version")
}

func jobpurgeVersion() {
	fmt.Printf("%s v%s (revision %s) (%s)\n", titleMessage, versionNo, revisionNo, buildDate)
	fmt.Printf("Compilation time: %s\n", time.Now().Format("2006-01-02 15:04:05"))
}
