package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/binary"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"strings"
)

func main() {
	// Subcommands
	verifyUnlimitedCmd := flag.NewFlagSet("verify-unlimited", flag.ExitOnError)
	verifyIchibanCmd := flag.NewFlagSet("verify-ichiban", flag.ExitOnError)
	inspectIchibanCmd := flag.NewFlagSet("inspect-ichiban", flag.ExitOnError)
	verifyFileCmd := flag.NewFlagSet("verify-file", flag.ExitOnError)

	// Unlimited Args
	vuSeed := verifyUnlimitedCmd.String("seed", "", "Server Seed (Hex)")
	vuIssue := verifyUnlimitedCmd.Int64("issue", 0, "Issue ID")
	vuUser := verifyUnlimitedCmd.Int64("user", 0, "User ID")
	vuSalt := verifyUnlimitedCmd.String("salt", "", "Salt (Hex)")
	vuWeights := verifyUnlimitedCmd.String("weights", "", "Rewards (Format: ID:Weight,ID:Weight...)")

	// Ichiban Args
	viSeed := verifyIchibanCmd.String("seed", "", "Server Seed (Hex)")
	viIssue := verifyIchibanCmd.Int64("issue", 0, "Issue ID")
	viSlot := verifyIchibanCmd.Int("slot", 0, "Selected Slot (1-based)")
	viRewards := verifyIchibanCmd.String("rewards", "", "Rewards (Format: ID:Count,ID:Count...)")

	// Inspect Ichiban Args
	iiSeed := inspectIchibanCmd.String("seed", "", "Server Seed (Hex)")
	iiIssue := inspectIchibanCmd.Int64("issue", 0, "Issue ID")
	iiRewards := inspectIchibanCmd.String("rewards", "", "Rewards (Format: ID:Count,ID:Count...)")

	// JSON File Args
	vfPath := verifyFileCmd.String("path", "", "Path to JSON receipt file")
	vfWeights := verifyFileCmd.String("weights", "", "Global Weights for Unlimited (Format: ID:Weight...)")
	vfRewards := verifyFileCmd.String("rewards", "", "Global Rewards for Ichiban (Format: ID:Count...)")

	if len(os.Args) < 2 {
		printUsage()
		return
	}

	switch os.Args[1] {
	case "verify-unlimited":
		verifyUnlimitedCmd.Parse(os.Args[2:])
		runUnlimited(*vuSeed, *vuIssue, *vuUser, *vuSalt, *vuWeights)
	case "verify-ichiban":
		verifyIchibanCmd.Parse(os.Args[2:])
		runIchiban(*viSeed, *viIssue, *viSlot, *viRewards)
	case "inspect-ichiban":
		inspectIchibanCmd.Parse(os.Args[2:])
		runInspectIchiban(*iiSeed, *iiIssue, *iiRewards)
	case "verify-file":
		verifyFileCmd.Parse(os.Args[2:])
		runVerifyFile(*vfPath, *vfWeights, *vfRewards)
	default:
		printUsage()
	}
}

func printUsage() {
	fmt.Println("BindBox Lottery Verifier Tool (v1.2)")
	fmt.Println("\nUsage:")
	fmt.Println("  verify-unlimited  --seed <hex> --issue <id> --user <id> --salt <hex> --weights <list>")
	fmt.Println("  verify-ichiban    --seed <hex> --issue <id> --slot <num> --rewards <list>")
	fmt.Println("  inspect-ichiban   --seed <hex> --issue <id> --rewards <list>  (Dump all slots)")
	fmt.Println("  verify-file       --path <json_file> [--weights <list>]       (Load from JSON, support array)")
	fmt.Println("\nExample Unlimited:")
	fmt.Println("  verify-unlimited --seed aabbcc... --issue 1001 --user 888 --salt 1234... --weights 1:10,2:50,3:100")
	fmt.Println("\nExample File (with global weights):")
	fmt.Println("  verify-file --path receipts.json --weights \"280:88200,281:100...\"")
}

func runUnlimited(seed string, issue int64, user int64, salt string, weightsStr string) {
	if seed == "" || issue == 0 || user == 0 || salt == "" || weightsStr == "" {
		fmt.Println("Error: Missing required arguments.")
		return
	}

	rewards, err := ParseRewardsString(weightsStr)
	if err != nil {
		fmt.Printf("Error parsing weights: %v\n", err)
		return
	}

	fmt.Println("========================================")
	fmt.Println("      UNLIMITED LOTTERY VERIFICATION    ")
	fmt.Println("========================================")
	fmt.Printf("Server Seed : %s\n", seed)
	fmt.Printf("Issue ID    : %d\n", issue)
	fmt.Printf("User ID     : %d\n", user)
	fmt.Printf("Salt        : %s\n", salt)
	fmt.Println("----------------------------------------")

	id, log, err := VerifyUnlimited(seed, issue, user, salt, rewards)
	if err != nil {
		fmt.Printf("Verification FAILED: %v\n", err)
		return
	}

	fmt.Println(log)
	fmt.Println("----------------------------------------")
	fmt.Printf("VERIFIED RESULT: Reward ID = %d\n", id)
	fmt.Println("========================================")
}

