package main

import (
	"flag"
	"fmt"
	"os"
	"sort"
	"strings"
	"sync"
	"time"
)

type Mode string

const (
	ModePrivateKeys Mode = "private-keys"
	ModeAxiom       Mode = "axiom"
	ModeBoth        Mode = "both"
)

type CheckResult struct {
	Line    string
	Balance float64
}

type Statistics struct {
	TotalBalance    float64
	KeysWithBalance int
	EmptyKeys       int
	TotalKeys       int
	Duration        time.Duration
	Mode            string
}

var (
	results      []CheckResult
	resultsMutex sync.Mutex
	stats        Statistics
)

func addResult(line string, balance float64) {
	resultsMutex.Lock()
	defer resultsMutex.Unlock()
	results = append(results, CheckResult{Line: line, Balance: balance})

	stats.TotalBalance += balance
	if balance > 0 {
		stats.KeysWithBalance++
	} else {
		stats.EmptyKeys++
	}
}

func main() {
	addressFlag := flag.String("address", "", "Check specific address")
	flag.Parse()

	printHeader()

	cfg, err := loadConfig()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err)
		os.Exit(1)
	}

	initProxies()

	if *addressFlag != "" {
		checkSingleAddress(*addressFlag, cfg)
		return
	}

	for {
		choice, err := showMainMenu(cfg)
		if err != nil {
			fmt.Fprintf(os.Stderr, "\nCancelled\n")
			os.Exit(1)
		}

		switch choice {
		case "settings":
			if err := showSettingsMenu(cfg); err != nil {
				fmt.Fprintf(os.Stderr, "\nSettings cancelled\n")
				continue
			}

		case "start":
			initApiKeyWorkers(cfg)

			fmt.Println()
			printConfigSummary(cfg)

			moduleManager := NewModuleManager(cfg)
			enabledModules := moduleManager.GetEnabledModules()

			if cfg.EnableSolanaCheck || len(enabledModules) > 0 {
				colorPrint(ColorGreen, "Active modules:      ")
				modules := []string{}
				if cfg.EnableSolanaCheck {
					modules = append(modules, "Solana")
				}
				for _, m := range enabledModules {
					modules = append(modules, m.Name())
				}
				for i, m := range modules {
					if i > 0 {
						fmt.Print(", ")
					}
					colorPrint(ColorCyan, m)
				}
				fmt.Println()
			}
			fmt.Printf("Loaded proxies:      %d\n", len(proxies))

			keys := loadPrivateKeys()
			if len(keys) == 0 {
				fmt.Fprintln(os.Stderr, "\nNo keys found!\nAdd to: private_keys.txt or keys/*.txt")
				continue
			}
			stats.TotalKeys = len(keys)

			runChecker(keys, cfg, moduleManager)

			fmt.Println("\nPress Enter to return to main menu...")
			fmt.Scanln()
			printHeader()

		case "exit":
			colorPrintln(ColorYellow, "\nGoodbye!")
			os.Exit(0)
		}
	}
}

func printHeader() {
	colorPrintln(ColorCyan, "")
	colorPrintln(ColorCyan, "   ___      _                  ___ _           _           ")
	colorPrintln(ColorCyan, "  / _ \\__ _(_)___ _ __  ___   / __| |_  ___ __| |_____ _ _ ")
	colorPrintln(ColorCyan, " | (_| \\ \\ / / _ \\ '  \\/ -_) | (__| ' \\/ -_) _| / / -_) '_|")
	colorPrintln(ColorCyan, "  \\___/_\\_\\_\\\\___/_|_|_\\___|  \\___|_||_\\___\\__|_\\_\\___|_|  ")
	fmt.Print("                                              ")
	colorPrintln(ColorYellow, "by @septum")
	colorPrintln(ColorCyan, "")
}

func checkSingleAddress(address string, cfg *Config) {
	if strings.HasPrefix(address, "0x") || strings.HasPrefix(address, "0X") {
		balance, details, err := getDeBankBalance(address)
		if err != nil {
			fmt.Printf("[evm address][debank] %s - Error: %v\n", address, err)
			return
		}

		fmt.Printf("[evm address][debank] %s - $%.2f\n", address, balance)
		if details != "" {
			fmt.Print(details)
		}
	} else {
		portfolio, err := getPortfolioValue(address, cfg)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
			os.Exit(1)
		}

		tokenStr := ""
		if portfolio.Value > 0 {
			if portfolio.TokenCount > 10 {
				tokenStr = " (>10 tokens)"
			} else {
				tokenStr = fmt.Sprintf(" (%d tokens)", portfolio.TokenCount)
			}
		}

		valueStr := "0.00"
		if portfolio.Value > 0 {
			valueStr = fmt.Sprintf("%.2f", portfolio.Value)
		}

		fmt.Printf("[solana address][jup.ag] %s - $%s%s\n", address, valueStr, tokenStr)
	}
}

