package com.za.common.log

import android.app.Application
import android.content.Context
import android.util.Log
import androidx.work.Configuration
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.blankj.utilcode.constant.MemoryConstants
import com.blankj.utilcode.util.AppUtils
import com.blankj.utilcode.util.DeviceUtils
import com.blankj.utilcode.util.FileUtils
import com.blankj.utilcode.util.TimeUtils
import com.za.common.GlobalData
import com.za.common.util.AppFileManager
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
import java.io.BufferedWriter
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.FileWriter
import java.io.IOException
import java.io.PrintWriter
import java.io.StringWriter
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread

object LogUtil {
	private var context : Application? = null
	private var logDestinationPath : String? = null
	private var orderLogDirPath : String? = null
	private var normalLogDirPath : String? = null
	private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
	private val logBuffer = StringBuilder()
	private val isWriting = AtomicBoolean(false)

	fun init(context : Application) {
		this.context = context

		logDestinationPath = AppFileManager.getLogPath(context).also { path ->
			createDirectoryIfNotExists(path)
			orderLogDirPath =
				"$path${File.separator}order_log".also { createDirectoryIfNotExists(it) }
			normalLogDirPath =
				"$path${File.separator}normal_log".also { createDirectoryIfNotExists(it) }
		}

		initializeWorkManager(context)
	}

	private fun createDirectoryIfNotExists(path : String) {
		File(path).apply { if (! exists()) mkdir() }
	}

	private fun initializeWorkManager(context : Application) {
		if (! WorkManager.isInitialized()) {
			WorkManager.initialize(context,
				Configuration.Builder().setMinimumLoggingLevel(Log.INFO).build())
		}

		WorkManager.getInstance(context).apply {
			cancelAllWorkByTag("logWorkRequest")
			enqueue(PeriodicWorkRequest.Builder(LogTask::class.java, 20, TimeUnit.MINUTES)
				.addTag("logWorkRequest").build())
		}
	}

	fun print(tag : String, content : String) {
		val time = getCurrentTime()
		val logEntry = "$time---$tag---$content\n"
		Log.e("normal", "$tag---$content")
		synchronized(logBuffer) {
			logBuffer.append(logEntry)
		}

		if (logBuffer.length > 4096) {
			coroutineScope.launch {
				flushBuffer()
			}
		}
	}

	fun print(tag : String, throwable : Throwable) {
		val content = StringWriter()
		val printWriter = PrintWriter(content)
		throwable.printStackTrace(printWriter)
		print(tag, content.toString())
	}

	private suspend fun flushBuffer() = withContext(Dispatchers.IO) {
		if (! isWriting.compareAndSet(false, true)) return@withContext

		val logContent : String
		synchronized(logBuffer) {
			if (logBuffer.isEmpty()) {
				isWriting.set(false)
				return@withContext
			}
			logContent = logBuffer.toString()
			logBuffer.clear()
		}

		try {
			val fileName = "normal_log.txt"
			val logFile = File("$normalLogDirPath${File.separator}$fileName")

			logFile.parentFile?.mkdirs()

			if (! logFile.exists()) {
				logFile.createNewFile()
				addLogHead(logFile, getCurrentTime())
			}

			BufferedWriter(FileWriter(logFile, true)).use { writer ->
				writer.write(logContent)
				writer.flush()
			}

			if (logFile.length() >= 8 * MemoryConstants.MB) {
				rotateLogFile(logFile)
			}
		} catch (e : IOException) {
			Log.e("LogUtil", "Error in flushBuffer: ${e.message}")
		} catch (e : Exception) {
			Log.e("LogUtil", "Error in flushBuffer: ${e.message}")
		} finally {
			isWriting.set(false)
		}
	}

	private fun rotateLogFile(file : File) {
		if (! file.exists()) return

		val newFileName = buildString {
			append(AppUtils.getAppVersionCode())
			append("_")
			append(GlobalData.driverInfoBean?.vehicleName ?: "unknown")
			append("_")
			append(GlobalData.driverInfoBean?.userName ?: "unknown")
			append("_")
			append(TimeUtils.getNowString())
			append(".txt")
		}

		val newFile = File("$normalLogDirPath${File.separator}$newFileName")

		try {
			if (file.renameTo(newFile)) {
				compressAndUploadLog(newFile) // 创建新的日志文件
				file.createNewFile()
				addLogHead(file, getCurrentTime())
			} else {
				print("LogUtil", "Failed to rename log file")
			}
		} catch (e : Exception) {
			print("LogUtil", "Error during log rotation: ${e.message}")
		}
	}

	private fun compressAndUploadLog(logFile : File) = coroutineScope.launch {
		try {
			val compressedFile = File("${logFile.absolutePath}.7z")
			compress(logFile, compressedFile.absolutePath)
			upload(logFile, compressedFile)
		} catch (e : Exception) {
			print("LogUtil", e.toString())
		}
	}

