package com.doctate.watch

import android.app.Application
import android.util.Log
import androidx.wear.tiles.TileService
import com.doctate.watch.audio.PendingStore
import com.doctate.watch.domain.CaseStore
import com.doctate.watch.domain.DiskCaseStore
import com.doctate.watch.domain.StartupCleanup
import com.doctate.watch.net.HttpClientProvider
import com.doctate.watch.net.OnelinerOverrideClient
import com.doctate.watch.net.OnelinersClient
import com.doctate.watch.net.SnapshotCache
import com.doctate.watch.net.UploadClient
import com.doctate.watch.settings.BuildConfigSettings
import com.doctate.watch.settings.Settings
import com.doctate.watch.sync.SyncKick
import com.doctate.watch.sync.SyncServiceLauncher
import com.doctate.watch.sync.SyncStateProvider
import com.doctate.watch.sync.SyncTrigger
import com.doctate.watch.tile.DoctateTileService
import java.io.File
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient

/**
 * Application-level service locator. Singletons are exposed via lazy properties so
 * consumers (ViewModelFactories, etc.) pull them through `(applicationContext as DoctateApp)`.
 */
class DoctateApp : Application() {
    val settings: Settings by lazy { BuildConfigSettings() }
    val httpClient: OkHttpClient by lazy { HttpClientProvider.create() }
    val uploadClient: UploadClient by lazy { UploadClient(httpClient, settings) }
    val onelinersClient: OnelinersClient by lazy { OnelinersClient(httpClient, settings) }
    val onelinerOverrideClient: OnelinerOverrideClient by lazy {
        OnelinerOverrideClient(httpClient, settings)
    }
    val snapshotCache: SnapshotCache by lazy {
        SnapshotCache(File(filesDir, "recordings/snapshot_cache.json"))
    }

    /** Persistent disk-backed marker store. Single source of truth for the case list. */
    val caseStore: CaseStore by lazy {
        DiskCaseStore(File(filesDir, "recordings/cases"))
    }

    /** Persistent FIFO queue of recordings waiting to be uploaded. */
    val pendingStore: PendingStore by lazy {
        PendingStore(File(filesDir, "recordings/unsynced"))
    }

    /** Routes sync kicks into [com.doctate.watch.sync.SyncService]. */
    val syncTrigger: SyncTrigger = SyncServiceLauncher

    /** Single source of truth for the worker's status, observed by UI/Tile. */
    val syncStateProvider: SyncStateProvider by lazy { SyncStateProvider() }

    /** Process-scoped coroutine scope for work that must outlive any single ViewModel
     *  (e.g. the post-stop OneLiner-burst fires after the user swipes back to the list). */
    val applicationScope: CoroutineScope by lazy {
        CoroutineScope(SupervisorJob() + Dispatchers.Default)
    }

    override fun onCreate() {
        super.onCreate()
        // Trigger a Tile refresh whenever the rendered head of the case list
        // changes — Tile shows currentCase, only the head matters.
        caseStore.casesFlow
            .map { it.firstOrNull()?.let { e -> Triple(e.caseId, e.lastActivityAt, e.oneliner) } }
            .distinctUntilChanged()
            .drop(1) // initial emission is the bootstrap snapshot, no refresh needed
            .onEach {
                Log.i(TAG, "tile refresh requested: head changed")
                TileService.getUpdater(this).requestUpdate(DoctateTileService::class.java)
            }
            .launchIn(applicationScope)

        // One-shot data-minimization sweep before the sync worker starts,
        // then kick the service so it polls /api/oneliners and uploads any
        // pending recordings. Kicking unconditionally — even with an empty
        // queue we want the case list to populate from the server.
        applicationScope.launch {
            StartupCleanup.run(
                store = caseStore,
                pendingDir = File(filesDir, "recordings/unsynced"),
            )
            Log.i(TAG, "startup: kicking sync service (queueLen=${pendingStore.scan().size})")
            syncTrigger.kick(this@DoctateApp, SyncKick.NewPending)
        }
    }

    private companion object {
        const val TAG = "DoctateApp"
    }
}
