// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// Command config provides utilities for working with luxd configuration.
//
// Usage:
//
//	luxd-config spec [--format=json|md|go]
//	luxd-config validate <config-file>
//	luxd-config diff <config-file>
package main

import (
	"encoding/json"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/spf13/pflag"
	"github.com/spf13/viper"

	"github.com/luxfi/node/config/spec"
)

func main() {
	if len(os.Args) < 2 {
		printUsage()
		os.Exit(1)
	}

	cmd := os.Args[1]
	args := os.Args[2:]

	switch cmd {
	case "spec":
		dumpSpec(args)
	case "validate":
		validate(args)
	case "diff":
		showDiff(args)
	case "help", "-h", "--help":
		printUsage()
	default:
		fmt.Printf("Unknown command: %s\n", cmd)
		printUsage()
		os.Exit(1)
	}
}

func printUsage() {
	fmt.Println(`luxd-config - Lux Node Configuration Tool

Usage:
  luxd-config <command> [options]

Commands:
  spec        Output the complete configuration specification
  validate    Validate a configuration file
  diff        Show difference between config file and defaults

Options for spec:
  --format=json   Output as JSON (default)
  --format=md     Output as Markdown documentation
  --format=go     Output as Go code for embedding

Options for validate:
  --strict        Fail on unknown keys (default: warn)
  --quiet         Only output errors

Examples:
  luxd-config spec --format=json > spec.json
  luxd-config validate /path/to/config.json
  luxd-config diff /path/to/config.json`)
}

func dumpSpec(args []string) {
	fs := pflag.NewFlagSet("spec", pflag.ExitOnError)
	format := fs.String("format", "json", "Output format: json, md, or go")
	if err := fs.Parse(args); err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}

	s := spec.Spec()

	switch *format {
	case "json":
		dumpJSON(s)
	case "md":
		dumpMarkdown(s)
	case "go":
		dumpGo(s)
	default:
		fmt.Printf("Unknown format: %s\n", *format)
		os.Exit(1)
	}
}

func dumpJSON(s *spec.ConfigSpec) {
	data, err := s.JSON()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}
	fmt.Println(string(data))
}

func dumpMarkdown(s *spec.ConfigSpec) {
	fmt.Printf("# Lux Node Configuration Reference\n\n")
	fmt.Printf("Version: %s\n", s.Version)
	fmt.Printf("Node Version: %s\n", s.NodeVersion)
	fmt.Printf("Generated: %s\n\n", s.GeneratedAt)

	// Group by category
	byCategory := make(map[spec.Category][]spec.FlagSpec)
	for _, f := range s.Flags {
		byCategory[f.Category] = append(byCategory[f.Category], f)
	}

	// Sort categories
	var cats []spec.Category
	for cat := range byCategory {
		cats = append(cats, cat)
	}
	sort.Slice(cats, func(i, j int) bool {
		return string(cats[i]) < string(cats[j])
	})

	fmt.Println("## Table of Contents")
	fmt.Println()
	for _, cat := range cats {
		fmt.Printf("- [%s](#%s)\n", s.Categories[cat], strings.ToLower(string(cat)))
	}
	fmt.Println()

	for _, cat := range cats {
		flags := byCategory[cat]
		fmt.Printf("## %s\n\n", s.Categories[cat])
		fmt.Printf("| Flag | Type | Default | Description |\n")
		fmt.Printf("|------|------|---------|-------------|\n")

		for _, f := range flags {
			defaultStr := formatDefault(f.Default)
			desc := strings.ReplaceAll(f.Description, "|", "\\|")
			if f.Deprecated {
				desc = "**DEPRECATED** " + desc
			}
			fmt.Printf("| `--%s` | %s | %s | %s |\n", f.Key, f.Type, defaultStr, desc)
		}
		fmt.Println()
	}
}

func dumpGo(s *spec.ConfigSpec) {
	fmt.Println("// Code generated by luxd-config spec --format=go. DO NOT EDIT.")
	fmt.Println()
	fmt.Printf("// Version: %s\n", s.Version)
	fmt.Printf("// NodeVersion: %s\n", s.NodeVersion)
	fmt.Println()

	data, _ := json.Marshal(s)
	fmt.Printf("const specJSON = `%s`\n", string(data))
}

func formatDefault(v interface{}) string {
	if v == nil {
		return "-"
	}
	switch val := v.(type) {
	case string:
		if val == "" {
			return `""`
		}
		if len(val) > 30 {
			return fmt.Sprintf(`"%s..."`, val[:27])
		}
		return fmt.Sprintf(`"%s"`, val)
	case bool:
		return fmt.Sprintf("%t", val)
	case int, int64, uint, uint64:
		return fmt.Sprintf("%d", val)
	case float64:
		return fmt.Sprintf("%.2f", val)
	default:
		return fmt.Sprintf("%v", val)
	}
}