	private fun deleteLog(file : File) {
		try {
			FileUtils.delete(file.absolutePath)
		} catch (e : Exception) {
			e.printStackTrace()
		}
	}

	fun updateNormalLog() {
		thread {
			if (GlobalData.token.isNullOrBlank()) {
				return@thread
			}
			val fileName = "normal_log.txt"
			val file = File("$normalLogDirPath${File.separator}$fileName")
			val reName =
				"${AppUtils.getAppVersionCode()}_${GlobalData.driverInfoBean?.vehicleName}_${GlobalData.driverInfoBean?.userName}_${TimeUtils.getNowString()}.txt"
			val reNamePath = "$normalLogDirPath${File.separator}$reName"
			file.renameTo(File(reNamePath))
			normalLogDirPath?.let { it ->
				File(it).listFiles()?.forEach {
					if (it.length() / MemoryConstants.MB >= 10) {
						deleteLog(it)
						return@thread
					}
					if (it.exists() && ! it.name.contains("normal_log")) {
						if (it.name.contains("7z")) {
							upload(null, desFile = it)
						} else {
							val zipNamePath = it.absolutePath + ".7z"
							val zipFile = File(zipNamePath)
							if (! zipFile.exists()) {
								try {
									zipFile.createNewFile()
								} catch (e : IOException) {
									e.printStackTrace()
								}
							}
							compress(it, zipNamePath)
						}
					}
				}
			}
		}
	}

	private fun compress(srcFile : File, desFilePath : String) {
		try {
			val out = XZCompressorOutputStream(FileOutputStream(desFilePath))
			addToArchiveCompression(out, srcFile, File(desFilePath))
		} catch (e : Exception) {
			e.printStackTrace()
		}
	}

	private fun addToArchiveCompression(sevenZOutputFile : XZCompressorOutputStream,
		srcFile : File,
		desFile : File) {
		if (srcFile.isFile) {
			var inputStream : FileInputStream? = null
			try {
				inputStream = FileInputStream(srcFile)
				val b = ByteArray(2048)
				var count : Int
				while (inputStream.read(b).also { count = it } != - 1) {
					sevenZOutputFile.write(b, 0, count)
				}
				sevenZOutputFile.close()
				inputStream.close()
				upload(srcFile, desFile)
			} catch (e : Exception) {
				e.printStackTrace()
			} finally {
				try {
					sevenZOutputFile.close()
					inputStream !!.close()
				} catch (e : Exception) {
					e.printStackTrace()
				}
			}
		}
	}

	private fun upload(srcFile : File?, desFile : File) {
		val requestBody : RequestBody = desFile.asRequestBody("multipart/form-data".toMediaType())
		val part = MultipartBody.Part.createFormData("file", desFile.name, requestBody)

		val disposable =
			LogRetrofitHelper.getDefaultService().uploadLog(part, desFile.name, "rescue-app")
				.subscribeOn(Schedulers.io()).subscribe({ it ->
					if (it.code == 200) {
						deleteLog(desFile)
					}
					srcFile?.let {
						deleteLog(it)
					}
				}, {}, {})
	}

	private fun addLogHead(file : File, time : String) {
		file.appendBytes("${time}---应用版本---${AppUtils.getAppVersionName()}".toByteArray())
		file.appendBytes("\n".toByteArray())
		file.appendBytes("$time---系统版本---Android${DeviceUtils.getSDKVersionName()} ${DeviceUtils.getSDKVersionCode()}".toByteArray())
		file.appendBytes("\n".toByteArray())
		file.appendBytes("$time---ROM---${DeviceUtils.getManufacturer()} ${DeviceUtils.getModel()}".toByteArray())
		file.appendBytes("\n".toByteArray())
		file.appendBytes("$time---build---${AppUtils.getAppVersionCode()}".toByteArray())
		file.appendBytes("\n".toByteArray())
		file.appendBytes("$time---APP名称---中道救援-司机端".toByteArray())
		file.appendBytes("\n".toByteArray())
		file.appendBytes("$time---车辆名称---${GlobalData.driverInfoBean?.vehicleName}".toByteArray())
		file.appendBytes("\n".toByteArray())
		file.appendBytes("$time---司机名称---${GlobalData.driverInfoBean?.userName ?: GlobalData.driverInfoBean?.userName}".toByteArray())
		file.appendBytes("\n".toByteArray())
	}

	private fun getCurrentTime() : String {
		return TimeUtils.millis2String(System.currentTimeMillis(), "yyyy/MM/dd HH:mm:ss.SSS")
	}

	class LogTask(appContext : Context, workerParams : WorkerParameters) :
		Worker(appContext, workerParams) {
		override fun doWork() : Result {
			updateNormalLog()
			return Result.success()
		}
	}
}