pipeline {
    agent { label 'mac' }
    
    triggers {
		pollSCM('* * * * *')
    }

    parameters {
        string(name: 'PRJ_URL', defaultValue: '<https://github.com/Geekble-Game/SerpentOfTheEnd-client.git>', description: 'GitHub repository URL')
        string(name: 'BRANCH', defaultValue: 'feat/playstore-test', description: 'Branch to build')
        string(name: 'UNITY_VER', defaultValue: '6000.0.26f1', description: 'Unity Editor version')
        string(name: 'BUILD_ROOT', defaultValue: '/Users/msg/Desktop/build', description: 'Build output root directory')
        string(name: 'BUILDFILE_PREFIX', defaultValue: 'SE', description: 'Build File Prefix')
        booleanParam(name: 'UNITY_BUILD_AAB', defaultValue: true, description: 'Build Android App Bundle (AAB)')

        string(name: 'PACKAGE_NAME', defaultValue: 'com.msg.serpent.dev', description: 'Play Store App Package Name')
        choice(name: 'RELEASE_STATUS', choices: ['draft', 'inProgress', 'completed'], description: 'Play store Release status')
        choice(name: 'TRACK', choices: ['internal', 'beta', 'production'], description: 'Play Store Track')

        booleanParam(name: 'SKIP_BUILD', defaultValue: false, description: 'Skip Unity Build (Deploy Only)')
        string(name: 'TEST_AAB_PATH', defaultValue: '', description: 'Manual AAB path to deploy (auto-filled if empty)')
    }

    environment {
        UNITY_PATH = "/Applications/Unity/Hub/Editor/${params.UNITY_VER}/Unity.app/Contents/MacOS/Unity"
    }

    options {
        disableConcurrentBuilds()
        quietPeriod(30)                 // 폴링 감지 후 30초 대기 (같은 커밋 중복 방지용)
        buildDiscarder(logRotator(numToKeepStr: '10'))  // 오래된 빌드 정리
    }

    stages {
        stage('Clean Workspace') {
            steps { cleanWs() }
        }

        stage('Checkout with Submodules') {
				    steps {
				        script {
					         
				            def (repoName, projectName) = parseProjectInfoFromRepoUrl(params.PRJ_URL)
				            env.BUILD_REPO_NAME = repoName
				            env.BUILD_PROJECT_NAME = projectName
				
				            try {
				                checkout([
				                    $class: 'GitSCM',
				                    branches: [[name: "*/${params.BRANCH}"]],
				                    userRemoteConfigs: [[
				                        url: "${params.PRJ_URL}",
				                        credentialsId: 'GITHUB_ACCESS_TOKEN'
				                    ]],
				                    extensions: [
				                        [$class: 'RelativeTargetDirectory', relativeTargetDir: "${repoName}"]
				                    ]
				                ])
				
				                withCredentials([string(credentialsId: 'GITHUB_ACCESS_TOKEN_STRING', variable: 'GITHUB_TOKEN')]) {
				                    sh """
				                        cd ${repoName}
				                        git submodule init
				                        git config submodule.SerpentOfTheEnd/Assets/GeekLib.url <https://${GITHUB_TOKEN}@github.com/Geekble-Game/GeekLib.git>
				                        git submodule update --recursive
				                    """
				                }
				
				            } catch (e) {
				                echo "[ERROR] Git checkout 실패: ${e.getMessage()}"
				                currentBuild.description = 'PROJECT_CLONE_FAIL'
				                error("[ABORTED] Git checkout 실패로 인해 파이프라인 중단")
				            }
				        }
				    }
				}

        stage('Init Project Info') {
            steps {
                script {
		                env.BUILD_VERSION = getUnityVersionCode()
                    env.BUILD_NAME = getBuildName(env.BUILD_VERSION)
                    env.DEFAULT_BUILD_PATH = "${env.BUILD_PROJECT_NAME}/${params.BRANCH}/${env.BUILD_VERSION}"
                    env.LOG_DIR = "${params.BUILD_ROOT}/${env.DEFAULT_BUILD_PATH}/Logs"
                    env.BUILD_DIR = "${params.BUILD_ROOT}/${env.DEFAULT_BUILD_PATH}/Builds/android"

                    if (params.SKIP_BUILD && !params.TEST_AAB_PATH) {
                        env.BUILD_OUTPUT_PATH = "${params.BUILD_ROOT}/${env.DEFAULT_BUILD_PATH}/Builds/android/${env.BUILD_NAME}.aab"
                    }
                }
            }
        }
        
        stage('Send Build Start Alarm') {
            steps {
                script {
                    sendSlack("START", "#439FE0")
                }
            }
        }
        
        stage('Extract Version') {
		        steps {
                script {
		                try {
		                    def actualPackageName = getUnityPackageName()
		                    if (actualPackageName != params.PACKAGE_NAME) {
		                        error *"[*ERROR] PACKAGE_NAME mismatch. Pipeline param: '${params.PACKAGE_NAME}', Project setting: '${actualPackageName}'"
		                    }
		                } catch (e) {
				                currentBuild.description = "PACKAGE_NAME_MISMATCH"
				                error("[ABORTED] Unity 패키지명 불일치로 인한 파이프라인 중단")
		                }    
                }
            }
        }

        stage('Build Unity Android') {
				    when { expression { return !params.SKIP_BUILD } }
				    options { timeout(time: 1, unit: 'HOURS') }
				    steps {
				        withCredentials([
				            string(credentialsId: 'SE_KEYSTORE_PASSWORD', variable: 'KEYSTORE_PASS'),
				            string(credentialsId: 'SE_KEY_ALIAS', variable: 'KEY_ALIAS'),
				            string(credentialsId: 'SE_KEY_ALIAS_PASSWORD', variable: 'KEY_ALIAS_PASS')
				        ]) {
				            script {
				                def outputDir = "${env.BUILD_DIR}"
				                def logDir = "${env.LOG_DIR}"
				                def ext = (params.UNITY_BUILD_AAB) ? 'aab' : 'apk'
				                def filePath = "${outputDir}/${env.BUILD_NAME}.${ext}"
				                def logPath = "${env.LOG_DIR}/${env.BUILD_NAME}.log"
				
				                def projectPath = "${env.WORKSPACE}/${env.BUILD_REPO_NAME}/${env.BUILD_PROJECT_NAME}"
				                def keystorePath = "${env.WORKSPACE}/${env.BUILD_REPO_NAME}/${env.BUILD_PROJECT_NAME}.keystore"
				                
				                if (fileExists(filePath)) {
                                    echo "[INFO] 이전 빌드 파일 삭제: ${filePath}"
                                    sh "rm -f '${filePath}'"
                                }
                                if (fileExists(logPath)) {
                                    echo "[INFO] 이전 로그 파일 삭제: ${logPath}"
                                    sh "rm -f '${logPath}'"
                                }
				                
				                // 환경변수 설정
				                env.BUILD_OUTPUT_PATH = filePath
				                env.BUILD_LOG_PATH = logPath
				
				                try {
				                    withEnv([
				                        "UNITY_BUILD_PATH=${filePath}",
				                        "UNITY_BUILD_AAB=${params.UNITY_BUILD_AAB}",
				                        "KEYSTORE_PATH=${keystorePath}",
				                        "KEYSTORE_PASS=${KEYSTORE_PASS}",   // 주의: env.XXX 사용 안함
				                        "KEY_ALIAS=${KEY_ALIAS}",
				                        "KEY_ALIAS_PASS=${KEY_ALIAS_PASS}"
				                    ]) {
				                        sh """
				                            mkdir -p "${logDir}"
				
				                            "${env.UNITY_PATH}" \
				                              -batchmode \
				                              -nographics \
				                              -projectPath "${projectPath}" \
				                              -executeMethod AndroidBuilder.BuildAndroid \
				                              -quit \
				                              -logFile "${logPath}" \
				                              -buildTarget android
				                        """
				                    }
				                    
				                    uploadToS3(filePath, "${env.BUILD_DIR}")
				
				                    if (!fileExists(filePath)) {
				                        error "[ERROR] Unity Build Output File not found: ${filePath}"
				                    }
				
				                } catch (err) {
				                    currentBuild.description = "BUILD_FAILED"
				                    env.BUILD_FAIL_LOG_URL = uploadToS3(logPath, "${env.DEFAULT_BUILD_PATH}/logs")
				                    error "[BUILD ERROR] Unity 빌드 실패: ${err.message}"
				                }
				            }
				        }
				    }
				}

        stage('Deploy to Play Store') {
            when { expression { return currentBuild.result != 'FAILURE' } }
            steps {
                withCredentials([file(credentialsId: 'GOOGLE_PLAYSTORE_API_JSON', variable: 'GOOGLE_PLAY_JSON')]) {
                    script {
                        def fastlaneLogPath = "${env.LOG_DIR}/fastlane_deploy.log"
                        def deployAabPath = params.SKIP_BUILD ? params.TEST_AAB_PATH : env.BUILD_OUTPUT_PATH
                      def defaultLang = "ko-KR"

                        sh """
												    mkdir -p "${env.WORKSPACE}/${env.BUILD_REPO_NAME}/metadata/android/${params.TRACK}/${defaultLang}/changelogs"
												"""
						
                        try {
                            withEnv([
															  "UNITY_BUILD_VERSION=${env.BUILD_VERSION}",
															  "AAB_PATH=${env.BUILD_OUTPUT_PATH}",
															  "GOOGLE_PLAY_JSON_KEY=${GOOGLE_PLAY_JSON}",
															  "PACKAGE_NAME=${params.PACKAGE_NAME}",
																  "TRACK=${params.TRACK}",
															  "RELEASE_STATUS=${params.RELEASE_STATUS}",
															  "DEFAULT_LANGUAGE=${defaultLang}",
															  "PATH=/opt/homebrew/bin:/usr/local/bin:$PATH"
															]) {
                                sh """
                                    set -o pipefail
                                    cd ${env.WORKSPACE}/${env.BUILD_REPO_NAME}
                                    fastlane android deploy 2>&1 | tee "${fastlaneLogPath}"
                                """
                            }
                        } catch (err) {
                            currentBuild.description = "DEPLOY_FAILED"
                            throw err
                        } finally {
                            if (fileExists(fastlaneLogPath)) {
                                env.BUILD_FASTLANE_LOG_URL = uploadToS3(fastlaneLogPath, "${env.DEFAULT_BUILD_PATH}/logs")
                            } else {
                                echo "[WARN] fastlane log not found: ${fastlaneLogPath}"
                            }
                        }
                    }
                }
            }
        }

        stage('Upload to S3') {
            when {
                expression {
                    return !["BUILD_FAILED", "DEPLOY_FAILED"].contains(currentBuild.description)
                }
            }
            steps {
                withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: 'AWS_CREDENTIALS']]) {
                    script {
                        env.BUILD_S3_URL = uploadToS3(env.BUILD_OUTPUT_PATH, env.DEFAULT_BUILD_PATH)
                    }
                }
            }
        }
    }

    post {
        always {
            script {
                def failReason = currentBuild.description?.trim()
                if (!failReason || failReason == '') {
                    echo "[WARN] No failure reason set. Assuming SUCCESS."
                    sendSlack("SUCCESS", "#36a64f")
                } else {
                    switch (failReason) {
		                    case "PROJECT_CLONE_FAIL":
                            sendSlack("PROJECT_CLONE_FAIL", "#FF0000")
                            break
                        case "BUILD_FAILED":
                            sendSlack("BUILD_FAILED", "#FF0000")
                            break
                        case "DEPLOY_FAILED":
                            sendSlack("DEPLOY_FAILED", "#FF0000")
                            break
                        default:
                            sendSlack("UNKNOWN", "#808080")
                            break
                    }
                }
            }
        }
    }
}

