package com.atridad.ascently.data.repository

import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.atridad.ascently.data.database.AscentlyDatabase
import com.atridad.ascently.data.format.BackupAttempt
import com.atridad.ascently.data.format.BackupClimbSession
import com.atridad.ascently.data.format.BackupGym
import com.atridad.ascently.data.format.BackupProblem
import com.atridad.ascently.data.format.ClimbDataBackup
import com.atridad.ascently.data.format.DeletedItem
import com.atridad.ascently.data.model.*
import com.atridad.ascently.data.state.DataStateManager
import com.atridad.ascently.utils.AppLogger
import com.atridad.ascently.utils.DateFormatUtils
import com.atridad.ascently.utils.ZipExportImportUtils
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.serialization.json.Json
import java.io.File
import java.time.Instant

class ClimbRepository(database: AscentlyDatabase, private val context: Context) {
    private val gymDao = database.gymDao()
    private val problemDao = database.problemDao()
    private val sessionDao = database.climbSessionDao()
    private val attemptDao = database.attemptDao()
    private val dataStateManager = DataStateManager(context)
    private val deletionPreferences: SharedPreferences =
        context.getSharedPreferences("deleted_items", Context.MODE_PRIVATE)

    private var autoSyncCallback: (() -> Unit)? = null

    private val json = Json {
        prettyPrint = true
        ignoreUnknownKeys = true
    }

    // Gym operations
    fun getAllGyms(): Flow<List<Gym>> = gymDao.getAllGyms()
    suspend fun getAllGymsSync(): List<Gym> = gymDao.getAllGyms().first()
    suspend fun getGymById(id: String): Gym? = gymDao.getGymById(id)
    suspend fun insertGym(gym: Gym) {
        gymDao.insertGym(gym)
        dataStateManager.updateDataState()
        triggerAutoSync()
    }

    suspend fun updateGym(gym: Gym) {
        gymDao.updateGym(gym)
        dataStateManager.updateDataState()
        triggerAutoSync()
    }

    suspend fun deleteGym(gym: Gym) {
        gymDao.deleteGym(gym)
        trackDeletion(gym.id, "gym")
        dataStateManager.updateDataState()
        triggerAutoSync()
    }

    // Problem operations
    fun getAllProblems(): Flow<List<Problem>> = problemDao.getAllProblems()
    suspend fun getAllProblemsSync(): List<Problem> = problemDao.getAllProblems().first()
    suspend fun getProblemById(id: String): Problem? = problemDao.getProblemById(id)
    fun getProblemsByGym(gymId: String): Flow<List<Problem>> = problemDao.getProblemsByGym(gymId)
    suspend fun insertProblem(problem: Problem) {
        problemDao.insertProblem(problem)
        dataStateManager.updateDataState()
    }

    suspend fun updateProblem(problem: Problem) {
        problemDao.updateProblem(problem)
        dataStateManager.updateDataState()
    }

    suspend fun deleteProblem(problem: Problem) {
        problemDao.deleteProblem(problem)
        trackDeletion(problem.id, "problem")
        dataStateManager.updateDataState()
    }

    // Session operations
    fun getAllSessions(): Flow<List<ClimbSession>> = sessionDao.getAllSessions()
    suspend fun getAllSessionsSync(): List<ClimbSession> = sessionDao.getAllSessions().first()
    suspend fun getSessionById(id: String): ClimbSession? = sessionDao.getSessionById(id)
    fun getSessionsByGym(gymId: String): Flow<List<ClimbSession>> =
        sessionDao.getSessionsByGym(gymId)

    suspend fun getActiveSession(): ClimbSession? = sessionDao.getActiveSession()
    fun getActiveSessionFlow(): Flow<ClimbSession?> = sessionDao.getActiveSessionFlow()
    suspend fun insertSession(session: ClimbSession) {
        sessionDao.insertSession(session)
        dataStateManager.updateDataState()
        // Only trigger sync for completed sessions
        if (session.status != SessionStatus.ACTIVE) {
            triggerAutoSync()
        }
    }

    suspend fun updateSession(session: ClimbSession) {
        sessionDao.updateSession(session)
        dataStateManager.updateDataState()
        // Only trigger sync for completed sessions
        if (session.status != SessionStatus.ACTIVE) {
            triggerAutoSync()
        }
    }

    suspend fun deleteSession(session: ClimbSession) {
        sessionDao.deleteSession(session)
        trackDeletion(session.id, "session")
        dataStateManager.updateDataState()
        triggerAutoSync()
    }

    suspend fun getLastUsedGym(): Gym? {
        val recentSessions = sessionDao.getRecentSessions(1).first()
        return if (recentSessions.isNotEmpty()) {
            getGymById(recentSessions.first().gymId)
        } else {
            null
        }
    }

