package com.atridad.openclimb.data.repository

import android.content.Context
import android.os.Environment
import com.atridad.openclimb.data.database.OpenClimbDatabase
import com.atridad.openclimb.data.model.*
import com.atridad.openclimb.utils.ImageUtils
import com.atridad.openclimb.utils.ZipExportImportUtils
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import java.time.LocalDateTime

class ClimbRepository(
    private val database: OpenClimbDatabase,
    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 json = Json { 
        prettyPrint = true 
        ignoreUnknownKeys = true
    }
    
    // Gym operations
    fun getAllGyms(): Flow<List<Gym>> = gymDao.getAllGyms()
    suspend fun getGymById(id: String): Gym? = gymDao.getGymById(id)
    suspend fun insertGym(gym: Gym) = gymDao.insertGym(gym)
    suspend fun updateGym(gym: Gym) = gymDao.updateGym(gym)
    suspend fun deleteGym(gym: Gym) = gymDao.deleteGym(gym)
    fun searchGyms(query: String): Flow<List<Gym>> = gymDao.searchGyms(query)
    
    // Problem operations
    fun getAllProblems(): Flow<List<Problem>> = problemDao.getAllProblems()
    suspend fun getProblemById(id: String): Problem? = problemDao.getProblemById(id)
    fun getProblemsByGym(gymId: String): Flow<List<Problem>> = problemDao.getProblemsByGym(gymId)
    fun getActiveProblems(): Flow<List<Problem>> = problemDao.getActiveProblems()
    suspend fun insertProblem(problem: Problem) = problemDao.insertProblem(problem)
    suspend fun updateProblem(problem: Problem) = problemDao.updateProblem(problem)
    suspend fun deleteProblem(problem: Problem) = problemDao.deleteProblem(problem)
    fun searchProblems(query: String): Flow<List<Problem>> = problemDao.searchProblems(query)
    
    // Session operations
    fun getAllSessions(): Flow<List<ClimbSession>> = sessionDao.getAllSessions()
    suspend fun getSessionById(id: String): ClimbSession? = sessionDao.getSessionById(id)
    fun getSessionsByGym(gymId: String): Flow<List<ClimbSession>> = sessionDao.getSessionsByGym(gymId)
    fun getRecentSessions(limit: Int = 10): Flow<List<ClimbSession>> = sessionDao.getRecentSessions(limit)
    suspend fun getActiveSession(): ClimbSession? = sessionDao.getActiveSession()
    fun getActiveSessionFlow(): Flow<ClimbSession?> = sessionDao.getActiveSessionFlow()
    fun getSessionsByStatus(status: SessionStatus): Flow<List<ClimbSession>> = sessionDao.getSessionsByStatus(status)
    suspend fun insertSession(session: ClimbSession) = sessionDao.insertSession(session)
    suspend fun updateSession(session: ClimbSession) = sessionDao.updateSession(session)
    suspend fun deleteSession(session: ClimbSession) = sessionDao.deleteSession(session)
    
    // Attempt operations
    fun getAllAttempts(): Flow<List<Attempt>> = attemptDao.getAllAttempts()
    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)
    suspend fun updateAttempt(attempt: Attempt) = attemptDao.updateAttempt(attempt)
    suspend fun deleteAttempt(attempt: Attempt) = attemptDao.deleteAttempt(attempt)
    

    
    // JSON Export functionality
    suspend fun exportAllDataToJson(directory: File? = null): File {
        val exportDir = directory ?: File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "OpenClimb")
        if (!exportDir.exists()) {
            exportDir.mkdirs()
        }
        
        val timestamp = LocalDateTime.now().toString().replace(":", "-").replace(".", "-")
        val exportFile = File(exportDir, "openclimb_export_$timestamp.json")
        
        val allGyms = gymDao.getAllGyms().first()
        val allProblems = problemDao.getAllProblems().first()
        val allSessions = sessionDao.getAllSessions().first()
        val allAttempts = attemptDao.getAllAttempts().first()
        
        val exportData = ClimbDataExport(
            exportedAt = LocalDateTime.now().toString(),
            gyms = allGyms,
            problems = allProblems,
            sessions = allSessions,
            attempts = allAttempts
        )
        
        val jsonString = json.encodeToString(exportData)
        exportFile.writeText(jsonString)
        
        return exportFile
    }
    
    suspend fun exportAllDataToUri(context: Context, uri: android.net.Uri) {
        val gyms = gymDao.getAllGyms().first()
        val problems = problemDao.getAllProblems().first()
        val sessions = sessionDao.getAllSessions().first()
        val attempts = attemptDao.getAllAttempts().first()
        
        val exportData = ClimbDataExport(
            exportedAt = LocalDateTime.now().toString(),
            gyms = gyms,
            problems = problems,
            sessions = sessions,
            attempts = attempts
        )
        
        val jsonString = json.encodeToString(exportData)
        
        context.contentResolver.openOutputStream(uri)?.use { outputStream ->
            outputStream.write(jsonString.toByteArray())
        } ?: throw Exception("Could not open output stream")
    }
    
    suspend fun importDataFromJson(file: File) {
        try {
            val jsonContent = file.readText()
            val importData = json.decodeFromString<ClimbDataExport>(jsonContent)
            
            // Import gyms (replace if exists due to primary key constraint)
            importData.gyms.forEach { gym ->
                try {
                    gymDao.insertGym(gym)
                } catch (e: Exception) {
                    // If insertion fails due to primary key conflict, update instead
                    gymDao.updateGym(gym)
                }
            }
            
            // Import problems
            importData.problems.forEach { problem ->
                try {
                    problemDao.insertProblem(problem)
                } catch (e: Exception) {
                    problemDao.updateProblem(problem)
                }
            }
            
            // Import sessions
            importData.sessions.forEach { session ->
                try {
                    sessionDao.insertSession(session)
                } catch (e: Exception) {
                    sessionDao.updateSession(session)
                }
            }
            
            // Import attempts
            importData.attempts.forEach { attempt ->
                try {
                    attemptDao.insertAttempt(attempt)
                } catch (e: Exception) {
                    attemptDao.updateAttempt(attempt)
                }
            }
            
        } catch (e: Exception) {
            throw Exception("Failed to import data: ${e.message}")
        }
    }
    
    // ZIP Export functionality with images
    suspend fun exportAllDataToZip(directory: File? = null): File {
        val allGyms = gymDao.getAllGyms().first()
        val allProblems = problemDao.getAllProblems().first()
        val allSessions = sessionDao.getAllSessions().first()
        val allAttempts = attemptDao.getAllAttempts().first()
        
        val exportData = ClimbDataExport(
            exportedAt = LocalDateTime.now().toString(),
            gyms = allGyms,
            problems = allProblems,
            sessions = allSessions,
            attempts = allAttempts
        )
        
        // Collect all referenced image paths
        val referencedImagePaths = allProblems.flatMap { it.imagePaths }.toSet()
        
        return ZipExportImportUtils.createExportZip(
            context = context,
            exportData = exportData,
            referencedImagePaths = referencedImagePaths,
            directory = directory
        )
    }
    
    suspend fun exportAllDataToZipUri(context: Context, uri: android.net.Uri) {
        val gyms = gymDao.getAllGyms().first()
        val problems = problemDao.getAllProblems().first()
        val sessions = sessionDao.getAllSessions().first()
        val attempts = attemptDao.getAllAttempts().first()
        
        val exportData = ClimbDataExport(
            exportedAt = LocalDateTime.now().toString(),
            gyms = gyms,
            problems = problems,
            sessions = sessions,
            attempts = attempts
        )
        
        // Collect all referenced image paths
        val referencedImagePaths = problems.flatMap { it.imagePaths }.toSet()
        
        ZipExportImportUtils.createExportZipToUri(
            context = context,
            uri = uri,
            exportData = exportData,
            referencedImagePaths = referencedImagePaths
        )
    }
    
    suspend fun importDataFromZip(file: File) {
        try {
            val importResult = ZipExportImportUtils.extractImportZip(context, file)
            val importData = json.decodeFromString<ClimbDataExport>(importResult.jsonContent)
            
            // Update problem image paths with the new imported paths
            val updatedProblems = ZipExportImportUtils.updateProblemImagePaths(
                importData.problems,
                importResult.importedImagePaths
            )
            
            // Import gyms (replace if exists due to primary key constraint)
            importData.gyms.forEach { gym ->
                try {
                    gymDao.insertGym(gym)
                } catch (e: Exception) {
                    // If insertion fails due to primary key conflict, update instead
                    gymDao.updateGym(gym)
                }
            }
            
            // Import problems with updated image paths
            updatedProblems.forEach { problem ->
                try {
                    problemDao.insertProblem(problem)
                } catch (e: Exception) {
                    problemDao.updateProblem(problem)
                }
            }
            
            // Import sessions
            importData.sessions.forEach { session ->
                try {
                    sessionDao.insertSession(session)
                } catch (e: Exception) {
                    sessionDao.updateSession(session)
                }
            }
            
            // Import attempts
            importData.attempts.forEach { attempt ->
                try {
                    attemptDao.insertAttempt(attempt)
                } catch (e: Exception) {
                    attemptDao.updateAttempt(attempt)
                }
            }
            
        } catch (e: Exception) {
            throw Exception("Failed to import data: ${e.message}")
        }
    }
}

@kotlinx.serialization.Serializable
data class ClimbDataExport(
    val exportedAt: String,
    val gyms: List<Gym>,
    val problems: List<Problem>,
    val sessions: List<ClimbSession>,
    val attempts: List<Attempt>
)