package kr.lunaticbum.back.lun.utils

import kotlin.random.Random

/**
 * [수정] 생성된 스도쿠 퍼즐과 해답을 반환하는 데이터 클래스 (기존과 동일)
 */
data class SudokuPuzzle(
    val solution: String, // N*N x N*N 크기의 완성된 정답 문자열 (예: 81, 256, 625...)
    val question: String  // 빈칸('0')이 포함된 문제 문자열
)

/**
 * [수정] 동적 그리드 생성을 지원하는 스도쿠 생성기
 *
 * @param blockSize (N) 박스 하나의 크기. (기본값: 3)
 * - 2: 4x4 (2x2 블록) 그리드. 숫자 1-4 사용.
 * - 3: 9x9 (3x3 블록) 그리드. 숫자 1-9 사용.
 * - 4: 16x16 (4x4 블록) 그리드. 숫자 1-9, A-G 사용.
 * - 5: 25x25 (5x5 블록) 그리드. 숫자 1-9, A-P 사용.
 */
class SudokuGenerator(private val blockSize: Int = 3) {

    // N*N (예: 3 -> 9, 4 -> 16)
    private val gridSize: Int = blockSize * blockSize
    // N*N x N*N (예: 3 -> 81, 4 -> 256)
    private val totalCells: Int = gridSize * gridSize

    // 그리드 크기에 맞게 보드 동적 생성
    private val board = Array(gridSize) { IntArray(gridSize) }

    // 해답의 개수를 세기 위한 내부 변수
    private var solutionCount = 0

    /**
     * [핵심] 지정된 레벨의 스도쿠 퍼즐과 해답을 생성합니다.
     */
    fun generatePuzzle(level: Int): SudokuPuzzle {
        // 1. 보드 초기화
        _clearBoard()

        // 2. 랜덤한 스도쿠 정답 보드 1개 생성
        _solve()
        val solutionString = _boardToString()

        // 3. 정답 보드에서 빈칸을 뚫어 문제를 생성
        _pokeHoles(level)
        val questionString = _boardToString()

        return SudokuPuzzle(solutionString, questionString)
    }

    /**
     * [신규] DB에서 가져온 solution을 기반으로 문제를 생성
     */
    fun generatePuzzleFromSolution(solutionString: String, level: Int): SudokuPuzzle {
        // 1. DB의 정답 문자열을 2D Int 배열로 변환
        val solutionBoard = _stringToBoard(solutionString)

        // 2. 내부 'board' 필드에 정답 보드를 복사
        for (i in 0 until gridSize) {
            this.board[i] = solutionBoard[i].clone()
        }

        // 3. 정답이 채워진 보드에서 빈칸 뚫기 시작
        _pokeHoles(level)
        val questionString = _boardToString()

        // 4. (정답, 문제) 반환
        return SudokuPuzzle(solutionString, questionString)
    }

    /**
     * [수정] 레벨에 맞춰 '유일한 해답'을 보장하며 빈칸을 뚫는 함수
     * 난이도 기준을 고정된 '빈칸 수'가 아닌 '비율(%)'로 변경하여 모든 그리드 크기에 대응
     */
    private fun _pokeHoles(level: Int) {
        // 난이도별 최소 클루(숫자) 비율, 최대 빈칸 비율
        val (minCluesRatio, maxHolesRatio) = when (level) {
            1 -> Pair(0.49, 0.51) // 아주 쉬움 (최소 49% 숫자 보장 / 9x9에서 약 40개)
            2 -> Pair(0.42, 0.58) // 쉬움 (최소 42% 숫자 보장 / 9x9에서 약 34개)
            3 -> Pair(0.37, 0.63) // 중급 (최소 37% 숫자 보장 / 9x9에서 약 30개)
            4 -> Pair(0.32, 0.68) // 어려움 (최소 32% 숫자 보장 / 9x9에서 약 26개)
            else -> Pair(0.27, 0.73) // 전문가 (최소 27% 숫자 보장 / 9x9에서 약 22개)
        }

        val minClues = (totalCells * minCluesRatio).toInt()
        val maxHoles = (totalCells * maxHolesRatio).toInt()

        var holesMade = 0
        // (0..80) -> (0..totalCells-1)
        val cells = (0 until totalCells).shuffled(Random)

        for (cellIndex in cells) {
            val row = cellIndex / gridSize
            val col = cellIndex % gridSize

            // 이미 빈 칸이면 건너뛰기 (혹시 모를 방어 코드)
            if (board[row][col] == 0) continue

            val temp = board[row][col]
            board[row][col] = 0

            // [!!!] 핵심 수정 지점 [!!!]
            // 9x9 (gridSize=9, blockSize=3) 이하에서만 유일성 검사를 수행합니다.
            var isUnique = true // 기본값은 '유일하다'로 가정
            if (gridSize <= 9) {
                solutionCount = 0
                _countSolutions()
                isUnique = (solutionCount == 1)
            }
            // [!!!] 수정 끝 [!!!]

            if (!isUnique) {
                // 9x9에서 유일 해가 깨졌다면 숫자를 복원
                board[row][col] = temp
            } else {
                // 9x9에서 유일 해가 유지되거나,
                // 16x16 이상이라서 검사를 건너뛰었다면 구멍 뚫기 성공
                holesMade++
            }

            val remainingClues = totalCells - holesMade
            if (holesMade >= maxHoles || (remainingClues <= minClues && holesMade > 0)) {
                break
            }
        }
    }