    // Attempt operations
    fun getAllAttempts(): Flow<List<Attempt>> = attemptDao.getAllAttempts()
    suspend fun getAllAttemptsSync(): List<Attempt> = attemptDao.getAllAttempts().first()
    suspend fun getAttemptById(id: String): Attempt? = attemptDao.getAttemptById(id)
    fun getAttemptsBySession(sessionId: String): Flow<List<Attempt>> =
        attemptDao.getAttemptsBySession(sessionId)

    fun getAttemptsByProblem(problemId: String): Flow<List<Attempt>> =
        attemptDao.getAttemptsByProblem(problemId)

    suspend fun insertAttempt(attempt: Attempt) {
        attemptDao.insertAttempt(attempt)
        dataStateManager.updateDataState()
    }

    suspend fun updateAttempt(attempt: Attempt) {
        attemptDao.updateAttempt(attempt)
        dataStateManager.updateDataState()
    }

    suspend fun deleteAttempt(attempt: Attempt) {
        attemptDao.deleteAttempt(attempt)
        trackDeletion(attempt.id, "attempt")
        dataStateManager.updateDataState()
    }

    suspend fun exportAllDataToZipUri(context: Context, uri: android.net.Uri) {
        try {
            val allGyms = gymDao.getAllGyms().first()
            val allProblems = problemDao.getAllProblems().first()
            val allSessions = sessionDao.getAllSessions().first()
            val allAttempts = attemptDao.getAllAttempts().first()

            validateDataIntegrity(allGyms, allProblems, allSessions, allAttempts)

            val backupData =
                ClimbDataBackup(
                    exportedAt = DateFormatUtils.nowISO8601(),
                    version = "2.0",
                    formatVersion = "2.0",
                    gyms = allGyms.map { BackupGym.fromGym(it) },
                    problems = allProblems.map { BackupProblem.fromProblem(it) },
                    sessions = allSessions.map { BackupClimbSession.fromClimbSession(it) },
                    attempts = allAttempts.map { BackupAttempt.fromAttempt(it) },
                )

            val referencedImagePaths = allProblems.flatMap { it.imagePaths }.toSet()
            val validImagePaths =
                referencedImagePaths
                    .filter { imagePath ->
                        try {
                            val imageFile =
                                com.atridad.ascently.utils.ImageUtils.getImageFile(
                                    context,
                                    imagePath,
                                )
                            imageFile.exists() && imageFile.length() > 0
                        } catch (_: Exception) {
                            false
                        }
                    }
                    .toSet()

            ZipExportImportUtils.createExportZipToUri(
                context = context,
                uri = uri,
                exportData = backupData,
                referencedImagePaths = validImagePaths,
            )
        } catch (e: Exception) {
            throw Exception("Export failed: ${e.message}")
        }
    }

    suspend fun importDataFromZip(file: File) {
        try {
            if (!file.exists() || file.length() == 0L) {
                throw Exception("Invalid ZIP file: file is empty or doesn't exist")
            }

            val importResult = ZipExportImportUtils.extractImportZip(context, file)

            if (importResult.jsonContent.isBlank()) {
                throw Exception("Invalid ZIP file: no data.json found or empty content")
            }

            val importData =
                try {
                    json.decodeFromString<ClimbDataBackup>(importResult.jsonContent)
                } catch (e: Exception) {
                    throw Exception("Invalid data format: ${e.message}")
                }

            validateImportData(importData)

            attemptDao.deleteAllAttempts()
            sessionDao.deleteAllSessions()
            problemDao.deleteAllProblems()
            gymDao.deleteAllGyms()

            importData.gyms.forEach { backupGym ->
                try {
                    gymDao.insertGym(backupGym.toGym())
                } catch (e: Exception) {
                    throw Exception("Failed to import gym '${backupGym.name}': ${e.message}")
                }
            }

            val updatedBackupProblems =
                ZipExportImportUtils.updateProblemImagePaths(
                    importData.problems,
                    importResult.importedImagePaths,
                )

            updatedBackupProblems.forEach { backupProblem ->
                try {
                    problemDao.insertProblem(backupProblem.toProblem())
                } catch (e: Exception) {
                    throw Exception(
                        "Failed to import problem '${backupProblem.name}': ${e.message}",
                    )
                }
            }

            importData.sessions.forEach { backupSession ->
                try {
                    sessionDao.insertSession(backupSession.toClimbSession())
                } catch (e: Exception) {
                    throw Exception("Failed to import session '${backupSession.id}': ${e.message}")
                }
            }

            importData.attempts.forEach { backupAttempt ->
                try {
                    attemptDao.insertAttempt(backupAttempt.toAttempt())
                } catch (e: Exception) {
                    throw Exception("Failed to import attempt '${backupAttempt.id}': ${e.message}")
                }
            }

            dataStateManager.updateDataState()
        } catch (e: Exception) {
            throw Exception("Import failed: ${e.message}")
        }
    }

    fun setAutoSyncCallback(callback: (() -> Unit)?) {
        autoSyncCallback = callback
    }