func validate(args []string) {
	fs := pflag.NewFlagSet("validate", pflag.ExitOnError)
	strict := fs.Bool("strict", false, "Fail on unknown keys")
	quiet := fs.Bool("quiet", false, "Only output errors")
	if err := fs.Parse(args); err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}

	if fs.NArg() < 1 {
		fmt.Println("Error: config file path required")
		fmt.Println("Usage: luxd-config validate <config-file>")
		os.Exit(1)
	}

	configFile := fs.Arg(0)

	// Load config file
	v := viper.New()
	v.SetConfigFile(configFile)
	if err := v.ReadInConfig(); err != nil {
		fmt.Printf("Error reading config: %v\n", err)
		os.Exit(1)
	}

	s := spec.Spec()
	knownKeys := make(map[string]bool)
	for _, f := range s.Flags {
		knownKeys[f.Key] = true
	}

	allKeys := v.AllKeys()
	var unknownKeys []string
	var deprecatedKeys []string
	hasError := false

	for _, key := range allKeys {
		if !knownKeys[key] {
			unknownKeys = append(unknownKeys, key)
			if *strict {
				hasError = true
			}
		} else {
			// Check if deprecated
			if f := s.GetFlag(key); f != nil && f.Deprecated {
				deprecatedKeys = append(deprecatedKeys, key)
			}
		}
	}

	if !*quiet {
		fmt.Printf("Validating: %s\n", configFile)
		fmt.Printf("Keys found: %d\n", len(allKeys))
	}

	if len(unknownKeys) > 0 {
		level := "Warning"
		if *strict {
			level = "Error"
		}
		fmt.Printf("\n%s: %d unknown key(s):\n", level, len(unknownKeys))
		for _, key := range unknownKeys {
			suggestion := findSimilar(key, s.Flags)
			if suggestion != "" {
				fmt.Printf("  - %s (did you mean: %s?)\n", key, suggestion)
			} else {
				fmt.Printf("  - %s\n", key)
			}
		}
	}

	if len(deprecatedKeys) > 0 {
		fmt.Printf("\nWarning: %d deprecated key(s):\n", len(deprecatedKeys))
		for _, key := range deprecatedKeys {
			if f := s.GetFlag(key); f != nil {
				msg := f.DeprecatedMessage
				if msg == "" {
					msg = "This flag is deprecated"
				}
				fmt.Printf("  - %s: %s\n", key, msg)
			}
		}
	}

	if hasError {
		os.Exit(1)
	}

	if !*quiet {
		fmt.Println("\nValidation passed!")
	}
}

func showDiff(args []string) {
	fs := pflag.NewFlagSet("diff", pflag.ExitOnError)
	if err := fs.Parse(args); err != nil {
		fmt.Printf("Error: %v\n", err)
		os.Exit(1)
	}

	if fs.NArg() < 1 {
		fmt.Println("Error: config file path required")
		fmt.Println("Usage: luxd-config diff <config-file>")
		os.Exit(1)
	}

	configFile := fs.Arg(0)

	v := viper.New()
	v.SetConfigFile(configFile)
	if err := v.ReadInConfig(); err != nil {
		fmt.Printf("Error reading config: %v\n", err)
		os.Exit(1)
	}

	s := spec.Spec()
	fmt.Printf("Configuration differences from defaults:\n\n")

	for _, f := range s.Flags {
		if v.IsSet(f.Key) {
			configVal := v.Get(f.Key)
			defaultVal := f.Default

			// Check if different from default
			if !equalValues(configVal, defaultVal) {
				fmt.Printf("%-45s default: %-20v config: %v\n",
					f.Key, formatDefault(defaultVal), configVal)
			}
		}
	}
}

func findSimilar(key string, flags []spec.FlagSpec) string {
	// Simple Levenshtein-like matching
	bestMatch := ""
	bestScore := 0

	for _, f := range flags {
		score := similarity(key, f.Key)
		if score > bestScore && score > len(key)/2 {
			bestScore = score
			bestMatch = f.Key
		}
	}
	return bestMatch
}

func similarity(a, b string) int {
	// Count common characters in sequence
	score := 0
	for i := 0; i < len(a) && i < len(b); i++ {
		if a[i] == b[i] {
			score++
		}
	}

	// Bonus for shared substrings
	for i := 0; i < len(a)-2; i++ {
		substr := a[i : i+3]
		if strings.Contains(b, substr) {
			score += 2
		}
	}

	return score
}

func equalValues(a, b interface{}) bool {
	if a == nil && b == nil {
		return true
	}
	if a == nil || b == nil {
		return false
	}
	return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
}
