package com.aletheia.app.diagnostics

import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.system.exitProcess

class DiagnosticsReporter(
    private val context: Context,
    private val clock: () -> Long = System::currentTimeMillis
) {
    private var installedHandler: Thread.UncaughtExceptionHandler? = null

    fun install() {
        if (installedHandler != null) {
            return
        }

        val previousHandler = Thread.getDefaultUncaughtExceptionHandler()
        val handler = Thread.UncaughtExceptionHandler { thread, error ->
            runCatching {
                record(
                    kind = "fatal",
                    threadName = thread.name,
                    error = error
                )
            }

            if (previousHandler != null && previousHandler !== installedHandler) {
                previousHandler.uncaughtException(thread, error)
            } else {
                exitProcess(10)
            }
        }

        installedHandler = handler
        Thread.setDefaultUncaughtExceptionHandler(handler)
    }

    fun recordNonFatal(source: String, error: Throwable): DiagnosticSnapshot =
        record(
            kind = sanitizeLabel(source).ifBlank { "non-fatal" },
            threadName = Thread.currentThread().name,
            error = error
        )

    fun latestReport(): DiagnosticSnapshot? {
        val file = latestReportFile()
        if (!file.exists()) {
            return null
        }

        val text = file.readText()
        return DiagnosticSnapshot(
            createdAt = file.lastModified(),
            summary = text.lineSequence()
                .firstOrNull { it.startsWith("Error: ") }
                ?.removePrefix("Error: ")
                ?.ifBlank { null }
                ?: "diagnostic report",
            text = text
        )
    }

    fun clearReports() {
        diagnosticsDirectory().deleteRecursively()
    }

    private fun record(kind: String, threadName: String, error: Throwable): DiagnosticSnapshot {
        val createdAt = clock()
        val text = DiagnosticsReportFormatter.build(
            input = DiagnosticsReportInput(
                createdAt = createdAt,
                kind = kind,
                threadName = sanitizeLabel(threadName).ifBlank { "unknown" },
                appVersion = appVersionText(),
                androidSdk = Build.VERSION.SDK_INT,
                error = error
            )
        )

        val file = latestReportFile()
        file.parentFile?.mkdirs()
        file.writeText(text)
        file.setLastModified(createdAt)

        return requireNotNull(latestReport())
    }

    private fun appVersionText(): String {
        val packageInfo = loadPackageInfo(context.packageManager, context.packageName)
        val versionName = packageInfo.versionName ?: "?"
        return "$versionName (${packageInfo.versionCodeCompat()})"
    }

    private fun loadPackageInfo(packageManager: PackageManager, packageName: String): PackageInfo =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
        } else {
            @Suppress("DEPRECATION")
            packageManager.getPackageInfo(packageName, 0)
        }

    private fun diagnosticsDirectory(): File =
        File(context.noBackupFilesDir, DIAGNOSTICS_DIRECTORY).apply { mkdirs() }

    private fun latestReportFile(): File =
        File(diagnosticsDirectory(), LATEST_REPORT_FILE)

    companion object {
        private const val DIAGNOSTICS_DIRECTORY = "diagnostics"
        private const val LATEST_REPORT_FILE = "latest-report.txt"
    }
}

data class DiagnosticSnapshot(
    val createdAt: Long,
    val summary: String,
    val text: String
)

internal data class DiagnosticsReportInput(
    val createdAt: Long,
    val kind: String,
    val threadName: String,
    val appVersion: String,
    val androidSdk: Int,
    val error: Throwable
)

internal object DiagnosticsReportFormatter {
    fun build(input: DiagnosticsReportInput): String = buildString {
        appendLine("Aletheia diagnostic report")
        appendLine("Privacy: sanitized stack frames only; exception messages, book text, file paths, URLs, credentials, and selected text are intentionally omitted.")
        appendLine("Created at: ${formatTimestamp(input.createdAt)}")
        appendLine("Kind: ${sanitizeLabel(input.kind).ifBlank { "diagnostic" }}")
        appendLine("Thread: ${sanitizeLabel(input.threadName).ifBlank { "unknown" }}")
        appendLine("App version: ${sanitizeLabel(input.appVersion).ifBlank { "unknown" }}")
        appendLine("Android SDK: ${input.androidSdk}")
        appendThrowable(input.error)
    }

    private fun StringBuilder.appendThrowable(error: Throwable) {
        var current: Throwable? = error
        var depth = 0
        while (current != null && depth < MAX_CAUSE_DEPTH) {
            appendLine(if (depth == 0) "Error: ${current.javaClass.name}" else "Cause: ${current.javaClass.name}")
            current.stackTrace.take(MAX_STACK_FRAMES).forEach { frame ->
                appendLine("  at ${frame.className}.${frame.methodName}(${frame.fileName ?: "Unknown"}:${frame.lineNumber})")
            }
            current = current.cause
            depth++
        }
    }

    private fun formatTimestamp(timestamp: Long): String =
        SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US).format(Date(timestamp))

    private const val MAX_CAUSE_DEPTH = 5
    private const val MAX_STACK_FRAMES = 64
}

private fun sanitizeLabel(value: String): String =
    value
        .take(120)
        .replace(Regex("[^A-Za-z0-9А-Яа-яёЁ._()\\- ]"), "?")
        .trim()

private fun PackageInfo.versionCodeCompat(): Long =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        longVersionCode
    } else {
        @Suppress("DEPRECATION")
        versionCode.toLong()
    }