    private fun triggerAutoSync() {
        autoSyncCallback?.invoke()
    }

    fun trackDeletion(itemId: String, itemType: String) {
        cleanupOldDeletions()
        val newDeletion =
            DeletedItem(id = itemId, type = itemType, deletedAt = DateFormatUtils.nowISO8601())

        val json = json.encodeToString(newDeletion)
        deletionPreferences.edit { putString("deleted_$itemId", json) }
    }

    fun getDeletedItems(): List<DeletedItem> {
        val deletions = mutableListOf<DeletedItem>()
        val allPrefs = deletionPreferences.all

        for ((key, value) in allPrefs) {
            if (key.startsWith("deleted_") && value is String) {
                try {
                    val deletion = json.decodeFromString<DeletedItem>(value)
                    deletions.add(deletion)
                } catch (_: Exception) {
                    // Invalid deletion record, ignore
                }
            }
        }

        return deletions
    }

    fun clearDeletedItems() {
        deletionPreferences.edit { clear() }
    }

    private fun cleanupOldDeletions() {
        val allPrefs = deletionPreferences.all
        val cutoff = Instant.now().minusSeconds(90L * 24 * 60 * 60)

        deletionPreferences.edit {
            for ((key, value) in allPrefs) {
                if (key.startsWith("deleted_") && value is String) {
                    try {
                        val deletion = json.decodeFromString<DeletedItem>(value)
                        val deletedAt = Instant.parse(deletion.deletedAt)
                        if (deletedAt.isBefore(cutoff)) {
                            remove(key)
                        }
                    } catch (_: Exception) {
                        // Ignore
                    }
                }
            }
        }
    }

    private fun validateDataIntegrity(
        gyms: List<Gym>,
        problems: List<Problem>,
        sessions: List<ClimbSession>,
        attempts: List<Attempt>,
    ) {
        val gymIds = gyms.map { it.id }.toSet()
        val invalidProblems = problems.filter { it.gymId !in gymIds }
        if (invalidProblems.isNotEmpty()) {
            throw Exception(
                "Data integrity error: ${invalidProblems.size} problems reference non-existent gyms",
            )
        }

        val invalidSessions = sessions.filter { it.gymId !in gymIds }
        if (invalidSessions.isNotEmpty()) {
            throw Exception(
                "Data integrity error: ${invalidSessions.size} sessions reference non-existent gyms",
            )
        }

        val problemIds = problems.map { it.id }.toSet()
        val sessionIds = sessions.map { it.id }.toSet()

        val invalidAttempts =
            attempts.filter { it.problemId !in problemIds || it.sessionId !in sessionIds }
        if (invalidAttempts.isNotEmpty()) {
            throw Exception(
                "Data integrity error: ${invalidAttempts.size} attempts reference non-existent problems or sessions",
            )
        }
    }

    private fun validateImportData(importData: ClimbDataBackup) {
        if (importData.gyms.isEmpty()) {
            throw Exception("Import data is invalid: no gyms found")
        }

        if (importData.version.isBlank()) {
            throw Exception("Import data is invalid: no version information")
        }

        if (importData.gyms.size > 1000 ||
            importData.problems.size > 10000 ||
            importData.sessions.size > 10000 ||
            importData.attempts.size > 100000
        ) {
            throw Exception("Import data is too large: possible corruption or malicious file")
        }
    }

    suspend fun resetAllData() {
        try {
            val originalCallback = autoSyncCallback
            autoSyncCallback = null

            attemptDao.deleteAllAttempts()
            sessionDao.deleteAllSessions()
            problemDao.deleteAllProblems()
            gymDao.deleteAllGyms()

            clearAllImages()

            autoSyncCallback = originalCallback
        } catch (e: Exception) {
            throw Exception("Reset failed: ${e.message}")
        }
    }

    suspend fun insertGymWithoutSync(gym: Gym) {
        gymDao.insertGym(gym)
        dataStateManager.updateDataState()
    }

    suspend fun insertProblemWithoutSync(problem: Problem) {
        problemDao.insertProblem(problem)
        dataStateManager.updateDataState()
    }

    suspend fun insertSessionWithoutSync(session: ClimbSession) {
        sessionDao.insertSession(session)
        dataStateManager.updateDataState()
    }

    suspend fun insertAttemptWithoutSync(attempt: Attempt) {
        attemptDao.insertAttempt(attempt)
        dataStateManager.updateDataState()
    }

    private fun clearAllImages() {
        try {
            val imagesDir = File(context.filesDir, "images")
            if (imagesDir.exists() && imagesDir.isDirectory) {
                val deletedCount = imagesDir.listFiles()?.size ?: 0
                imagesDir.deleteRecursively()
                AppLogger.i("ClimbRepository") { "Cleared $deletedCount image files" }
            }
        } catch (e: Exception) {
            AppLogger.w("ClimbRepository", e) { "Failed to clear some images: ${e.message}" }
        }
    }
}
