package com.syncflow.ui.addpair

import android.content.Context
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.syncflow.data.db.CloudAccountDao
import com.syncflow.data.db.SyncFileStateDao
import com.syncflow.data.db.SyncPairDao
import com.syncflow.data.db.entities.CloudAccountEntity
import com.syncflow.data.db.entities.SyncPairEntity
import com.syncflow.domain.model.*
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.WorkManager
import com.syncflow.worker.FileWatchService
import com.syncflow.worker.SyncWorker
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject

data class AddPairUiState(
    // ── Identity ─────────────────────────────────────────────────────────────
    val name: String = "",
    // ── Folders ──────────────────────────────────────────────────────────────
    val localPath: String = "",
    // Multi-folder mode: pick several local folders that each back up to their own subfolder
    // under one remote base. Empty unless multiFolder is on.
    val multiFolder: Boolean = false,
    val localPaths: List<String> = emptyList(),
    val remotePath: String = "",
    val selectedAccountId: Long = -1L,
    val accounts: List<CloudAccountEntity> = emptyList(),
    // ── Sync type ────────────────────────────────────────────────────────────
    val syncDirection: SyncDirection = SyncDirection.TWO_WAY,
    val conflictStrategy: ConflictStrategy = ConflictStrategy.KEEP_NEWEST,
    val deleteBehavior: DeleteBehavior = recommendedDeleteBehavior(SyncDirection.TWO_WAY),
    // True once the user explicitly picks a deletion behaviour, so changing direction stops
    // auto-overriding their choice.
    val deleteBehaviorTouched: Boolean = false,
    val recursive: Boolean = true,
    // ── Schedule ─────────────────────────────────────────────────────────────
    val scheduleType: ScheduleType = ScheduleType.INTERVAL,
    val intervalMinutes: Int = 30,
    val dailyTime: String = "02:00",
    val weekdays: Int = 0b1111111,
    // ── Constraints ──────────────────────────────────────────────────────────
    val wifiOnly: Boolean = true,
    val wifiSsid: String = "",
    val chargingOnly: Boolean = false,
    val minBatteryPct: Int = 0,
    // ── File filters ─────────────────────────────────────────────────────────
    val excludePatterns: String = ".DS_Store\n*.tmp\n.nomedia\nThumbs.db\n.thumbnails",
    val includeExtensions: String = "",
    val excludeExtensions: String = "",
    val skipHiddenFiles: Boolean = true,
    val minFileSizeKb: Long = 0L,
    val maxFileSizeKb: Long = 0L,
    // ── Notifications ────────────────────────────────────────────────────────
    val notifyOnComplete: Boolean = false,
    val notifyOnError: Boolean = true,
    // ── Form state ───────────────────────────────────────────────────────────
    val isEditing: Boolean = false,
    val isSaving: Boolean = false,
    val error: String? = null,
    val done: Boolean = false,
)

/**
 * Safe default deletion behaviour for a given direction. One-way backups must NOT propagate a
 * local deletion to the cloud (the whole point of a backup), so they default to KEEP; two-way
 * sync defaults to MIRROR. The user can always override — all three options stay selectable.
 */
internal fun recommendedDeleteBehavior(direction: SyncDirection): DeleteBehavior = when (direction) {
    SyncDirection.UPLOAD_ONLY, SyncDirection.DOWNLOAD_ONLY -> DeleteBehavior.KEEP
    SyncDirection.TWO_WAY -> DeleteBehavior.MIRROR
}

/**
 * Last path component of a local folder path or SAF content:// tree URI, used as the remote
 * subfolder name in multi-folder mode. Handles URL-encoded tree URIs
 * (content://…/tree/primary%3ADCIM%2FCamera → "Camera") and plain filesystem paths.
 */
internal fun folderLeafName(path: String): String {
    val decoded = try { java.net.URLDecoder.decode(path, "UTF-8") } catch (e: Exception) { path }
    val afterColon = decoded.substringAfterLast(':')   // strip the "primary:" storage-volume prefix
    val leaf = afterColon.trimEnd('/').substringAfterLast('/')
    return leaf.ifBlank { "folder" }
}

/**
 * Make a remote subfolder name unique within one multi-folder batch so two source folders that
 * share a leaf name (e.g. DCIM/Camera and Movies/Camera) don't collide into one remote dir.
 * Slashes are flattened to underscores; collisions get a numeric suffix.
 */
