package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	// Define the --path and --patches flags
	rootPath := flag.String("path", ".", "Root path for patch application")
	patchSource := flag.String("patches", "./patches", "File or directory containing patch(es)")
	flag.Parse()

	// Convert root path to an absolute path
	absoluteRootPath, err := filepath.Abs(*rootPath)
	if err != nil {
		log.Fatalf("Failed to resolve absolute path for root: %v", err)
	}

	// Resolve patches path (handle both absolute and relative paths)
	absolutePatchesPath := *patchSource
	if !filepath.IsAbs(absolutePatchesPath) {
		absolutePatchesPath = filepath.Clean(filepath.Join(absoluteRootPath, *patchSource))
	}

	fmt.Printf("Starting custom patcher...\nRoot path: %s\nPatches source: %s\n", absoluteRootPath, absolutePatchesPath)

	var successfulPatches, failedPatches []string

	// Determine if --patches is a directory or a file
	info, err := os.Stat(absolutePatchesPath)
	if err != nil {
		log.Fatalf("Failed to access patch source: %v", err)
	}

	if info.IsDir() {
		// Walk through directory to apply all patches
		err = filepath.Walk(absolutePatchesPath, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if !info.IsDir() && strings.HasSuffix(info.Name(), ".patch") {
				applyPatchWrapper(path, absoluteRootPath, &successfulPatches, &failedPatches)
			}
			return nil
		})
		if err != nil {
			log.Fatalf("Error reading patches from directory: %v", err)
		}
	} else {
		// Single file provided
		if strings.HasSuffix(info.Name(), ".patch") {
			applyPatchWrapper(absolutePatchesPath, absoluteRootPath, &successfulPatches, &failedPatches)
		} else {
			log.Fatalf("Provided patch file is not a .patch file")
		}
	}

	// Print the summary
	fmt.Println("\nSummary:")
	fmt.Printf("Successful patches (%d):\n", len(successfulPatches))
	for _, patch := range successfulPatches {
		fmt.Printf("  - %s\n", patch)
	}
	fmt.Printf("Failed patches (%d):\n", len(failedPatches))
	for _, patch := range failedPatches {
		fmt.Printf("  - %s\n", patch)
	}

	// Exit with appropriate status
	if len(failedPatches) > 0 {
		fmt.Println("\nPatch process completed with failures.")
		os.Exit(1)
	} else {
		fmt.Println("\nAll patches applied successfully.")
		os.Exit(0)
	}
}

// applyPatchWrapper wraps the patch application and updates results
func applyPatchWrapper(patchPath, rootPath string, successfulPatches, failedPatches *[]string) {
	fmt.Printf("Applying patch: %s\n", patchPath)
	err := applyPatch(patchPath, rootPath)
	if err != nil {
		errorMsg := fmt.Sprintf("Failed to apply patch '%s': %v", patchPath, err)
		fmt.Println(errorMsg)
		*failedPatches = append(*failedPatches, errorMsg) // Log detailed error message
	} else {
		fmt.Printf("Successfully applied patch: %s\n", patchPath)
		*successfulPatches = append(*successfulPatches, patchPath)
	}
}

// applyPatch processes a single patch file
func applyPatch(patchPath, rootPath string) error {
	file, err := os.Open(patchPath)
	if err != nil {
		return fmt.Errorf("failed to open patch file: %v", err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	var inputFilePath, outputFilePath string
	var modifications []string
	var patchType string

	// Parse the patch file
	for scanner.Scan() {
		line := scanner.Text()

		// Check for header type
		if strings.HasPrefix(line, "t:") && patchType == "" {
			patchType = strings.TrimSpace(strings.TrimPrefix(line, "t:"))
			fmt.Printf("Detected patch type: %s\n", patchType)
			continue
		}

		// Detect the input file
		if strings.HasPrefix(line, "i:") {
			relativePath := strings.TrimSpace(strings.TrimPrefix(line, "i:"))
			inputFilePath = filepath.Join(rootPath, relativePath)
			continue
		}

		// Detect the output file
		if strings.HasPrefix(line, "o:") {
			relativePath := strings.TrimSpace(strings.TrimPrefix(line, "o:"))
			outputFilePath = filepath.Join(rootPath, relativePath)
			continue
		}

		// Ignore comment lines
		if strings.HasPrefix(strings.TrimSpace(line), "//") {
			continue
		}

		// Collect modifications
		modifications = append(modifications, line)
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("failed to read patch file: %v", err)
	}

	if inputFilePath == "" && patchType != "new" {
		return fmt.Errorf("patch file must specify input (i:) file")
	}

	if outputFilePath == "" {
		return fmt.Errorf("patch file must specify output (o:) file")
	}

	if patchType != "new" && patchType != "copy" {
		err = os.Rename(inputFilePath, outputFilePath)
		if err != nil {
			return fmt.Errorf("failed to replace output file: %v", err)
		}
	}

	// Process based on patch type
	switch patchType {
	case "pref":
		return applyPrefModifications(outputFilePath, modifications)
	case "standard":
		return applyStandardModifications(outputFilePath, modifications)
	case "new":
		return applyNewModifications(outputFilePath, modifications)
	case "copy":
		return applyCopyPatch(inputFilePath, outputFilePath)
	default:
		fmt.Printf("Type not specified defaulting to standard")
		return applyStandardModifications(outputFilePath, modifications)
	}
}
