package net.buzzert.kordophonedroid.ui.attachments

import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import coil.Coil
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.annotation.ExperimentalCoilApi
import coil.decode.DataSource
import coil.decode.ImageSource
import coil.disk.DiskCache
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.request.Options
import dagger.hilt.android.qualifiers.ApplicationContext
import net.buzzert.kordophone.backend.server.ChatRepository

const val AVM_LOG: String = "AttachmentImageLoader"

data class AttachmentFetchData(
    val guid: String,
    val preview: Boolean = false
)

class AttachmentImageLoader(
    private val repository: ChatRepository,
    @ApplicationContext val application: Context,
) : ViewModel(), ImageLoaderFactory, Fetcher.Factory<AttachmentFetchData>
{
    init {
        // Register Coil image loader
        Coil.setImageLoader(this)
    }

    override fun newImageLoader(): ImageLoader {
        val factory = this
        return ImageLoader.Builder(application)
            .components {
                // Adds the FetcherFactory
                add(factory)
            }
            .build()
    }

    override fun create(
        data: AttachmentFetchData,
        options: Options,
        imageLoader: ImageLoader
    ): Fetcher {
        return AttachmentFetcher(repository, application, data)
    }
}

private class AttachmentFetcher(
    val repository: ChatRepository,
    val context: Context,
    val data: AttachmentFetchData
): Fetcher {
    val cache = DiskCache.Builder()
        .directory(context.cacheDir.resolve("attachments"))
        .maxSizePercent(0.02)
        .build()

    val cacheKey: String get() { return data.guid + if (data.preview) "_preview" else "" }

    @OptIn(ExperimentalCoilApi::class)
    override suspend fun fetch(): FetchResult {
        // Try loading from cache
        var snapshot = cache.openSnapshot(cacheKey)
        if (snapshot != null) {
            Log.d(AVM_LOG, "Found attachment ${data.guid} in disk cache")
            return SourceResult(
                source = snapshot.toImageSource(),
                dataSource = DataSource.DISK,
                mimeType = null,
            )
        }

        Log.d(AVM_LOG, "Loading attachment ${data.guid} from network")
        val source = repository.fetchAttachmentDataSource(data.guid, data.preview)

        // Save to cache
        val editor = cache.openEditor(cacheKey)
        if (editor != null) {
            Log.d(AVM_LOG, "Writing attachment ${data.guid} to disk cache")

            cache.fileSystem.write(editor.data) {
                source.readAll(this)
            }

            snapshot = editor.commitAndOpenSnapshot()
            if (snapshot != null) {
                return SourceResult(
                    source = snapshot.toImageSource(),
                    dataSource = DataSource.NETWORK,
                    mimeType = null
                )
            }
        }

        // We'll go down this path if for some reason we couldn't save to cache.
        return SourceResult(
            source = ImageSource(source, context),
            dataSource = DataSource.NETWORK,
            mimeType = null,
        )
    }

    @OptIn(ExperimentalCoilApi::class)
    private fun DiskCache.Snapshot.toImageSource(): ImageSource {
        val fileSystem = cache.fileSystem
        return ImageSource(data, fileSystem, cacheKey, this)
    }
}