package jp.juggler.subwaytooter.table

import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabaseCorruptException
import android.database.sqlite.SQLiteOpenHelper
import android.provider.BaseColumns
import jp.juggler.util.data.TableCompanion
import jp.juggler.util.data.getBlobOrNull
import jp.juggler.util.data.getLong
import jp.juggler.util.log.LogCategory
import java.util.concurrent.TimeUnit

private val log = LogCategory("EmojiCacheDatabase")

// カスタム絵文字のキャッシュ専用のデータベースファイルを作る
// (DB破損などの際に削除してしまえるようにする)
private const val CACHE_DB_NAME = "emoji_cache_db"
private const val CACHE_DB_VERSION = 1

class EmojiCache(
    val id: Long,
    val timeUsed: Long,
    val data: ByteArray,
) {
    companion object : TableCompanion {

        override val table = "custom_emoji_cache"

        const val COL_ID = BaseColumns._ID
        const val COL_TIME_SAVE = "time_save"
        const val COL_TIME_USED = "time_used"
        const val COL_URL = "url"
        const val COL_DATA = "data"

        override fun onDBCreate(db: SQLiteDatabase) {
            db.execSQL(
                """create table if not exists $table
						($COL_ID INTEGER PRIMARY KEY
						,$COL_TIME_SAVE integer not null
						,$COL_TIME_USED integer not null
						,$COL_URL text not null
						,$COL_DATA blob not null
						)""".trimIndent()
            )
            db.execSQL("create unique index if not exists ${table}_url on $table($COL_URL)")
            db.execSQL("create index if not exists ${table}_old on $table($COL_TIME_USED)")
        }

        override fun onDBUpgrade(
            db: SQLiteDatabase,
            oldVersion: Int,
            newVersion: Int,
        ) {
        }
    }

    class Access(val db: SQLiteDatabase) {
        fun load(url: String, now: Long) =
            db.rawQuery(
                "select $COL_ID,$COL_TIME_USED,$COL_DATA from $table where $COL_URL=?",
                arrayOf(url)
            )?.use { cursor ->
                if (cursor.moveToNext()) {
                    EmojiCache(
                        id = cursor.getLong(COL_ID),
                        timeUsed = cursor.getLong(COL_TIME_USED),
                        data = cursor.getBlobOrNull(COL_DATA)!!
                    ).apply {
                        if (now - timeUsed >= 5 * 3600000L) {
                            db.update(
                                table,
                                ContentValues().apply {
                                    put(COL_TIME_USED, now)
                                },
                                "$COL_ID=?",
                                arrayOf(id.toString())
                            )
                        }
                    }
                } else {
                    null
                }
            }

        fun sweep(now: Long) {
            val expire = now - TimeUnit.DAYS.toMillis(30)
            db.delete(
                table,
                "$COL_TIME_USED < ?",
                arrayOf(expire.toString())
            )
        }

        fun update(url: String, data: ByteArray) {
            val now = System.currentTimeMillis()
            db.replace(table,
                null,
                ContentValues().apply {
                    put(COL_URL, url)
                    put(COL_DATA, data)
                    put(COL_TIME_USED, now)
                    put(COL_TIME_SAVE, now)
                }
            )
        }
    }
}

class EmojiCacheDbOpenHelper(val context: Context) :
    SQLiteOpenHelper(context, CACHE_DB_NAME, null, CACHE_DB_VERSION) {

    private val tables = arrayOf(EmojiCache)
    override fun onCreate(db: SQLiteDatabase) =
        tables.forEach { it.onDBCreate(db) }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) =
        tables.forEach { it.onDBUpgrade(db, oldVersion, newVersion) }

    fun deleteDatabase() {
        try {
            close()
        } catch (ex: Throwable) {
            log.e(ex, "deleteDatabase: close() failed.")
        }
        try {
            SQLiteDatabase.deleteDatabase(context.getDatabasePath(databaseName))
        } catch (ex: Throwable) {
            log.e(ex, "deleteDatabase failed.")
        }
    }

    // DB処理を行い、SQLiteDatabaseCorruptExceptionを検出したらDBを削除してリトライする
    fun <T : Any> access(block: EmojiCache.Access.() -> T?): T? {
        for (nTry in 0 until 3) {
            try {
                val db = writableDatabase
                if (db == null) {
                    log.e("access[$nTry]: writableDatabase returns null.")
                    break
                }
                return EmojiCache.Access(db).block()
            } catch (ex: SQLiteDatabaseCorruptException) {
                log.e(ex, "access[$nTry]: db corrupt!")
                deleteDatabase()
            } catch (ex: Throwable) {
                log.e(ex, "access[$nTry]: failed.")
                break
            }
        }
        return null
    }
}
