// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: GPL-3.0-or-later

package xyz.apiote.bimba.czwek.repo

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import androidx.core.content.edit
import androidx.core.database.sqlite.transaction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import xyz.apiote.bimba.czwek.api.Server
import xyz.apiote.fruchtfleisch.Reader
import xyz.apiote.fruchtfleisch.Writer
import java.io.File
import java.net.URLEncoder
import java.time.LocalDate
import xyz.apiote.bimba.czwek.R

class OfflineRepository(context: Context) : Repository {
	private val db =
		SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath("favourites").path, null)

	fun saveFeedCache(context: Context, feedInfos: Map<String, FeedInfo>) {
		val file = File(
			context.filesDir, URLEncoder.encode(Server.get(context).apiPath, "utf-8")
		)
		context.getSharedPreferences("offlineFeeds", Context.MODE_PRIVATE).edit {
			putInt("version", FeedInfo.VERSION.toInt())
		}
		val stream = file.outputStream()
		val writer = Writer(stream)
		writer.writeUInt(feedInfos.size.toULong())
		feedInfos.forEach {
			it.value.marshal(stream)
		}
		stream.flush()
		stream.close()
	}

	override suspend fun getFavourite(stopCode: String): Favourite? {
		val cursor =
			db.rawQuery(
				"select sequence, stop_name, feed_id, feed_name, lines from favourites where stop_code = ?",
				listOf(stopCode).toTypedArray()
			)
		if (cursor.count == 0) {
			return null
		}
		cursor.moveToNext()
		val f = Favourite(
			cursor.getInt(0),
			cursor.getString(2),
			cursor.getString(3),
			stopCode,
			cursor.getString(1),
			cursor.getString(4).split("||").filter { it != "" }
		)
		cursor.close()
		return f
	}

	override suspend fun getFavourites(feedIDs: Set<String>): List<Favourite> {
		val whereClause = if (feedIDs.isNotEmpty()) {
			feedIDs.indices.joinToString(prefix = "where feed_id in (", postfix = ")") { "?" }
		} else {
			""
		}
		val cursor =
			db.rawQuery(
				"select sequence, stop_name, feed_id, feed_name, stop_code, lines from favourites $whereClause order by sequence",
				feedIDs.toTypedArray()
			)
		val l = mutableListOf<Favourite>()
		while (cursor.moveToNext()) {
			l.add(
				Favourite(
					cursor.getInt(0),
					cursor.getString(2),
					cursor.getString(3),
					cursor.getString(4),
					cursor.getString(1),
					cursor.getString(5).split("||").filter { it != "" }
				)
			)
		}
		cursor.close()
		return l
	}

	override suspend fun saveFavourite(favourite: Favourite) {
		val sequence = favourite.sequence ?: run {
			val cursor =
				db.rawQuery("select max(ROWID) from favourites", emptyArray<String?>())
			val s = if (cursor.count == 0) {
				0
			} else {
				cursor.moveToNext()
				cursor.getInt(0)
			}
			cursor.close()
			s
		}

		// XXX `on conflict` is not supported on older versions of Android
		val cursor = db.rawQuery(
			"select * from favourites where feed_id = ? and stop_code = ?",
			arrayOf(favourite.feedID, favourite.stopCode)
		)
		if (cursor.count > 0) {
			db.execSQL(
				"update favourites set stop_name = ?, lines = ?, sequence = ? where feed_id = ? and stop_code = ?",
				arrayOf(
					favourite.stopName,
					favourite.lines.joinToString(separator = "||"),
					favourite.sequence,
					favourite.feedID,
					favourite.stopCode
				)
			)
		} else {
			db.execSQL(
				"insert into favourites(sequence, feed_id, feed_name, stop_code, stop_name, lines) values (?, ?,?,?,?,?)",
				arrayOf(
					sequence,
					favourite.feedID,
					favourite.feedName,
					favourite.stopCode,
					favourite.stopName,
					favourite.lines.joinToString(separator = "||"),
				)
			)
		}
		cursor.close()
	}

	override suspend fun saveFavourites(favourites: Set<Favourite>) {
		db.execSQL("delete from favourites")
		db.transaction {
			favourites.forEach {
				saveFavourite(it)
			}
		}
	}

	@Suppress("RedundantNullableReturnType")
	override suspend fun getFeeds(
		context: Context,
		server: Server
	): Map<String, FeedInfo>? {
		val file = File(
			context.filesDir, withContext(Dispatchers.IO) {
				URLEncoder.encode(server.apiPath, "utf-8")
			}
		)
		if (!file.exists()) {
			return emptyMap()
		}

		val version =
			context.getSharedPreferences("offlineFeeds", Context.MODE_PRIVATE).getInt("version", -1)
		if (version < 0) {
			return emptyMap()
		}
		val unmarshaller =
			if (version.toUInt() == FeedInfo.VERSION) FeedInfo::unmarshal else FeedInfoPrev::unmarshal
		val stream = file.inputStream()
		val feeds = mutableMapOf<String, FeedInfo>()
		val n = Reader(stream).readUInt().toULong().toInt()
		repeat(n) {
			val feed = unmarshaller(stream)
			feeds[feed.id] = feed
			if (version.toUInt() != FeedInfo.VERSION) {
				saveFeedCache(context, feeds)
			}
		}
		val feedsWithTransitous = feeds.toMutableMap()
		feedsWithTransitous.put(
			"transitous", FeedInfo(
				"transitous",
				"Transitous",
				context.getString(R.string.transitous_description),
				context.getString(R.string.transitous_attribution),
				LocalDate.now(),
				"",
				QrLocation.NONE,
				"",
				null,
				null,
				false
			)
		)
		return feedsWithTransitous
	}

	override suspend fun getDepartures(
		feedID: String,
		stop: String,
		date: LocalDate?,
		context: Context,
		limit: Int?
	): StopEvents? {
		TODO("Not yet implemented")
	}

	override suspend fun getLocatablesIn(
		context: Context,
		bl: Position,
		tr: Position,
	): List<Locatable>? {
		TODO("Not yet implemented")
	}

	override suspend fun getLine(
		context: Context,
		feedID: String,
		lineName: String,
		lineID: String,
	): Line? {
		TODO("Not yet implemented")
	}

	override suspend fun queryQueryables(
		query: String,
		context: Context
	): List<Queryable>? {
		TODO("Not yet implemented")
	}

	override suspend fun locateQueryables(
		position: Position,
		context: Context
	): List<Queryable>? {
		TODO("Not yet implemented")
	}

	fun close() {
		db.close()
	}
}


fun migrateDB(context: Context) {
	val db = SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath("favourites").path, null)
	db.execSQL("create table if not exists favourites(sequence integer, feed_id text, feed_name text, stop_code text, stop_name text, lines text, primary key(feed_id, stop_code))")
}