func runIchiban(seed string, issue int64, slot int, rewardsStr string) {
	if seed == "" || issue == 0 || slot == 0 || rewardsStr == "" {
		fmt.Println("Error: Missing required arguments.")
		return
	}

	rewards, err := ParseRewardsString(rewardsStr)
	if err != nil {
		fmt.Printf("Error parsing rewards: %v\n", err)
		return
	}

	fmt.Println("========================================")
	fmt.Println("       ICHIBAN LOTTERY VERIFICATION     ")
	fmt.Println("========================================")
	fmt.Printf("Server Seed : %s\n", seed)
	fmt.Printf("Issue ID    : %d\n", issue)
	fmt.Printf("Values      : %d unique items expanded\n", len(rewards))
	fmt.Println("----------------------------------------")

	id, log, err := VerifyIchiban(seed, issue, slot, rewards)
	if err != nil {
		fmt.Printf("Verification FAILED: %v\n", err)
		return
	}

	fmt.Println(log)
	fmt.Println("----------------------------------------")
	fmt.Printf("VERIFIED RESULT: Reward ID = %d\n", id)
	fmt.Println("========================================")
}

func runInspectIchiban(seed string, issue int64, rewardsStr string) {
	if seed == "" || issue == 0 || rewardsStr == "" {
		fmt.Println("Error: Missing required arguments.")
		return
	}
	rewards, err := ParseRewardsString(rewardsStr)
	if err != nil {
		fmt.Printf("Error parsing rewards: %v\n", err)
		return
	}

	fmt.Println("========================================")
	fmt.Println("     ICHIBAN GLOBAL INSPECTION          ")
	fmt.Println("========================================")
	fmt.Printf("Server Seed : %s\n", seed)
	fmt.Printf("Issue ID    : %d\n", issue)
	fmt.Println("----------------------------------------")

	var slots []RewardItem
	for _, r := range rewards {
		for k := 0; k < r.Count; k++ {
			slots = append(slots, r)
		}
	}

	totalSlots := len(slots)
	// Shuffle
	seedKey, _ := decodeHex(seed)

	// Create indices mapping
	indices := make([]int, totalSlots)
	for i := 0; i < totalSlots; i++ {
		indices[i] = i
	}

	mac := hmac.New(sha256.New, seedKey)
	// Reconstruct actual items
	workingSlots := make([]RewardItem, totalSlots)
	copy(workingSlots, slots)
	for i := totalSlots - 1; i > 0; i-- {
		mac.Reset()
		mac.Write([]byte(fmt.Sprintf("shuffle:%d|issue:%d", i, issue)))
		sum := mac.Sum(nil)
		j := int(binary.BigEndian.Uint64(sum[:8]) % uint64(i+1))
		workingSlots[i], workingSlots[j] = workingSlots[j], workingSlots[i]
	}

	// Print Grid
	fmt.Printf("%-10s | %-10s | %s\n", "SLOT (NO.)", "REWARD ID", "NAME")
	fmt.Println(strings.Repeat("-", 40))
	for i, item := range workingSlots {
		fmt.Printf("%-10d | %-10d | %s\n", i+1, item.ID, item.Name)
	}
	fmt.Println("========================================")
}

func runVerifyFile(path string, globalWeights string, globalRewards string) {
	if path == "" {
		fmt.Println("Error: Missing file path.")
		return
	}
	data, err := os.ReadFile(path)
	if err != nil {
		fmt.Printf("Error reading file: %v\n", err)
		return
	}

	type Receipt struct {
		Mode      string `json:"mode"`
		Seed      string `json:"seed"`
		IssueID   int64  `json:"issue_id"`
		UserID    int64  `json:"user_id"`
		Salt      string `json:"salt"`
		Weights   string `json:"weights"`
		SlotIndex int    `json:"slot_index"`
		Rewards   string `json:"rewards"`
	}

	var receipts []Receipt
	// Try parsing as array first
	if err := json.Unmarshal(data, &receipts); err != nil {
		// Try parsing as single object
		var single Receipt
		if err2 := json.Unmarshal(data, &single); err2 != nil {
			fmt.Printf("Error parsing JSON (tried both array and object): %v\n", err)
			return
		}
		receipts = append(receipts, single)
	}

	fmt.Printf("Loaded %d receipt(s) from file.\n", len(receipts))

	for i, r := range receipts {
		fmt.Printf("\n>>> Verifying Receipt #%d <<<\n", i+1)

		if r.Mode == "unlimited" {
			w := r.Weights
			if w == "" {
				w = globalWeights
				if w != "" {
					fmt.Println("(Using global weights)")
				}
			}
			runUnlimited(r.Seed, r.IssueID, r.UserID, r.Salt, w)
		} else if r.Mode == "ichiban" {
			rew := r.Rewards
			if rew == "" {
				rew = globalRewards
				if rew != "" {
					fmt.Println("(Using global rewards)")
				}
			}
			runIchiban(r.Seed, r.IssueID, r.SlotIndex, rew)
		} else {
			fmt.Printf("Unknown or missing mode in JSON: %s\n", r.Mode)
		}
	}
}

// Helper to decode Hex inside main pkg if needed,
// but verify.go already has decodeHex.
// As they are in the same package 'main', main.go can call functions in verify.go