// 유틸 함수
def parseProjectInfoFromRepoUrl(repoUrl) {
    def repoName = repoUrl.tokenize('/').last().replace('.git', '')
    def projectName = repoName.replaceFirst(/-client$/, '')
    return [repoName, projectName]
}

def getUnityVersionCode() {
    def versionFile = "${env.WORKSPACE}/${env.BUILD_REPO_NAME}/${env.BUILD_PROJECT_NAME}/ProjectSettings/ProjectSettings.asset"
    if (!fileExists(versionFile)) {
        error "[ERROR] ProjectSettings.asset not found: ${versionFile}"
    }
    def versionName = sh(
        script: """
            grep 'bundleVersion:' "${versionFile}" \
            | head -n 1 \
            | sed 's/.*:[ ]*//'
        """,
        returnStdout: true
    ).trim()
    if (!versionName) {
        error "[ERROR] bundleVersion not found in ${versionFile}"
    }
    echo "[INFO] Extracted VERSION_NAME: ${versionName}"
    return "${versionName}"
}

def getUnityPackageName() {
    def settingFile = "${env.WORKSPACE}/${env.BUILD_REPO_NAME}/${env.BUILD_PROJECT_NAME}/ProjectSettings/ProjectSettings.asset"
    if (!fileExists(settingFile)) {
        error "[ERROR] ProjectSettings.asset not found: ${settingFile}"
    }

    def identifier = sh(
    script: """
        awk '/^[[:space:]]*applicationIdentifier:/ { found=1 } found && /Android:/ { print \$2; exit }' "${settingFile}"
		    """,
		    returnStdout: true
		).trim()

    if (!identifier) {
        error "[ERROR] Android package name (applicationIdentifier) not found in ${settingFile}"
    }

    return identifier
}

