package internal

import (
	"bufio"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"fmt"
	"os"
	"strings"

	"golang.org/x/crypto/blake2b"
	"golang.org/x/crypto/sha3"
)

// HashCrafter represents the main hash crafting engine
type HashCrafter struct {
	strings            []string
	separators         []string
	hashType           string
	targetHash         string
	verbose            bool
	combinationsTested int
}

// NewHashCrafter creates a new HashCrafter instance
func NewHashCrafter(inputFile, separators, hashType, targetHash string, verbose bool) *HashCrafter {
	return &HashCrafter{
		separators: parseSeparators(separators),
		hashType:   hashType,
		targetHash: strings.ToLower(targetHash),
		verbose:    verbose,
	}
}

// LoadStrings reads strings from the input file
func (hc *HashCrafter) LoadStrings(inputFile string) error {
	file, err := os.Open(inputFile)
	if err != nil {
		return fmt.Errorf("failed to open input file: %w", err)
	}
	defer file.Close()

	var stringList []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line != "" {
			stringList = append(stringList, line)
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("error reading file: %w", err)
	}

	if len(stringList) == 0 {
		return fmt.Errorf("no valid strings found in input file")
	}

	hc.strings = stringList
	if hc.verbose {
		fmt.Printf("Loaded %d strings from %s\n", len(stringList), inputFile)
		fmt.Printf("Using separators: %v\n", hc.separators)
	}

	return nil
}

// FindMatch generates all permutations and checks for hash matches
func (hc *HashCrafter) FindMatch() ([]string, error) {
	if len(hc.strings) == 0 {
		return nil, fmt.Errorf("no strings loaded")
	}

	permutations := hc.generatePermutations()
	hc.combinationsTested = len(permutations)
	if hc.verbose {
		fmt.Printf("Generated %d permutations\n", len(permutations))
	}

	var matches []string
	hashFunc := hc.getHashFunction()

	for _, perm := range permutations {
		hash := hashFunc(perm)
		if strings.ToLower(hash) == hc.targetHash {
			matches = append(matches, perm)
			if hc.verbose {
				fmt.Printf("Match found: %s -> %s\n", perm, hash)
			}
		}
	}

	return matches, nil
}

// generatePermutations creates all possible combinations of strings with separators
func (hc *HashCrafter) generatePermutations() []string {
	var result []string
	seen := make(map[string]bool)

	// Generate all possible subsets of strings (powerset)
	subsets := hc.generateSubsets(hc.strings)

	// For each subset, generate all permutations
	for _, subset := range subsets {
		if len(subset) == 0 {
			continue
		}

		// Generate all permutations of the subset
		permutations := hc.permute(subset)

		// For each permutation, generate combinations with different separators
		for _, perm := range permutations {
			// Add the permutation without separators
			noSep := strings.Join(perm, "")
			if !seen[noSep] {
				result = append(result, noSep)
				seen[noSep] = true
			}

			// Add combinations with separators
			for _, sep := range hc.separators {
				withSep := strings.Join(perm, sep)
				if !seen[withSep] {
					result = append(result, withSep)
					seen[withSep] = true
				}
			}
		}
	}

	return result
}

// GetCombinationsTested returns the number of combinations that were tested
func (hc *HashCrafter) GetCombinationsTested() int {
	return hc.combinationsTested
}

// generateSubsets generates all possible subsets of a slice of strings (powerset)
func (hc *HashCrafter) generateSubsets(arr []string) [][]string {
	n := len(arr)
	total := 1 << n // 2^n subsets

	var result [][]string
	for i := 0; i < total; i++ {
		var subset []string
		for j := 0; j < n; j++ {
			if i&(1<<j) != 0 {
				subset = append(subset, arr[j])
			}
		}
		result = append(result, subset)
	}

	return result
}

// permute generates all possible permutations of a slice of strings
func (hc *HashCrafter) permute(arr []string) [][]string {
	if len(arr) == 1 {
		return [][]string{arr}
	}

	var result [][]string
	for i, v := range arr {
		// Create a copy of the array without the current element
		rest := make([]string, len(arr)-1)
		copy(rest[:i], arr[:i])
		copy(rest[i:], arr[i+1:])

		// Get permutations of the rest
		perms := hc.permute(rest)

		// Prepend the current element to each permutation
		for _, perm := range perms {
			newPerm := make([]string, len(perm)+1)
			newPerm[0] = v
			copy(newPerm[1:], perm)
			result = append(result, newPerm)
		}
	}

	return result
}

// getHashFunction returns the appropriate hash function based on hash type
func (hc *HashCrafter) getHashFunction() func(string) string {
	switch hc.hashType {
	case "md5":
		return func(s string) string {
			h := md5.Sum([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha1":
		return func(s string) string {
			h := sha1.Sum([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha224":
		return func(s string) string {
			h := sha256.Sum224([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha256":
		return func(s string) string {
			h := sha256.Sum256([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha384":
		return func(s string) string {
			h := sha512.Sum384([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha512":
		return func(s string) string {
			h := sha512.Sum512([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha3-224":
		return func(s string) string {
			h := sha3.Sum224([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha3-256":
		return func(s string) string {
			h := sha3.Sum256([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha3-384":
		return func(s string) string {
			h := sha3.Sum384([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "sha3-512":
		return func(s string) string {
			h := sha3.Sum512([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	case "blake2b-256":
		return func(s string) string {
			h, _ := blake2b.New256(nil)
			h.Write([]byte(s))
			return fmt.Sprintf("%x", h.Sum(nil))
		}
	case "blake2b-384":
		return func(s string) string {
			h, _ := blake2b.New384(nil)
			h.Write([]byte(s))
			return fmt.Sprintf("%x", h.Sum(nil))
		}
	case "blake2b-512":
		return func(s string) string {
			h, _ := blake2b.New512(nil)
			h.Write([]byte(s))
			return fmt.Sprintf("%x", h.Sum(nil))
		}
	default:
		// Default to SHA256
		return func(s string) string {
			h := sha256.Sum256([]byte(s))
			return fmt.Sprintf("%x", h)
		}
	}
}

// parseSeparators splits the comma-separated separator string into a slice
func parseSeparators(separators string) []string {
	if separators == "" {
		return []string{""}
	}

	parts := strings.Split(separators, ",")
	var result []string
	for _, part := range parts {
		// Don't trim spaces, just trim other whitespace
		trimmed := strings.TrimFunc(part, func(r rune) bool {
			return r != ' ' && (r == '\t' || r == '\n' || r == '\r')
		})
		if trimmed != "" {
			result = append(result, trimmed)
		}
	}

	if len(result) == 0 {
		return []string{}
	}

	return result
}