func runChecker(keys []string, cfg *Config, moduleManager *ModuleManager) {
	outputFile := fmt.Sprintf("results_%s.txt", time.Now().Format("2006-01-02_15-04-05"))

	fmt.Printf("\n%s\n", strings.Repeat("━", 60))
	fmt.Printf("Keys to check: %d\n", len(keys))
	fmt.Printf("Output file: %s\n", outputFile)
	fmt.Printf("%s\n\n", strings.Repeat("━", 60))

	startTime := time.Now()
	processKeys(keys, cfg, moduleManager)
	stats.Duration = time.Since(startTime)

	sort.Slice(results, func(i, j int) bool { return results[i].Balance > results[j].Balance })

	if err := writeResults(outputFile); err != nil {
		fmt.Fprintf(os.Stderr, "Failed to write results: %v\n", err)
		return
	}

	fmt.Printf("\n\n")
	colorPrintln(ColorGreen, fmt.Sprintf("✅ Results saved to: %s", outputFile))
	printSummary()
}

func processKeys(keys []string, cfg *Config, moduleManager *ModuleManager) {
	tracker := NewProgressTracker(len(keys))
	display := NewStatusDisplay(tracker)
	display.Start()
	defer display.Stop()

	for i := 0; i < len(keys); i += cfg.Threads {
		end := min(i+cfg.Threads, len(keys))
		var wg sync.WaitGroup
		for _, key := range keys[i:end] {
			wg.Add(1)
			go func(pk string) {
				defer wg.Done()
				checkKey(pk, cfg, moduleManager, tracker)
			}(key)
		}
		wg.Wait()
	}
}

