package com.syncflow.ui.files

import android.content.Context
import android.media.MediaScannerConnection
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.syncflow.data.db.SyncFileStateDao
import com.syncflow.data.db.SyncPairDao
import com.syncflow.data.db.entities.SyncFileStateEntity
import com.syncflow.data.db.entities.SyncPairEntity
import com.syncflow.data.providers.ProviderFactory
import com.syncflow.data.repository.AccountRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.update
import timber.log.Timber
import java.io.File
import javax.inject.Inject

sealed class FileAction {
    data class Open(val file: File) : FileAction()
    data class Share(val file: File) : FileAction()
    data class ShareMultiple(val files: List<File>) : FileAction()
    data class Error(val message: String) : FileAction()
}

@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class FilesViewModel @Inject constructor(
    private val syncPairDao: SyncPairDao,
    private val fileStateDao: SyncFileStateDao,
    private val accountRepository: AccountRepository,
    private val providerFactory: ProviderFactory,
    @ApplicationContext private val context: Context,
) : ViewModel() {

    val pairs: StateFlow<List<SyncPairEntity>> = syncPairDao.observeAll()
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

    val accounts = accountRepository.observeAll()
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

    private val _selectedPairId = MutableStateFlow<Long?>(null)

    val selectedPair: StateFlow<SyncPairEntity?> = combine(_selectedPairId, pairs) { id, list ->
        list.firstOrNull { it.id == id } ?: list.firstOrNull()
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)

    val files: StateFlow<List<SyncFileStateEntity>> = _selectedPairId
        .flatMapLatest { id ->
            if (id == null) pairs.map { it.firstOrNull()?.id }.filterNotNull()
                .flatMapLatest { fileStateDao.observeForPair(it) }
            else fileStateDao.observeForPair(id)
        }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())

    private val _fileAction = MutableSharedFlow<FileAction>()
    val fileAction: SharedFlow<FileAction> = _fileAction

    private val _isDownloading = MutableStateFlow(false)
    val isDownloading: StateFlow<Boolean> = _isDownloading

    private val _selectedKeys = MutableStateFlow<Set<String>>(emptySet())
    val selectedKeys: StateFlow<Set<String>> = _selectedKeys
    val isSelectionMode: StateFlow<Boolean> = _selectedKeys.map { it.isNotEmpty() }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
    val selectedCount: StateFlow<Int> = _selectedKeys.map { it.size }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), 0)

    fun selectPair(id: Long) { _selectedPairId.value = id }

    fun openFile(file: SyncFileStateEntity) {
        val resolved = resolveFile(file, emitErrorIfMissing = false)
        if (resolved != null) {
            // Ensure MediaStore knows about this file so gallery apps can open it
            MediaScannerConnection.scanFile(context, arrayOf(resolved.absolutePath), null, null)
            viewModelScope.launch { _fileAction.emit(FileAction.Open(resolved)) }
        } else {
            downloadAndOpen(file)
        }
    }

    fun shareFile(file: SyncFileStateEntity) {
        val resolved = resolveFile(file, emitErrorIfMissing = false)
        if (resolved != null) {
            viewModelScope.launch { _fileAction.emit(FileAction.Share(resolved)) }
        } else {
            downloadAndShare(file)
        }
    }

    fun deleteFile(file: SyncFileStateEntity) {
        viewModelScope.launch {
            try {
                val resolved = resolveFile(file, emitErrorIfMissing = false)
                resolved?.delete()
                fileStateDao.delete(file.syncPairId, file.relativePath)
            } catch (e: Exception) {
                Timber.e(e, "Delete failed: ${file.relativePath}")
                _fileAction.emit(FileAction.Error("Delete failed: ${e.message}"))
            }
        }
    }

    fun renameFile(file: SyncFileStateEntity, newName: String) {
        viewModelScope.launch {
            try {
                val resolved = resolveFile(file) ?: return@launch
                val parent = resolved.parentFile ?: return@launch
                val dest = File(parent, newName)
                if (!resolved.renameTo(dest)) {
                    _fileAction.emit(FileAction.Error("Rename failed"))
                    return@launch
                }
                fileStateDao.delete(file.syncPairId, file.relativePath)
            } catch (e: Exception) {
                Timber.e(e, "Rename failed: ${file.relativePath}")
                _fileAction.emit(FileAction.Error("Rename failed: ${e.message}"))
            }
        }
    }

    fun isSelected(file: SyncFileStateEntity): Boolean = fileKey(file) in _selectedKeys.value

    fun toggleSelection(file: SyncFileStateEntity) {
        val key = fileKey(file)
        _selectedKeys.update { if (key in it) it - key else it + key }
    }

    fun clearSelection() { _selectedKeys.value = emptySet() }

    fun deleteSelected() {
        viewModelScope.launch {
            val toDelete = files.value.filter { isSelected(it) }
            toDelete.forEach { file ->
                try {
                    resolveFile(file, emitErrorIfMissing = false)?.delete()
                    fileStateDao.delete(file.syncPairId, file.relativePath)
                } catch (e: Exception) {
                    Timber.e(e, "Bulk delete failed: ${file.relativePath}")
                }
            }
            clearSelection()
        }
    }

    fun shareSelected() {
        viewModelScope.launch {
            val toShare = files.value.filter { isSelected(it) }
            val resolved = toShare.mapNotNull { resolveFile(it, emitErrorIfMissing = false) }
            if (resolved.isEmpty()) {
                _fileAction.emit(FileAction.Error("No local files available to share"))
                return@launch
            }
            _fileAction.emit(FileAction.ShareMultiple(resolved))
        }
    }

    fun fileKey(file: SyncFileStateEntity) = "${file.syncPairId}:${file.relativePath}"

    fun openCloudFile(accountId: Long, remotePath: String) {
        viewModelScope.launch {
            val account = accountRepository.getAccount(accountId) ?: run {
                _fileAction.emit(FileAction.Error("Account not found"))
                return@launch
            }
            val provider = providerFactory.create(account)
            val fileName = remotePath.substringAfterLast('/')
            val cacheFile = File(context.cacheDir, "syncflow_open/$fileName")
            cacheFile.parentFile?.mkdirs()
            _isDownloading.value = true
            try {
                cacheFile.outputStream().use { out ->
                    provider.downloadFile(remotePath, out) { }.getOrThrow()
                }
                _fileAction.emit(FileAction.Open(cacheFile))
            } catch (e: Exception) {
                Timber.e(e, "Cloud open failed: $remotePath")
                _fileAction.emit(FileAction.Error("Cannot open: ${e.message}"))
            } finally {
                _isDownloading.value = false
            }
        }
    }

    // ── Download-then-open/share ──────────────────────────────────────────────

    private fun downloadAndOpen(file: SyncFileStateEntity) {
        viewModelScope.launch {
            downloadToCache(file)?.let { cached ->
                _fileAction.emit(FileAction.Open(cached))
            }
        }
    }

    private fun downloadAndShare(file: SyncFileStateEntity) {
        viewModelScope.launch {
            downloadToCache(file)?.let { cached ->
                _fileAction.emit(FileAction.Share(cached))
            }
        }
    }

    private suspend fun downloadToCache(file: SyncFileStateEntity): File? {
        val pair = selectedPair.value ?: run {
            _fileAction.emit(FileAction.Error("No sync pair selected"))
            return null
        }
        val account = accountRepository.getAccount(pair.accountId) ?: run {
            _fileAction.emit(FileAction.Error("Cloud account not found"))
            return null
        }
        val provider = providerFactory.create(account)
        val fileName = file.relativePath.substringAfterLast('/')
        val cacheFile = File(context.cacheDir, "syncflow_open/$fileName")
        cacheFile.parentFile?.mkdirs()

        _isDownloading.value = true
        return try {
            cacheFile.outputStream().use { out ->
                provider.downloadFile("${pair.remotePath}/${file.relativePath}", out) { }.getOrThrow()
            }
            MediaScannerConnection.scanFile(context, arrayOf(cacheFile.absolutePath), null, null)
            cacheFile
        } catch (e: Exception) {
            Timber.e(e, "Download for preview failed: ${file.relativePath}")
            cacheFile.delete()
            _fileAction.emit(FileAction.Error("Download failed: ${e.message}"))
            null
        } finally {
            _isDownloading.value = false
        }
    }

    // ── Path resolution ───────────────────────────────────────────────────────

    private fun resolveFile(file: SyncFileStateEntity, emitErrorIfMissing: Boolean = true): File? {
        // Guard against path traversal from untrusted server responses
        if (file.relativePath.contains("..")) {
            viewModelScope.launch { _fileAction.emit(FileAction.Error("Invalid file path")) }
            return null
        }
        val pair = selectedPair.value ?: return null
        val root = safTreeUriToRealPath(pair.localPath) ?: pair.localPath
        // localPath is a content:// URI we couldn't resolve — File-based access won't work
        if (root.startsWith("content://")) return null
        val f = File(root, file.relativePath)
        if (!f.exists()) {
            if (emitErrorIfMissing) {
                viewModelScope.launch { _fileAction.emit(FileAction.Error("File not found on device")) }
            }
            return null
        }
        return f
    }

    private fun safTreeUriToRealPath(uriString: String): String? {
        if (!uriString.startsWith("content://")) return uriString
        return try {
            val treeUri = android.net.Uri.parse(uriString)
            val docId = android.provider.DocumentsContract.getTreeDocumentId(treeUri)
            if (docId.startsWith("primary:")) "/storage/emulated/0/${docId.removePrefix("primary:")}"
            else null
        } catch (e: Exception) { null }
    }
}