def getBuildName(version) {
    return "${params.BUILDFILE_PREFIX}_${version}"
}

def uploadToS3(filePath, uploadPath, region = 'ap-northeast-2') {
    def bucketName = "msg-game"
    def s3Path = "s3://${bucketName}/${uploadPath}/android/"
    def fileName = filePath.tokenize('/')[-1]
    def s3Url = "<https://$>{bucketName}.s3.${region}.amazonaws.com/${uploadPath}/android/${fileName}"
    sh """
        export PATH=/usr/local/bin:/opt/homebrew/bin:\$PATH
        aws s3 cp "${filePath}" "${s3Path}" --region ${region}
    """
    echo "[INFO] Uploaded to S3: ${s3Url}"
    return s3Url
}

def sendSlack(String status, String color) {
    def slackTitle = getSlackTitle(status)

    def fields = [
        ["title": "Project", "value": "${env.BUILD_PROJECT_NAME}", "short": true],
        ["title": "Branch", "value": "${params.BRANCH}", "short": true],
        ["title": "Job", "value": "${env.JOB_NAME} #${env.BUILD_NUMBER}", "short": true]
    ]

    def logLinks = []
    if (status == "SUCCESS") {
        if (env.BUILD_S3_URL) {
            logLinks << "<${env.BUILD_S3_URL}|📦 ${env.BUILD_NAME}.aab>"
        }
    } else {
        if (env.BUILD_FAIL_LOG_URL) {
            logLinks << "<${env.BUILD_FAIL_LOG_URL}|🧾 ${env.BUILD_NAME}_build.log>"
        }
        if (env.BUILD_FASTLANE_LOG_URL) {
            logLinks << "<${env.BUILD_FASTLANE_LOG_URL}|🛠️ ${env.BUILD_NAME}_fastlane_deploy.log>"
        }
    }

    if (logLinks) {
        fields << ["title": (status == "SUCCESS") ? "Artifacts" : "Logs", "value": logLinks.join("\n"), "short": false]
    }

    if (status == "SUCCESS" && params.RELEASE_STATUS == 'draft') {
        withCredentials([
		        string(credentialsId: 'PLAYSTORE_DEVELOPER_ID', variable: 'DEV_ID'),
		        string(credentialsId: 'SE_PLAYSTORE_PROJECT_ID', variable: 'PRJ_ID')
        ]) {
            def releaseUrl = "<https://play.google.com/console/u/0/developers/${DEV_ID}/app/${PRJ_ID}/tracks/internal-testing?releaseType=defaultReleases>"
            fields << [
                "title": "릴리스 승인 페이지",
                "value": "<${releaseUrl}|Google Play Console로 이동하기>\n(내부 테스트 릴리즈 승인 필요)",
                "short": false
            ]
        }
    }

    def payload = [
        text: "${slackTitle}",
        attachments: [[color: "${color}", fields: fields]]
    ]

    def curlBody = groovy.json.JsonOutput.toJson(payload)

    withCredentials([string(credentialsId: 'TEST_SLACK_URL', variable: 'SLACK_URL')]) {
        sh """
            curl -X POST -H 'Content-type: application/json' --data '${curlBody}' "$SLACK_URL"
        """
    }
}

def getSlackTitle(String status) {
    switch (status) {
        case "START": return "🚀 *Unity Android 빌드 시작*"
        case "SUCCESS": return "✅ *Unity Android 빌드 및 배포 성공*"
        case "PACKAGE_NAME_MISMATCH": return "❌ *Unity 프로젝트 패키지명 불일치*"
        case "PROJECT_CLONE_FAIL": return "❌ *Unity 프로젝트 클론 실패*"
        case "BUILD_FAILED": return "❌ *Unity Android 빌드 실패*"
        case "DEPLOY_FAILED": return "❌ *Play Store 업로드 실패*"
        default: return "❌ *파이프라인 실패 (원인 미상)*"
    }
}