package generator

import (
	"math/rand"
	"time"

	"termdoku/internal/solver"
)

// randomizedFullSolution builds a complete valid Sudoku solution using randomized DFS.
func randomizedFullSolution(seed string, timeout time.Duration) (Grid, error) {
	var rng *rand.Rand
	if seed == "" {
		rng = rand.New(rand.NewSource(time.Now().UnixNano()))
	} else {
		rng = rand.New(rand.NewSource(int64(hashStringToUint64(seed))))
	}
	deadline := time.Now().Add(timeout)
	var g Grid
	if fillCellRandom(&g, 0, 0, rng, deadline) {
		return g, nil
	}
	return Grid{}, ErrTimeout
}

func fillCellRandom(g *Grid, row, col int, rng *rand.Rand, deadline time.Time) bool {
	if time.Now().After(deadline) {
		return false
	}
	nextRow, nextCol := row, col+1
	if nextCol == 9 {
		nextRow++
		nextCol = 0
	}
	if row == 9 {
		return true
	}
	vals := []uint8{1, 2, 3, 4, 5, 6, 7, 8, 9}
	rng.Shuffle(len(vals), func(i, j int) { vals[i], vals[j] = vals[j], vals[i] })
	for _, v := range vals {
		if isSafe(*g, row, col, v) {
			g[row][col] = v
			if fillCellRandom(g, nextRow, nextCol, rng, deadline) {
				return true
			}
			g[row][col] = 0
		}
	}
	return false
}

func isSafe(g Grid, row, col int, v uint8) bool {
	for i := 0; i < 9; i++ {
		if g[row][i] == v || g[i][col] == v {
			return false
		}
	}
	r0 := (row / 3) * 3
	c0 := (col / 3) * 3
	for r := r0; r < r0+3; r++ {
		for c := c0; c < c0+3; c++ {
			if g[r][c] == v {
				return false
			}
		}
	}
	return true
}

// carveCellsUnique removes cells while trying to keep a single solution.
func carveCellsUnique(full Grid, targetRemoved int, seed string, timeout time.Duration) (Grid, error) {
	puzzle := full
	var rng *rand.Rand
	if seed == "" {
		rng = rand.New(rand.NewSource(time.Now().UnixNano()))
	} else {
		rng = rand.New(rand.NewSource(int64(hashStringToUint64(seed) + 0x9e3779b97f4a7c15)))
	}
	deadline := time.Now().Add(timeout)
	cells := make([]int, 81)
	for i := 0; i < 81; i++ {
		cells[i] = i
	}
	rng.Shuffle(len(cells), func(i, j int) { cells[i], cells[j] = cells[j], cells[i] })
	removed := 0
	for _, idx := range cells {
		if time.Now().After(deadline) {
			break
		}
		r := idx / 9
		c := idx % 9
		backup := puzzle[r][c]
		puzzle[r][c] = 0
		// Check uniqueness using solver.CountSolutions up to 2
		if solver.CountSolutions(convertToSolverGrid(puzzle), 50*time.Millisecond, 2) != 1 {
			puzzle[r][c] = backup
			continue
		}
		removed++
		if removed >= targetRemoved {
			break
		}
	}
	return puzzle, nil
}

