// src/main.go
package main

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

func main() {
	// Define command-line flags
	baseURL := flag.String("base-url", "https://code.252.no/api/v1", "Base URL of the Forgejo API")
	token := flag.String("token", "", "API token for authentication")

	action := flag.String("action", "", "Action to perform: get, create, get-single, update, delete, apply, sync")
	owner := flag.String("owner", "", "Owner of the repository")
	repo := flag.String("repo", "", "Name of the repository")
	id := flag.Int64("id", 0, "ID of the issue or pull request")
	name := flag.String("name", "", "Name of the label")
	color := flag.String("color", "", "Color of the label")
	description := flag.String("description", "", "Description of the label")

	// Define exclusive and isArchived as regular boolean flags
	exclusive := flag.Bool("exclusive", false, "Exclusive flag for the label (true/false)")
	isArchived := flag.Bool("is-archived", false, "Archived flag for the label (true/false)")

	// Define paths to configuration files
	labelerConfigPath := flag.String("labeler-config", ".forgejo/labeler.yaml", "Path to labeler.yaml configuration file")
	labelsConfigPath := flag.String("labels-config", ".forgejo/labels.yaml", "Path to labels.yaml configuration file")

	// Define changed files (comma-separated list)
	changedFilesInput := flag.String("changed-files", "", "Comma-separated list of changed file paths")

	// Define deleteOtherLabels flag
	deleteOtherLabels := flag.Bool("delete-other-labels", false, "Delete labels not defined in the config file")

	// Define dryRun flag
	dryRun := flag.Bool("dry-run", false, "Simulate the sync without making any changes")

	page := flag.Int("page", 1, "Page number for listing labels")
	limit := flag.Int("limit", 30, "Number of labels per page")

	flag.Parse()

	// Validate required flags
	if *action == "" || *owner == "" || *repo == "" {
		fmt.Println("Error: action, owner, and repo are required.")
		flag.Usage()
		os.Exit(1)
	}

	// Initialize the Forgejo client
	client := NewForgejoClient(*baseURL, *token)

	switch *action {
	case "get":
		labels, err := client.GetLabels(*owner, *repo, *page, *limit)
		if err != nil {
			log.Fatalf("Error getting labels: %v", err)
		}
		for _, label := range labels {
			fmt.Printf("ID: %d, Name: %s, Color: %s, Description: %s\n", label.ID, label.Name, label.Color, label.Description)
		}

	case "create":
		if *name == "" || *color == "" {
			fmt.Println("Error: name and color are required for creating a label.")
			flag.Usage()
			os.Exit(1)
		}
		option := CreateLabelOption{
			Name:        *name,
			Color:       *color,
			Description: *description,
			Exclusive:   *exclusive,
			IsArchived:  *isArchived,
		}
		label, err := client.CreateLabel(*owner, *repo, option)
		if err != nil {
			log.Fatalf("Error creating label: %v", err)
		}
		fmt.Printf("Created Label: ID=%d, Name=%s\n", label.ID, label.Name)

	case "get-single":
		if *id == 0 {
			fmt.Println("Error: id is required for getting a single label.")
			flag.Usage()
			os.Exit(1)
		}
		label, err := client.GetLabel(*owner, *repo, *id)
		if err != nil {
			log.Fatalf("Error getting label: %v", err)
		}
		fmt.Printf("Label: ID=%d, Name=%s, Color=%s, Description: %s\n", label.ID, label.Name, label.Color, label.Description)

	case "update":
		if *id == 0 {
			fmt.Println("Error: id is required for updating a label.")
			flag.Usage()
			os.Exit(1)
		}
		option := EditLabelOption{
			Name:        *name,
			Color:       *color,
			Description: *description,
		}
		if *exclusive {
			option.Exclusive = exclusive
		}
		if *isArchived {
			option.IsArchived = isArchived
		}
		err := client.UpdateLabel(*owner, *repo, *id, option)
		if err != nil {
			log.Fatalf("Error updating label: %v", err)
		}
		fmt.Printf("Updated Label: Name=%s\n", option.Name)

	case "delete":
		if *id == 0 {
			fmt.Println("Error: id is required for deleting a label.")
			flag.Usage()
			os.Exit(1)
		}
		err := client.DeleteLabel(*owner, *repo, *id)
		if err != nil {
			log.Fatalf("Error deleting label: %v", err)
		}
		fmt.Println("Label deleted successfully.")

	case "apply":
		// Action to apply labels based on configuration and changed files
		if *changedFilesInput == "" {
			fmt.Println("Error: changed-files are required for the apply action.")
			flag.Usage()
			os.Exit(1)
		}

		// Parse changed files
		changedFiles := parseChangedFiles(*changedFilesInput)

		// Load configuration files
		labelerConfig, err := LoadLabelerConfig(*labelerConfigPath)
		if err != nil {
			log.Fatalf("Error loading labeler config: %v", err)
		}

		labelsConfig, err := LoadLabelsConfig(*labelsConfigPath)
		if err != nil {
			log.Fatalf("Error loading labels config: %v", err)
		}

		// Validate configurations
		if err := ValidateLabelerConfig(labelerConfig); err != nil {
			log.Fatalf("Invalid labeler config: %v", err)
		}
		if err := ValidateLabelsConfig(labelsConfig); err != nil {
			log.Fatalf("Invalid labels config: %v", err)
		}

		// Ensure all labels exist, optionally deleting others, with dryRun
		err = EnsureLabelsExist(client, *owner, *repo, labelsConfig, *deleteOtherLabels, *dryRun)
		if err != nil {
			log.Fatalf("Error ensuring labels exist: %v", err)
		}

		// Determine labels to apply based on changed files
		labelsToApply, err := DetermineLabels(labelerConfig, changedFiles)
		if err != nil {
			log.Fatalf("Error determining labels to apply: %v", err)
		}

		if len(labelsToApply) == 0 {
			fmt.Println("No labels to apply based on the changed files.")
			return
		}

		// Apply labels to an issue or pull request
		// For demonstration, assume applying to a specific issue ID passed via the `id` flag
		if *id == 0 {
			fmt.Println("Error: id (issue or PR ID) is required to apply labels.")
			flag.Usage()
			os.Exit(1)
		}

		// Apply labels using SetLabelsOnIssue
		err = client.SetLabelsOnIssue(*owner, *repo, *id, labelsToApply)
		if err != nil {
			log.Fatalf("Error applying labels: %v", err)
		}
		fmt.Printf("Labels applied successfully: %v\n", labelsToApply)

	case "sync":
		// New "sync" action
		// Sync labels based on labels.yaml, optionally deleting other labels
		// This action doesn't require changed-files or issue ID

		// Load configuration files
		labelsConfig, err := LoadLabelsConfig(*labelsConfigPath)
		if err != nil {
			log.Fatalf("Error loading labels config: %v", err)
		}

		// Validate labels configuration
		if err := ValidateLabelsConfig(labelsConfig); err != nil {
			log.Fatalf("Invalid labels config: %v", err)
		}

		// Ensure all labels exist, optionally deleting others, with dryRun
		err = EnsureLabelsExist(client, *owner, *repo, labelsConfig, *deleteOtherLabels, *dryRun)
		if err != nil {
			log.Fatalf("Error ensuring labels exist: %v", err)
		}

		if *dryRun {
			fmt.Println("Dry-run completed successfully. No changes were made.")
		} else {
			fmt.Println("Label synchronization completed successfully.")
		}

	default:
		fmt.Println("Error: Invalid action. Available actions: get, create, get-single, update, delete, apply, sync.")
		flag.Usage()
		os.Exit(1)
	}
}

// parseChangedFiles parses a comma-separated list of file paths into a slice.
func parseChangedFiles(input string) []string {
	var files []string
	for _, file := range strings.Split(input, ",") {
		trimmed := filepath.Clean(strings.TrimSpace(file))
		if trimmed != "" {
			files = append(files, trimmed)
		}
	}
	return files
}
