package com.example.studentfaceregistry.upload

import android.content.Context
import android.util.Base64
import com.example.studentfaceregistry.data.Student
import com.example.studentfaceregistry.data.StudentRepository
import com.example.studentfaceregistry.face.SmartEnrollment
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.net.Inet4Address
import java.net.NetworkInterface
import java.net.ServerSocket
import java.net.Socket
import java.nio.charset.StandardCharsets
import kotlin.concurrent.thread

class UploadServer(
    private val context: Context,
    private val repository: StudentRepository
) : AutoCloseable {
    private var serverSocket: ServerSocket? = null
    @Volatile private var running = false
    private var smartEnrollment: SmartEnrollment? = null

    fun start() {
        if (running) return
        val socket = ServerSocket(8080)
        serverSocket = socket
        running = true
        thread(name = "upload-server", isDaemon = true) {
            while (running) {
                val socket = try {
                    serverSocket?.accept()
                } catch (_: Exception) {
                    null
                } ?: break
                handle(socket)
            }
        }
    }

    fun accessUrl(): String {
        val ip = currentIpAddress() ?: "127.0.0.1"
        return "http://$ip:8080"
    }

    override fun close() {
        running = false
        runCatching { serverSocket?.close() }
        serverSocket = null
        runCatching { smartEnrollment?.close() }
        smartEnrollment = null
    }

    private fun handle(socket: Socket) {
        thread(name = "upload-request", isDaemon = true) {
            socket.use { client ->
                val input = BufferedInputStream(client.getInputStream())
                val output = client.getOutputStream()
                val request = readHttpRequest(input)
                when {
                    request.method == "GET" && request.path == "/" -> respondHtml(output)
                    request.method == "POST" && request.path == "/upload" -> handleUpload(request, output)
                    else -> respondText(output, 404, "Not found", "text/plain; charset=utf-8")
                }
            }
        }
    }

    private fun handleUpload(request: HttpRequest, output: OutputStream) {
        val enrollment = getOrCreateEnrollment()
        if (enrollment == null) {
            respondText(output, 503, "Face model unavailable", "text/plain; charset=utf-8")
            return
        }
        val bodyText = String(request.body, StandardCharsets.UTF_8)
        var accepted = 0
        var rejected = 0
        val notices = mutableListOf<String>()

        runCatching {
            val payload = JSONObject(bodyText)
            // 单文件或文件列表上传（支持图片和视频）
            val items = parseUploadItems(payload)
            items.forEachIndexed { index, item ->
                processUploadItem(item.fileName.ifBlank { "student_$index" }, item.base64, item.mimeType, enrollment) { ok, name, error ->
                    if (ok) {
                        accepted += 1
                        notices += "OK $name"
                    } else {
                        rejected += 1
                        notices += "FAIL $name: $error"
                    }
                }
            }
        }.onFailure {
            respondText(output, 400, """{"accepted":0,"rejected":0,"message":${jsonString(it.message ?: "invalid request")}}""", "application/json; charset=utf-8")
            return
        }

        val result = """
            {"accepted":$accepted,"rejected":$rejected,"message":${jsonString(notices.joinToString("\n"))}}
        """.trimIndent()
        respondText(output, 200, result, "application/json; charset=utf-8")
    }

    private fun respondHtml(output: OutputStream) {
        val html = """
            <!doctype html>
            <html>
            <head>
              <meta charset="utf-8">
              <meta name="viewport" content="width=device-width, initial-scale=1">
              <title>学生人脸注册上传</title>
              <style>
                body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin: 24px; max-width: 920px; }
                input, button { font-size: 16px; padding: 10px; margin: 8px 0; }
                .hint { color: #555; line-height: 1.6; }
                pre { background: #f4f4f5; padding: 12px; white-space: pre-wrap; }
              </style>
            </head>
            <body>
              <h2>学生人脸注册上传</h2>
              <p class="hint">选择学生图片或视频后上传，手机会自动按文件类型注册，并优先使用画面中最大的人脸。文件名建议使用 学号_姓名.jpg 或 学号_姓名.mp4。</p>
              <div>
                <label>上传文件夹</label><br>
                <input id="folderFiles" type="file" multiple webkitdirectory directory accept="image/*,video/*"><br>
                <button id="uploadFolderBtn">上传文件夹</button>
              </div>
              <pre id="result"></pre>
              <script>
                const result = document.getElementById('result');
                document.getElementById('uploadFolderBtn').onclick = async () => {
                  result.textContent = '上传中...';
                  const files = document.getElementById('folderFiles').files;
                  let ok = 0;
                  let fail = 0;
                  const logs = [];
                  for (let i = 0; i < files.length; i++) {
                    const file = files[i];
                    result.textContent = `上传中 ${'$'}{i + 1}/${'$'}{files.length}: ${'$'}{file.name}`;
                    const base64 = await toBase64(file);
                    const resp = await fetch('/upload', {
                      method: 'POST',
                      headers: { 'Content-Type': 'application/json' },
                      body: JSON.stringify({ items: [{ fileName: file.name, base64, mimeType: file.type }] })
                    });
                    const text = await resp.text();
                    logs.push(text);
                    try {
                      const parsed = JSON.parse(text);
                      ok += parsed.accepted || 0;
                      fail += parsed.rejected || 0;
                    } catch (e) {
                      fail += 1;
                    }
                  }
                  result.textContent = `完成：成功 ${'$'}{ok}，失败 ${'$'}{fail}\n\n` + logs.join('\n');
                };
                function toBase64(file) {
                  return new Promise((resolve, reject) => {
                    const reader = new FileReader();
                    reader.onload = () => resolve(String(reader.result).split(',')[1]);
                    reader.onerror = reject;
                    reader.readAsDataURL(file);
                  });
                }
              </script>
            </body>
            </html>
        """.trimIndent()
        respondText(output, 200, html, "text/html; charset=utf-8")
    }

    private fun parseUploadItems(root: JSONObject): List<UploadItem> {
        val array = root.optJSONArray("items") ?: root.optJSONArray("images") ?: org.json.JSONArray()
        return buildList {
            for (index in 0 until array.length()) {
                val item = array.optJSONObject(index) ?: continue
                add(
                    UploadItem(
                        fileName = item.optString("fileName", "student_$index"),
                        base64 = item.optString("base64", ""),
                        mimeType = item.optString("mimeType", "")
                    )
                )
            }
        }
    }

    private fun processUploadItem(
        fileName: String,
        base64: String,
        mimeType: String,
        enrollment: SmartEnrollment,
        report: (ok: Boolean, name: String, error: String?) -> Unit
    ) {
        runCatching {
            val data = Base64.decode(base64, Base64.DEFAULT)
            processBytes(fileName, data, mimeType, enrollment, report)
        }.onFailure {
            report(false, fileName, it.message)
        }
    }

    private fun processBytes(
        fileName: String,
        data: ByteArray,
        mimeType: String,
        enrollment: SmartEnrollment,
        report: (ok: Boolean, name: String, error: String?) -> Unit
    ) {
        runCatching {
            val result = runBlocking {
                enrollment.enroll(data, mimeType, fileName)
            }

            when (result) {
                is com.example.studentfaceregistry.face.EnrollmentResult.Success -> {
                    val (studentNo, name) = parseStudent(fileName)
                    repository.add(
                        Student(
                            studentNo = studentNo,
                            name = name,
                            photoUri = "upload://$fileName",
                            embedding = result.embedding
                        )
                    )
                    val sourceInfo = if (result.sourceType == com.example.studentfaceregistry.face.EnrollmentSourceType.VIDEO) {
                        "（视频${result.frameCount}帧）"
                    } else ""
                    report(true, fileName + sourceInfo, null)
                }
                is com.example.studentfaceregistry.face.EnrollmentResult.Failure -> {
                    report(false, fileName, result.reason)
                }
            }
        }.onFailure {
            report(false, fileName, it.message)
        }
    }

    @Synchronized
    private fun getOrCreateEnrollment(): SmartEnrollment? {
        val current = smartEnrollment
        if (current != null) return current

        val created = runCatching { SmartEnrollment(context) }.getOrNull()
        smartEnrollment = created
        return created
    }

    private fun parseStudent(filename: String): Pair<String, String> {
        val base = filename.substringBeforeLast(".")
        val parts = base.split(Regex("[_\\-\\s]+"), limit = 2).map { it.trim() }
        return if (parts.size == 2 && parts[0].isNotEmpty() && parts[1].isNotEmpty()) {
            parts[0] to parts[1]
        } else {
            base to base
        }
    }

    private fun readHttpRequest(input: BufferedInputStream): HttpRequest {
        val headerBytes = ByteArrayOutputStream()
        var previous = -1
        var current = -1
        while (true) {
            current = input.read()
            if (current == -1) break
            headerBytes.write(current)
            if (previous == '\r'.code && current == '\n'.code) {
                val bytes = headerBytes.toByteArray()
                if (bytes.size >= 4 &&
                    bytes[bytes.size - 4] == '\r'.code.toByte() &&
                    bytes[bytes.size - 3] == '\n'.code.toByte() &&
                    bytes[bytes.size - 2] == '\r'.code.toByte() &&
                    bytes[bytes.size - 1] == '\n'.code.toByte()
                ) {
                    break
                }
            }
            previous = current
        }
        val headerText = headerBytes.toString(StandardCharsets.ISO_8859_1.name())
        val lines = headerText.split("\r\n").filter { it.isNotEmpty() }
        val requestLine = lines.firstOrNull().orEmpty().split(" ")
        val method = requestLine.getOrNull(0).orEmpty()
        val path = requestLine.getOrNull(1).orEmpty()
        val contentLength = lines.firstOrNull { it.startsWith("Content-Length:", true) }
            ?.substringAfter(":")?.trim()?.toIntOrNull() ?: 0
        val body = readExactly(input, contentLength)
        return HttpRequest(method, path, body)
    }

    private fun readExactly(input: BufferedInputStream, length: Int): ByteArray {
        val output = ByteArrayOutputStream(length)
        var remaining = length
        val buffer = ByteArray(8192)
        while (remaining > 0) {
            val read = input.read(buffer, 0, minOf(buffer.size, remaining))
            if (read <= 0) break
            output.write(buffer, 0, read)
            remaining -= read
        }
        return output.toByteArray()
    }

    private fun respondText(output: OutputStream, code: Int, text: String, contentType: String) {
        val bytes = text.toByteArray(StandardCharsets.UTF_8)
        val head = buildString {
            append("HTTP/1.1 ")
            append(code)
            append(" ")
            append(statusText(code))
            append("\r\nContent-Type: ")
            append(contentType)
            append("\r\nContent-Length: ")
            append(bytes.size)
            append("\r\nConnection: close\r\n\r\n")
        }.toByteArray(StandardCharsets.ISO_8859_1)
        output.write(head)
        output.write(bytes)
        output.flush()
    }

    private fun statusText(code: Int): String = when (code) {
        200 -> "OK"
        404 -> "Not Found"
        503 -> "Service Unavailable"
        else -> "OK"
    }

    private fun currentIpAddress(): String? {
        return NetworkInterface.getNetworkInterfaces().toList()
            .flatMap { it.inetAddresses.toList() }
            .filterIsInstance<Inet4Address>()
            .firstOrNull { !it.isLoopbackAddress }
            ?.hostAddress
    }

    private fun jsonString(value: String): String = JSONObject.quote(value)
}

private data class HttpRequest(
    val method: String,
    val path: String,
    val body: ByteArray
)

private data class UploadItem(
    val fileName: String,
    val base64: String,
    val mimeType: String = ""
)