// carveCellsSymmetric removes cells with symmetry pattern while maintaining uniqueness.
func carveCellsSymmetric(full Grid, targetRemoved int, seed string, timeout time.Duration, symmetry SymmetryType) (Grid, error) {
	puzzle := full
	var rng *rand.Rand
	if seed == "" {
		rng = rand.New(rand.NewSource(time.Now().UnixNano()))
	} else {
		rng = rand.New(rand.NewSource(int64(hashStringToUint64(seed) + 0x9e3779b97f4a7c15)))
	}
	deadline := time.Now().Add(timeout)

	// Generate cell pairs based on symmetry type
	var cellPairs [][]struct{ r, c int }
	switch symmetry {
	case SymmetryRotational180:
		cellPairs = generateRotational180Pairs()
	case SymmetryVertical:
		cellPairs = generateVerticalPairs()
	case SymmetryHorizontal:
		cellPairs = generateHorizontalPairs()
	default:
		// Fallback to non-symmetric
		return carveCellsUnique(full, targetRemoved, seed, timeout)
	}

	rng.Shuffle(len(cellPairs), func(i, j int) { cellPairs[i], cellPairs[j] = cellPairs[j], cellPairs[i] })

	removed := 0
	for _, pair := range cellPairs {
		if time.Now().After(deadline) {
			break
		}

		// Try removing all cells in the pair
		backups := make([]uint8, len(pair))
		for i, pos := range pair {
			backups[i] = puzzle[pos.r][pos.c]
			puzzle[pos.r][pos.c] = 0
		}

		// Check if still unique
		if solver.CountSolutions(convertToSolverGrid(puzzle), 50*time.Millisecond, 2) != 1 {
			// Restore if not unique
			for i, pos := range pair {
				puzzle[pos.r][pos.c] = backups[i]
			}
			continue
		}

		removed += len(pair)
		if removed >= targetRemoved {
			break
		}
	}

	return puzzle, nil
}

// generateRotational180Pairs creates cell pairs with 180° rotational symmetry.
func generateRotational180Pairs() [][]struct{ r, c int } {
	var pairs [][]struct{ r, c int }
	used := make(map[int]bool)

	for r := 0; r < 9; r++ {
		for c := 0; c < 9; c++ {
			idx := r*9 + c
			if used[idx] {
				continue
			}

			r2 := 8 - r
			c2 := 8 - c
			idx2 := r2*9 + c2

			if idx == idx2 {
				// Center cell (4,4)
				pairs = append(pairs, []struct{ r, c int }{{r, c}})
			} else {
				pairs = append(pairs, []struct{ r, c int }{{r, c}, {r2, c2}})
				used[idx2] = true
			}
			used[idx] = true
		}
	}

	return pairs
}

// generateVerticalPairs creates cell pairs with vertical symmetry.
func generateVerticalPairs() [][]struct{ r, c int } {
	var pairs [][]struct{ r, c int }
	used := make(map[int]bool)

	for r := 0; r < 9; r++ {
		for c := 0; c < 9; c++ {
			idx := r*9 + c
			if used[idx] {
				continue
			}

			c2 := 8 - c
			idx2 := r*9 + c2

			if c == c2 {
				// Center column
				pairs = append(pairs, []struct{ r, c int }{{r, c}})
			} else {
				pairs = append(pairs, []struct{ r, c int }{{r, c}, {r, c2}})
				used[idx2] = true
			}
			used[idx] = true
		}
	}

	return pairs
}

// generateHorizontalPairs creates cell pairs with horizontal symmetry.
func generateHorizontalPairs() [][]struct{ r, c int } {
	var pairs [][]struct{ r, c int }
	used := make(map[int]bool)

	for r := 0; r < 9; r++ {
		for c := 0; c < 9; c++ {
			idx := r*9 + c
			if used[idx] {
				continue
			}

			r2 := 8 - r
			idx2 := r2*9 + c

			if r == r2 {
				// Center row
				pairs = append(pairs, []struct{ r, c int }{{r, c}})
			} else {
				pairs = append(pairs, []struct{ r, c int }{{r, c}, {r2, c}})
				used[idx2] = true
			}
			used[idx] = true
		}
	}

	return pairs
}

func convertToSolverGrid(g Grid) solver.Grid {
	var s solver.Grid
	for r := 0; r < 9; r++ {
		for c := 0; c < 9; c++ {
			s[r][c] = g[r][c]
		}
	}
	return s
}

// Simple FNV-1a 64-bit hash for seed strings.
func hashStringToUint64(s string) uint64 {
	const (
		offset64 = 1469598103934665603
		prime64  = 1099511628211
	)
	h := uint64(offset64)
	for i := 0; i < len(s); i++ {
		h ^= uint64(s[i])
		h *= prime64
	}
	return h
}