internal fun uniqueSubName(base: String, used: MutableSet<String>): String {
    val clean = base.replace('/', '_').replace('\\', '_').ifBlank { "folder" }
    if (used.add(clean)) return clean
    var n = 2
    while (!used.add("${clean}_$n")) n++
    return "${clean}_$n"
}

@HiltViewModel
class AddPairViewModel @Inject constructor(
    private val syncPairDao: SyncPairDao,
    private val fileStateDao: SyncFileStateDao,
    private val accountDao: CloudAccountDao,
    @ApplicationContext private val context: Context,
    savedState: SavedStateHandle,
) : ViewModel() {

    private val editPairId = savedState.get<Long>("pairId").takeIf { it != -1L }

    private val _state = MutableStateFlow(AddPairUiState(isEditing = editPairId != null))
    val state = _state.asStateFlow()

    init {
        viewModelScope.launch {
            accountDao.observeAll().collect { accounts ->
                _state.update { s ->
                    s.copy(
                        accounts = accounts,
                        selectedAccountId = if (s.selectedAccountId == -1L) accounts.firstOrNull()?.id ?: -1L else s.selectedAccountId,
                    )
                }
            }
        }
        editPairId?.let { id ->
            viewModelScope.launch {
                syncPairDao.getById(id)?.let { pair ->
                    _state.update { _ ->
                        AddPairUiState(
                            isEditing = true,
                            name = pair.name,
                            localPath = pair.localPath,
                            remotePath = pair.remotePath,
                            selectedAccountId = pair.accountId,
                            syncDirection = pair.syncDirection,
                            conflictStrategy = pair.conflictStrategy,
                            deleteBehavior = pair.deleteBehavior,
                            deleteBehaviorTouched = true, // preserve the saved choice when editing
                            recursive = pair.recursive,
                            scheduleType = pair.scheduleType,
                            intervalMinutes = pair.scheduleIntervalMinutes,
                            dailyTime = pair.scheduleDailyTime ?: "02:00",
                            weekdays = pair.scheduleWeekdays,
                            wifiOnly = pair.wifiOnly,
                            wifiSsid = pair.wifiSsid,
                            chargingOnly = pair.chargingOnly,
                            minBatteryPct = pair.minBatteryPct,
                            excludePatterns = pair.excludePatterns,
                            includeExtensions = pair.includeExtensions,
                            excludeExtensions = pair.excludeExtensions,
                            skipHiddenFiles = pair.skipHiddenFiles,
                            minFileSizeKb = pair.minFileSizeKb,
                            maxFileSizeKb = pair.maxFileSizeKb,
                            notifyOnComplete = pair.notifyOnComplete,
                            notifyOnError = pair.notifyOnError,
                        )
                    }
                }
            }
        }
    }

    fun update(transform: AddPairUiState.() -> AddPairUiState) = _state.update(transform)

    /** Changing direction re-applies the safe deletion default unless the user already chose one. */
    fun setDirection(direction: SyncDirection) = _state.update { s ->
        s.copy(
            syncDirection = direction,
            deleteBehavior = if (s.deleteBehaviorTouched) s.deleteBehavior else recommendedDeleteBehavior(direction),
        )
    }

    fun setDeleteBehavior(behavior: DeleteBehavior) = _state.update {
        it.copy(deleteBehavior = behavior, deleteBehaviorTouched = true)
    }

    fun setMultiFolder(enabled: Boolean) = _state.update { it.copy(multiFolder = enabled) }

    fun addLocalFolder(path: String) = _state.update {
        if (path.isBlank() || path in it.localPaths) it else it.copy(localPaths = it.localPaths + path)
    }

    fun removeLocalFolder(path: String) = _state.update { it.copy(localPaths = it.localPaths - path) }

    fun save() {
        val s = _state.value
        val errors = buildList {
            if (s.multiFolder) {
                if (s.localPaths.isEmpty()) add("Add at least one folder")
                if (s.remotePath.isBlank()) add("Remote base folder is required")
            } else {
                if (s.name.isBlank()) add("Name is required")
                if (s.localPath.isBlank()) add("Local folder is required")
                if (s.remotePath.isBlank()) add("Remote folder is required")
            }
            if (s.selectedAccountId == -1L) add("Select a cloud account")
            if (s.scheduleType == ScheduleType.INTERVAL && s.intervalMinutes < 15) add("Minimum interval is 15 minutes")
        }
        if (errors.isNotEmpty()) { _state.update { it.copy(error = errors.first()) }; return }

        viewModelScope.launch {
            _state.update { it.copy(isSaving = true, error = null) }
            runCatching {
                if (s.multiFolder) {
                    // One normal pair per folder, each into its OWN subfolder under the remote base.
                    // Keeping each source in a distinct subfolder is what makes many-to-one safe —
                    // flattening would let same-named files from different folders overwrite each other.
                    val base = s.remotePath.trimEnd('/')
                    val used = mutableSetOf<String>()
                    s.localPaths.map { folder ->
                        val sub = uniqueSubName(folderLeafName(folder), used)
                        val pairName = if (s.name.isBlank()) sub else "${s.name} — $sub"
                        val entity = buildEntity(s, name = pairName, localPath = folder, remotePath = "$base/$sub", id = 0L)
                        entity.copy(id = syncPairDao.insert(entity))
                    }
                } else {
                    val entity = buildEntity(s, name = s.name, localPath = s.localPath, remotePath = s.remotePath, id = editPairId ?: 0L)
                    val pairId = if (editPairId == null) {
                        syncPairDao.insert(entity)
                    } else {
                        val existing = syncPairDao.getById(editPairId)
                        syncPairDao.update(entity)
                        // If local or remote folder changed, old file-state records no longer
                        // correspond to any real path — wipe them so the next sync starts fresh
                        // instead of trying to delete/re-upload stale paths.
                        if (existing != null &&
                            (existing.localPath != entity.localPath || existing.remotePath != entity.remotePath)
                        ) {
                            fileStateDao.deleteForPair(editPairId)
                        }
                        editPairId
                    }
                    listOf(entity.copy(id = pairId))
                }
            }
                .onSuccess { saved ->
                    saved.forEach { applySchedule(it) }
                    _state.update { it.copy(done = true) }
                }
                .onFailure { e -> _state.update { it.copy(isSaving = false, error = e.message) } }
        }
    }

    private fun buildEntity(s: AddPairUiState, name: String, localPath: String, remotePath: String, id: Long) =
        SyncPairEntity(
            id = id,
            name = name, localPath = localPath, remotePath = remotePath,
            accountId = s.selectedAccountId,
            syncDirection = s.syncDirection, conflictStrategy = s.conflictStrategy,
            deleteBehavior = s.deleteBehavior, recursive = s.recursive,
            scheduleType = s.scheduleType, scheduleIntervalMinutes = s.intervalMinutes,
            scheduleDailyTime = if (s.scheduleType == ScheduleType.DAILY || s.scheduleType == ScheduleType.WEEKLY) s.dailyTime else null,
            scheduleWeekdays = s.weekdays,
            wifiOnly = s.wifiOnly, wifiSsid = s.wifiSsid,
            chargingOnly = s.chargingOnly, minBatteryPct = s.minBatteryPct,
            excludePatterns = s.excludePatterns, includeExtensions = s.includeExtensions,
            excludeExtensions = s.excludeExtensions, skipHiddenFiles = s.skipHiddenFiles,
            minFileSizeKb = s.minFileSizeKb, maxFileSizeKb = s.maxFileSizeKb,
            notifyOnComplete = s.notifyOnComplete, notifyOnError = s.notifyOnError,
            isEnabled = true, lastSyncAt = null, lastSyncResult = SyncStatus.IDLE, pendingConflicts = 0,
        )

    /**
     * Register the pair's background work the moment it's saved. Previously this only happened on
     * the enable-toggle or a reboot, so a freshly-created scheduled pair never actually ran in the
     * background. Mirrors HomeViewModel.toggleEnabled / BootReceiver.
     */
    private fun applySchedule(pair: SyncPairEntity) {
        val wm = WorkManager.getInstance(context)
        when (pair.scheduleType) {
            ScheduleType.ON_CHANGE -> {
                wm.cancelUniqueWork("periodic_${pair.id}")
                FileWatchService.start(context)
            }
            ScheduleType.MANUAL -> wm.cancelUniqueWork("periodic_${pair.id}")
            else -> {
                val req = SyncWorker.buildPeriodicRequest(
                    pair.id,
                    pair.scheduleIntervalMinutes.toLong().coerceAtLeast(15),
                    pair.wifiOnly,
                    pair.chargingOnly,
                )
                wm.enqueueUniquePeriodicWork("periodic_${pair.id}", ExistingPeriodicWorkPolicy.UPDATE, req)
            }
        }
    }
}