func checkKey(pk string, cfg *Config, moduleManager *ModuleManager, tracker *ProgressTracker) {
	defer func() {
		if r := recover(); r != nil {
			line := fmt.Sprintf("Error: %v", r)
			addResult(line, 0)
			tracker.Increment(0)
		}
	}()

	var line string
	totalBalance := 0.0

	if cfg.EnableSolanaCheck {
		kp, err := keypairFromSecretKey(pk)
		if err != nil {
			addResult(fmt.Sprintf("Error: %v", err), 0)
			tracker.Increment(0)
			return
		}
		addr := kp.PublicKeyBase58()
		pkBalance, _ := getPortfolioValue(addr, cfg)

		line += fmt.Sprintf("[Private Key] %s\n", pk)
		line += fmt.Sprintf("  [SOL] %s - $%.2f", addr, pkBalance.Value)
		if pkBalance.TokenCount > 0 {
			line += fmt.Sprintf(" (%d tokens)", pkBalance.TokenCount)
		}
		line += "\n"

		totalBalance += pkBalance.Value
	} else {
		line += fmt.Sprintf("[Private Key] %s\n", pk)
	}

	moduleBalance, moduleDetails := moduleManager.CheckAll(pk)
	if moduleDetails != "" {
		line += moduleDetails
		totalBalance += moduleBalance
	}

	if totalBalance > 0 {
		line += fmt.Sprintf("  %s[TOTAL: $%.2f]%s\n", ColorYellow, totalBalance, ColorReset)
	}

	addResult(line, totalBalance)
	tracker.Increment(totalBalance)
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func printSummary() {
	fmt.Println("\n" + strings.Repeat("=", 60))
	fmt.Println("SUMMARY")
	fmt.Println(strings.Repeat("=", 60))
	fmt.Printf("Mode:               %s\n", stats.Mode)
	fmt.Printf("Total Balance:      $%.2f\n", stats.TotalBalance)
	fmt.Printf("Keys with Balance:  %d\n", stats.KeysWithBalance)
	fmt.Printf("Empty Keys:         %d\n", stats.EmptyKeys)
	fmt.Printf("Total Keys:         %d\n", stats.TotalKeys)
	fmt.Printf("Duration:           %s\n", stats.Duration.Round(time.Second))
	fmt.Println(strings.Repeat("=", 60))
}

func writeResults(filename string) error {
	f, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer f.Close()

	fmt.Fprintf(f, "RESULTS\n")
	fmt.Fprintf(f, "Generated: %s\n\n", time.Now().Format("2006-01-02 15:04:05"))
	fmt.Fprintf(f, "Mode:               %s\n", stats.Mode)
	fmt.Fprintf(f, "Total Balance:      $%.2f\n", stats.TotalBalance)
	fmt.Fprintf(f, "Accounts with Balance:  %d\n", stats.KeysWithBalance)
	fmt.Fprintf(f, ":0.01$> accounts%d\n", stats.EmptyKeys)
	fmt.Fprintf(f, "Total Keys:         %d\n", stats.TotalKeys)
	fmt.Fprintf(f, "Duration:           %s\n", stats.Duration.Round(time.Second))
	fmt.Fprintf(f, "\n%s\n\n", strings.Repeat("=", 80))

	// Write sorted results
	for _, result := range results {
		fmt.Fprintln(f, result.Line)
	}

	return nil
}

func check(pk string, mode Mode, cfg *Config) {
	defer func() {
		if r := recover(); r != nil {
			line := fmt.Sprintf("Error: %v", r)
			fmt.Println(line)
			addResult(line, 0)
		}
	}()

	if mode == ModePrivateKeys {
		kp, err := keypairFromSecretKey(pk)
		if err != nil {
			line := fmt.Sprintf("Error: %v", err)
			fmt.Println(line)
			addResult(line, 0)
			return
		}

		addr := kp.PublicKeyBase58()
		portfolio, err := getPortfolioValue(addr, cfg)
		if err != nil {
			line := fmt.Sprintf("Error: %v", err)
			fmt.Println(line)
			addResult(line, 0)
			return
		}

		tokenStr := ""
		if portfolio.Value > 0 {
			if portfolio.TokenCount > 10 {
				tokenStr = " (>10 tokens)"
			} else {
				tokenStr = fmt.Sprintf(" (%d tokens)", portfolio.TokenCount)
			}
		}

		valueStr := "0.00"
		if portfolio.Value > 0 {
			valueStr = fmt.Sprintf("%.2f", portfolio.Value)
		}

		line := fmt.Sprintf("[private key][jup.ag] %s | %s - $%s%s", pk, addr, valueStr, tokenStr)
		fmt.Println(line)
		addResult(line, portfolio.Value)
		return
	}

	ax, err := getAxiomWallet(pk)
	if err != nil {
		line := fmt.Sprintf("Error: %v", err)
		fmt.Println(line)
		addResult(line, 0)
		return
	}

	if ax.IsNewUser {
		line := fmt.Sprintf("[axiom][jup.ag] %s | %s - NEW USER (skipped)", pk, ax.Sol)
		fmt.Println(line)
		addResult(line, 0)
		return
	}

	if mode == ModeAxiom {
		portfolio, err := getPortfolioValue(ax.Sol, cfg)
		if err != nil {
			line := fmt.Sprintf("Error: %v", err)
			fmt.Println(line)
			addResult(line, 0)
			return
		}

		tokenStr := ""
		if portfolio.Value > 0 {
			if portfolio.TokenCount > 10 {
				tokenStr = " (>10 tokens)"
			} else {
				tokenStr = fmt.Sprintf(" (%d tokens)", portfolio.TokenCount)
			}
		}

		valueStr := "0.00"
		if portfolio.Value > 0 {
			valueStr = fmt.Sprintf("%.2f", portfolio.Value)
		}

		line := fmt.Sprintf("[axiom][jup.ag] %s | %s - $%s%s", pk, ax.Sol, valueStr, tokenStr)
		fmt.Println(line)
		addResult(line, portfolio.Value)
		return
	}

	if mode == ModeBoth {
		kp, err := keypairFromSecretKey(pk)
		if err != nil {
			line := fmt.Sprintf("Error: %v", err)
			fmt.Println(line)
			addResult(line, 0)
			return
		}

		addr := kp.PublicKeyBase58()

		var p1, p2 *PortfolioValue
		var wg sync.WaitGroup
		var err1, err2 error

		wg.Add(2)

		go func() {
			defer wg.Done()
			p1, err1 = getPortfolioValue(addr, cfg)
		}()

		go func() {
			defer wg.Done()
			p2, err2 = getPortfolioValue(ax.Sol, cfg)
		}()

		wg.Wait()

		if err1 != nil || err2 != nil {
			line := "Error fetching portfolios"
			fmt.Println(line)
			addResult(line, 0)
			return
		}

		total := p1.Value + p2.Value
		count := p1.TokenCount + p2.TokenCount

		tokenStr := ""
		if total > 0 {
			if count > 10 {
				tokenStr = " (>10 tokens)"
			} else {
				tokenStr = fmt.Sprintf(" (%d tokens)", count)
			}
		}

		valueStr := "0.00"
		if total > 0 {
			valueStr = fmt.Sprintf("%.2f", total)
		}

		line := fmt.Sprintf("[pk+axiom][jup.ag] %s | %s | %s - $%s%s", pk, addr, ax.Sol, valueStr, tokenStr)
		fmt.Println(line)
		addResult(line, total)
	}
}

func tern(cond bool, t, f string) string {
	if cond {
		return t
	}
	return f
}
