package com.doctate.watch.presentation

import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.doctate.watch.DoctateApp
import com.doctate.watch.audio.AudioRecorder
import com.doctate.watch.audio.PendingStore
import com.doctate.watch.audio.PendingUpload
import com.doctate.watch.domain.CaseStore
import com.doctate.watch.sync.SyncKick
import com.doctate.watch.sync.SyncTrigger
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class RecordingViewModel(
    private val caseId: String,
    private val recorder: AudioRecorder,
    private val pendingStore: PendingStore,
    private val caseStore: CaseStore,
    private val syncTrigger: SyncTrigger,
    private val appContext: Context,
    private val applicationScope: CoroutineScope,
) : ViewModel() {

    private val _state = MutableStateFlow<UiState>(UiState.Idle)
    val state: StateFlow<UiState> = _state.asStateFlow()

    // Ticker coroutine that counts elapsed seconds during active recording.
    private var recordingJob: Job? = null

    // Guards against double-finalization when stop, discard and auto-stop race.
    private val finalizationStarted = AtomicBoolean(false)

    // Captured once recording starts; reused by finalizeAndUpload so audio file name
    // and sidecar metadata agree on the same `recorded_at` timestamp.
    private var pendingRecordedAt: String? = null

    fun onRecordTap() = dispatch(RecordingEvent.OnRecordTap)

    fun onPermissionResult(granted: Boolean) {
        if (!granted) {
            dispatch(RecordingEvent.PermissionDenied)
            return
        }
        dispatch(RecordingEvent.PermissionGranted)
        startRecordingFlow()
    }

    fun onStopTap() {
        if (!finalizationStarted.compareAndSet(false, true)) return
        recordingJob?.cancel()
        recordingJob = null
        dispatch(RecordingEvent.OnStopTap)
        applicationScope.launch { finalizeAndUpload() }
    }

    fun onDiscardTap() {
        if (!finalizationStarted.compareAndSet(false, true)) return
        recordingJob?.cancel()
        recordingJob = null
        dispatch(RecordingEvent.OnDiscardTap)
        Log.i(TAG, "discard: caseId=${caseId.take(8)}")
        applicationScope.launch(Dispatchers.IO) {
            try {
                recorder.cancel()
            } catch (e: Exception) {
                Log.w(TAG, "recorder.cancel() failed on discard: ${e.message}")
            }
        }
    }

    /**
     * Idempotent trigger for swipe-dismiss / back-press paths that land in
     * DisposableEffect.onDispose. Clean stop already flipped the state to
     * Uploading, in which case this is a no-op.
     */
    fun discardIfStillRecording() {
        if (_state.value is UiState.Recording) onDiscardTap()
    }

    fun onResetTap() = dispatch(RecordingEvent.OnResetTap)

    private fun startRecordingFlow() {
        finalizationStarted.set(false)
        recordingJob = viewModelScope.launch {
            val recordedAt = PendingStore.nowRfc3339()
            val target = pendingStore.audioPathFor(caseId, recordedAt)
            try {
                withContext(Dispatchers.IO) { recorder.start(target) }
                pendingRecordedAt = recordedAt
            } catch (e: Exception) {
                dispatch(RecordingEvent.UploadFailed("Recorder failed: ${e.message}", transient = false))
                return@launch
            }

            var elapsed = 0
            while (elapsed < MAX_SECONDS) {
                delay(1_000)
                elapsed++
                dispatch(RecordingEvent.RecordingTick(elapsed))
            }

            // Hit the safety cap: treat like an external onStopTap().
            if (finalizationStarted.compareAndSet(false, true)) {
                recordingJob = null
                dispatch(RecordingEvent.OnStopTap)
                Log.i(TAG, "auto-stop at $MAX_SECONDS s: caseId=${caseId.take(8)}")
                applicationScope.launch { finalizeAndUpload() }
            }
        }
    }

    /**
     * Hand the freshly-finished recording over to the sync queue and kick
     * the worker. The actual `POST /api/upload` happens in the foreground
     * service (Phase 4); from the UI's point of view "queued" looks like
     * "uploaded" — we surface the success state immediately and pop back
     * to the case detail.
     */
    private suspend fun finalizeAndUpload() {
        val finalFile = try {
            withContext(Dispatchers.IO) { recorder.stop() }
        } catch (e: Exception) {
            Log.w(TAG, "recorder.stop() failed: ${e.message}")
            dispatch(RecordingEvent.UploadFailed("Stop failed: ${e.message}", transient = false))
            return
        }
        val recordedAt = pendingRecordedAt ?: PendingStore.nowRfc3339()
        pendingRecordedAt = null
        try {
            withContext(Dispatchers.IO) {
                pendingStore.writeSidecar(PendingUpload(caseId, recordedAt, finalFile))
            }
            caseStore.markActivity(caseId)
            syncTrigger.kick(appContext, SyncKick.NewPending)
            dispatch(RecordingEvent.UploadSucceeded(caseId))
        } catch (e: Exception) {
            Log.w(TAG, "writeSidecar failed: ${e.message}")
            // Audio is on disk but no sidecar: pending_cleanup will reap the orphan.
            dispatch(RecordingEvent.UploadFailed("Queue write failed: ${e.message}", transient = false))
        }
    }

    private fun dispatch(event: RecordingEvent) {
        _state.update { current -> reduce(current, event) }
    }

    companion object {
        private const val TAG = "RecordingVM"
        private const val MAX_SECONDS = 300

        fun factoryFor(caseId: String): ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val app = this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as DoctateApp
                RecordingViewModel(
                    caseId = caseId,
                    recorder = AudioRecorder(app),
                    pendingStore = app.pendingStore,
                    caseStore = app.caseStore,
                    syncTrigger = app.syncTrigger,
                    appContext = app,
                    applicationScope = app.applicationScope,
                )
            }
        }
    }
}