    /**
     * [수정] 현재 board 상태에서 가능한 모든 해답의 '개수'를 세는 함수
     */
    private fun _countSolutions() {
        val empty = _findEmpty(board)
        if (empty == null) {
            solutionCount++
            return
        }

        val (row, col) = empty
        // (1..9) -> (1..gridSize)
        for (num in 1..gridSize) {
            if (_isValid(num, row, col, board)) {
                board[row][col] = num
                _countSolutions()
                board[row][col] = 0 // 백트래킹

                if (solutionCount > 1) return
            }
        }
    }

    /**
     * [수정] 백트래킹을 사용하여 보드를 채우는 재귀 함수
     */
    private fun _solve(): Boolean {
        val emptyCell = _findEmpty(board) ?: return true

        val row = emptyCell.first
        val col = emptyCell.second

        // (1..9) -> (1..gridSize)
        val numbers = (1..gridSize).shuffled(Random)

        for (num in numbers) {
            if (_isValid(num, row, col, board)) {
                board[row][col] = num
                if (_solve()) {
                    return true
                }
                board[row][col] = 0 // 백트래킹
            }
        }
        return false
    }

    /**
     * [수정] 특정 위치에 숫자를 놓는 것이 유효한지 검사 (blockSize 기반)
     */
    private fun _isValid(num: Int, row: Int, col: Int, board: Array<IntArray>): Boolean {
        // (0..8) -> (0..gridSize-1)
        for (c in 0 until gridSize) {
            if (board[row][c] == num) return false
        }
        for (r in 0 until gridSize) {
            if (board[r][col] == num) return false
        }

        // (3x3 박스) -> (NxN 박스)
        val boxStartRow = row - row % blockSize
        val boxStartCol = col - col % blockSize
        for (r in 0 until blockSize) {
            for (c in 0 until blockSize) {
                if (board[boxStartRow + r][boxStartCol + c] == num) return false
            }
        }
        return true
    }

    /**
     * [수정] 보드에서 비어있는 첫 번째 칸의 좌표를 찾습니다.
     */
    private fun _findEmpty(board: Array<IntArray>): Pair<Int, Int>? {
        // (0..8) -> (0..gridSize-1)
        for (r in 0 until gridSize) {
            for (c in 0 until gridSize) {
                if (board[r][c] == 0) {
                    return Pair(r, c)
                }
            }
        }
        return null
    }

    /**
     * [수정] 보드를 0으로 모두 초기화
     */
    private fun _clearBoard() {
        for (i in 0 until gridSize) {
            for (j in 0 until gridSize) {
                board[i][j] = 0
            }
        }
    }

    /**
     * [신규] 10 이상의 숫자를 'A', 'B' 등으로 변환하는 헬퍼
     */
    private fun _intToChar(num: Int): Char {
        return when (num) {
            0 -> '0'
            in 1..9 -> num.toString()[0]
            in 10..35 -> ('A' + (num - 10)) // 10=A, 11=B ... 35=Z
            else -> '?' // 36 이상은 지원 안 함
        }
    }

    /**
     * [신규] 10진수 보드(IntArray)를 문자열로 변환 (10 -> A)
     */
    private fun _boardToString(): String {
        return board.joinToString("") { row ->
            row.joinToString("") { cell -> _intToChar(cell).toString() }
        }
    }

    // [신규] 문자(A)를 다시 10진수(10)로 변환하는 헬퍼
    private fun _charToInt(char: Char): Int {
        return when (char) {
            '0' -> 0
            in '1'..'9' -> char.toString().toInt()
            in 'A'..'Z' -> (char - 'A') + 10
            else -> -1 // Invalid
        }
    }

    // [신규] 문자열 보드를 10진수 IntArray로 변환 (A -> 10)
    private fun _stringToBoard(puzzleString: String): Array<IntArray> {
        val newBoard = Array(gridSize) { IntArray(gridSize) }
        for (i in 0 until totalCells) {
            val row = i / gridSize
            val col = i % gridSize
            newBoard[row][col] = _charToInt(puzzleString[i])
        }
        return newBoard
    }

    // [신규] 외부에서 정답 검증을 요청할 때 사용할 공개 함수
    fun validate(questionString: String, answerString: String): Boolean {
        if (answerString.length != totalCells || questionString.length != totalCells) {
            return false // 크기가 다름
        }
        if (answerString.contains('0')) {
            return false // 0을 포함 (미완성)
        }

        val answerBoard = _stringToBoard(answerString)
        val questionBoard = _stringToBoard(questionString)

        for (r in 0 until gridSize) {
            for (c in 0 until gridSize) {
                val answerNum = answerBoard[r][c]
                val questionNum = questionBoard[r][c]

                // 1. 답안이 유효한지 (규칙 위반)
                answerBoard[r][c] = 0 // 임시로 비워서 검사
                if (!_isValid(answerNum, r, c, answerBoard)) {
                    return false
                }
                answerBoard[r][c] = answerNum // 원상복구

                // 2. 답안이 원본 문제와 일치하는지 (원본 숫자를 바꿨는지)
                if (questionNum != 0 && questionNum != answerNum) {
                    return false
                }
            }
        }

        return true // 모든 검증 통과
    }
}