package com.fabledsword.minstrel.player

import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DataSpec
import androidx.media3.datasource.TransferListener
import com.fabledsword.minstrel.connectivity.NetworkStatusController
import com.fabledsword.minstrel.connectivity.ServerHealth
import java.io.IOException
import java.io.InterruptedIOException

/**
 * DataSource wrapper that fails the network read immediately when
 * [NetworkStatusController] reports a gating state. CacheDataSource only
 * calls this upstream factory on cache misses, so playback of cached audio is
 * unaffected -- only "tap a non-cached track while offline" hits this branch
 * and gets a fast, meaningful error instead of a multi-second network timeout
 * (which then surfaced as a silent decode failure to the user).
 *
 * Wrapping rather than substituting the OkHttp data source lets the cache
 * write path remain intact for when health returns and we DO want to fetch:
 * we keep the same upstream all the time, just gate `open()`.
 */
class OfflineGatedDataSource(
    private val delegate: DataSource,
    private val health: NetworkStatusController,
) : DataSource {

    override fun open(dataSpec: DataSpec): Long {
        gateOnHealth()
        return try {
            val opened = delegate.open(dataSpec)
            health.reportSuccess() // bytes flowing from the server == reachable
            opened
        } catch (e: IOException) {
            health.reportFailure() // real network read failed → arbitrate via /healthz
            throw e
        }
    }

    /** Fast-fail before touching the network when the server can't be reached. */
    private fun gateOnHealth() {
        when (health.state.value) {
            ServerHealth.Offline -> throw OfflineException(
                "Track not in the on-device cache and the device is offline.",
            )
            ServerHealth.ServerDown -> throw OfflineException(
                "Track not in the on-device cache and the Minstrel server is unreachable.",
            )
            // Unstable is non-gating: still try the network. Healthy too.
            ServerHealth.Unstable, ServerHealth.Healthy -> Unit
        }
    }

    override fun close() = delegate.close()
    override fun getUri() = delegate.uri
    override fun read(buffer: ByteArray, offset: Int, length: Int): Int =
        delegate.read(buffer, offset, length)
    override fun addTransferListener(transferListener: TransferListener) =
        delegate.addTransferListener(transferListener)
    override fun getResponseHeaders() = delegate.responseHeaders
}

class OfflineGatedDataSourceFactory(
    private val upstream: DataSource.Factory,
    private val health: NetworkStatusController,
) : DataSource.Factory {
    override fun createDataSource(): DataSource =
        OfflineGatedDataSource(upstream.createDataSource(), health)
}

/**
 * Signals the audio-source error path that the request was denied because the
 * device is offline / the server is unreachable. ExoPlayer's [androidx.media3
 * .common.PlaybackException] catches it via [InterruptedIOException]'s
 * `IOException` ancestor and surfaces it as a SOURCE error, which then flows
 * through the existing [PlaybackErrorReporter] -> snackbar path.
 */
class OfflineException(message: String) : IOException(message)
