package com.hady.codeeditor.viewmodels

import android.content.ContentResolver
import android.content.Context
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.toMutableStateList
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import com.hady.CodeEditor.Screens.EditorTabState
import com.hady.codeeditor.launchIO
import com.hady.codeeditor.repos.FileCreationState
import com.hady.codeeditor.repos.FileSaveState
import com.hady.codeeditor.repos.FilesRepo
import com.hady.core.Ui.CodeEditor.ui.theme.ErrorRed
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okio.Path
import okio.Path.Companion.toPath
import java.io.File
import java.io.IOException
import javax.inject.Inject
import kotlin.io.path.absolutePathString


class MainViewmodel
@Inject constructor(
    val filesRepo: FilesRepo,
    @ApplicationContext val context: Context,
) : ScreenModel {
    val tabs = MutableStateFlow(mutableStateListOf<EditorTabState>())
    var tabsNumberChange = tabs.map { it.size }.stateIn(
        screenModelScope, SharingStarted.WhileSubscribed(), 0
    )

    val showErrorSnackbar = MutableStateFlow<com.hady.core.Ui.uiframework.Dialogs.SnackBarState>(
        com.hady.core.Ui.uiframework.Dialogs.SnackBarState(
            shown = false, message = "", backgroundColor = ErrorRed
        )
    )
    val showSaveDialog =
        MutableStateFlow<com.hady.core.Ui.uiframework.Dialogs.SaveDialogState>(com.hady.core.Ui.uiframework.Dialogs.SaveDialogState())

    private fun deselectAllTabs() {
        tabs.value.forEach { it.selected = false }
    }

    val showDirectorySheet = MutableStateFlow(false)

    var onTabDeselcted: ((deselectedTab: EditorTabState, selectedTab: EditorTabState) -> Unit)? =
        null
    var onTabClosed: ((tab: EditorTabState) -> Unit)? = null

    fun createTemporaryFile(
        filename: String? = null,
    ) {
        screenModelScope.launch {
            withContext(Dispatchers.IO) {
                val newFileState = filesRepo.createFile(
                    filename ?: ""
                )
                when (newFileState) {
                    is FileCreationState.Error -> {
                        showErrorMessage(newFileState.message)
                    }

                    is FileCreationState.Success -> {
                        val newFileName = newFileState.fileName
                        createTab(newFileName, "", newFileState.filePath, true)
                    }
                }
            }
        }
    }

    fun createTab(
        title: String,
        originalContent: String,
        filePath: String,
        isTemp: Boolean,
    ) {
        //if the file path already exists just select it
        tabs.value.forEach {
            if (it.filePath == filePath) {
                selectTab(it)
                return
            }
        }
        tabs.update {
            deselectAllTabs()
            val x = it.apply { add(EditorTabState(title, originalContent, filePath, isTemp)) }
            x.toMutableStateList()
        }



    }


    fun openFileFromDrawer(
        path: Path,
    ) {
        Log.wtf("open", "Opening file from drawer with path : $path")

        tabs.value.forEach {
            if (it.filePath == path.toNioPath().absolutePathString()) {
                selectTab(it)
                return
            }
        }
        openFile(path)
        Log.wtf("open", "Opening file from drawer with path : $path")
        //Todo update the case when the file is already opened in the editor  ,
        // the tab should re-read the contents to update any external changes
    }

    fun openFileFromSamples(
        path: Path,
    ) {
        Log.wtf("open", "Opening file from drawer with path : $path")

        tabs.value.forEach {
            if (it.filePath == path.toNioPath().absolutePathString()) {
                selectTab(it)
                return
            }
        }
        screenModelScope.launch {
            withContext(Dispatchers.IO) {
                val newFileState = filesRepo.createFile(
                    path.name ?: ""
                )
                when (newFileState) {
                    is FileCreationState.Error -> {
                        showErrorMessage(newFileState.message)
                    }

                    is FileCreationState.Success -> {
                        val newFileName = newFileState.fileName
                        openAssetFile(path, newFileState.filePath.toPath(), newFileName)
                    }
                }
            }
        }
        //Todo update the case when the file is already opened in the editor  ,
        // the tab should re-read the contents to update any external changes
    }


    private fun openFile(path: Path, isTemp: Boolean = false) {

        try {
            val file = path.toFile()
            if (file.exists() && file.isFile) {
                // File exists, read the content
                val content = file.readText()
                // Add the tab with the file's name
                createTab(file.name, content, path.toNioPath().absolutePathString(), isTemp)
            } else {
                showErrorMessage("File does not exist or is not a regular file.")
                null
            }
        } catch (e: IOException) {
            e.printStackTrace()
            Log.e("FileOpening", "Error occurred while opening the file: ${e.message}")
            showErrorMessage("Error occurred while opening the file: ${e.message}")
            null

        }
    }

    private fun openAssetFile(AssetPath: Path, TempFilePath: Path, filename: String?) {
        try {
            // Open the asset file as an InputStream
            val assetManager = context.assets
            val inputStream = assetManager.open(AssetPath.toString())

            // Read the content of the file as a String
            val content = inputStream.bufferedReader().use { it.readText() }

            // Close the stream after reading
            inputStream.close()
            val FallbackName = filename ?: AssetPath.name
            // Create a tab for the asset file
            createTab(
                FallbackName,
                content,
                TempFilePath.toString(),
                false,
            )
        } catch (e: IOException) {
            e.printStackTrace()
            Log.e("AssetFileOpening", "Error occurred while opening asset file: ${e.message}")
            showErrorMessage("Error occurred while opening asset file: ${e.message}")
        }
    }

    fun createTab(title: String, path: String) {
        tabs.update {
            deselectAllTabs()
            val x = it.apply { add(EditorTabState(title, true, "", path)) }
            x.toMutableStateList()
        }

    }


    //function that creates tab with tabstate directly
    fun createTab(tab: EditorTabState) {
        //if the file path already exists just select it
        tabs.value.forEach {
            if (it.filePath == tab.filePath) {
                selectTab(it)
                return
            }
        }
        tabs.update {
            deselectAllTabs()
            val x = it.apply { add(tab) }
            x.toMutableStateList()
        }
    }

    fun closeTab(tab: EditorTabState) {
        //if the file has not Changed just close it
        if (!tab.changed && !tab.temp) {
            tabs.update { it ->
                val index = it.indexOf(tab)
                if (index < 0) {
                    return@update it
                }
                it.remove(tab)
                onTabClosed?.invoke(tab)
                selectTab(it.getOrNull(index - 1) ?: return@update it.toMutableStateList())
                it.toMutableStateList()
            }
            return
        }
        //if the file has changed show a dialog to ask the user if he wants to save the file
        //if he wants to save the file save it and then close it
        showSaveDialog.update {
            it.copy(isShown = true, currentTab = tab)
        }
    }

    fun closeWithoutSaving(tab: EditorTabState?) {
        tab ?: return
        tabs.update { it ->
            val index = it.indexOf(tab)
            if (index < 0) {
                return@update it
            }
            it.remove(tab)
            onTabClosed?.invoke(tab)
            selectTab(it.getOrNull(index - 1) ?: return@update it.toMutableStateList())
            it.toMutableStateList()
        }
        showSaveDialog.update { it.copy(isShown = false, currentTab = null) }

    }

    fun saveAndCloseTab(tab: EditorTabState?) {
        tab ?: run {
            showSaveDialog.update { it.copy(isShown = false, currentTab = null) }
            return
        }
        saveFile(
            tab.filePath
        )
        closeTab(tab)
        showSaveDialog.update { it.copy(isShown = false, currentTab = null) }

    }

    fun selectTab(tab: EditorTabState) {
        //find the currently selected tab if it is not the same path as the tab then call onTabDeselected
        tabs.value.forEach {
            if (it.selected && (it.filePath != tab.filePath)) {
                onTabDeselcted?.invoke(it, tab)
            }
        }
        deselectAllTabs()
        tabs.update {
            it.apply { find { it == tab }?.selected = true }
            it.toMutableStateList()
        }
//        selectedTabIndex.value = tabs.value.indexOf(tab)

    }

    fun saveCurrentFile() {
        val tab = tabs.value.firstOrNull {
            it.selected
        } ?: return
        saveFile(tab.filePath)
    }

    fun saveFile(
        filePath: String,
    ) {
        screenModelScope.launchIO {
            //Save the Selected Tab
            val tab = tabs.value.firstOrNull {
                //get the tab by its absolutepath
                it.filePath == filePath
            } ?: return@launchIO
            try {
                val fileState = filesRepo.saveFile(filePath, tab.editorText)
                when (fileState) {
                    is FileSaveState.Error -> {
                        showErrorMessage(fileState.message)
                        return@launchIO
                    }

                    is FileSaveState.Success -> {}
                }
                tab.saveFile()
            } catch (e: IOException) {
                e.printStackTrace()
                showErrorMessage("Error occurred while saving the file: ${e.message}")
            }


        }
    }

    fun showErrorMessage(
        message: String,
    ) {
        showErrorSnackbar.update {
            it.copy(
                shown = true,
                message = "$message",
                backgroundColor = ErrorRed,
            )
        }
    }

    fun openautoSavedTabs(openTabs: List<EditorTabState>) {
        val tabsSize = tabs.value.size
        tabs.update { currentTabs ->
            val existingPaths = currentTabs.map(EditorTabState::filePath).toSet()
            val distinctNewTabs = openTabs.filter { it.filePath !in existingPaths }
            if (tabsSize > 0) {
                distinctNewTabs.map { it.selected = false }
            }
            (currentTabs + distinctNewTabs).toMutableStateList()
        }
    }

    fun closeCurrentTab() {
        val tab = tabs.value.firstOrNull {
            it.selected
        } ?: return
        closeTab(tab)
    }

    fun openProjectTab(path: Path, assetPath: String) {
        //Create a normal Editor tab state with the path and the content and add it to the tabs but mark it as isproject and the project path is the path until the asset name which always ends with .zip
        var projectPath = assetPath.substringBeforeLast(".zip")
        val projectTabContent = openProjectFile(path)
        val projectTab = EditorTabState(
            path.name,
            originalContent = projectTabContent,
            path.toString(),
            isTemp = false
        ).apply {
            isproject = true
            projectPath = projectPath
        }
        createTab(projectTab)
    }

    private fun openProjectFile(path: Path): String {
        try {
            val file = path.toFile()
            if (file.exists() && file.isFile) {
                // File exists, read the content
                val content = file.readText()
                // Add the tab with the file's name
                return content
            } else {
                showErrorMessage("File does not exist or is not a regular file.")
            }
        } catch (e: IOException) {
            e.printStackTrace()
            Log.e("FileOpening", "Error occurred while opening the file: ${e.message}")
            showErrorMessage("Error occurred while opening the file: ${e.message}")
        }
        return ""
    }

//
//    fun openautoSavedTabs(openautoSavedTabs: List<EditorTabState>) {
//        //if the tabs were not empty we need to add all the openautoSavedTabs but they are all unselcted
//        //also if the tab already exists and selected we shouldn't be deslecting it by distinct by
//        //how to do that?
//        tabs.update {
//            //we add the new saved tabs to the current ones
//            //but we make sure they are distinct
//            it.apply {
//                addAll(openautoSavedTabs)
//            }.distinctBy { it.filePath }.toMutableStateList()
//        }
//
//    }
}

  @Test
    fun openFileFromDrawer_opensNewFile() = runTest {
        val filePath = "path/to/file".toPath()
        val file = mock(File::class.java)
        `when`(file.exists()).thenReturn(true)
        `when`(file.isFile).thenReturn(true)
        `when`(file.readText()).thenReturn("file content")
        `when`(filePath.toFile()).thenReturn(file)

        viewModel.openFileFromDrawer(filePath)

        val tabs = viewModel.tabs.value
        delayedAssert {
            Assert.assertEquals(1, tabs.size)
            Assert.assertEquals("file", tabs[0].title)
            Assert.assertEquals("file content", tabs[0].editorText)
        }
    }
Test fails with exception
java.lang.NullPointerException
at java.io.FileInputStream.<init>(FileInputStream.java:152)
at kotlin.io.FilesKt__FileReadWriteKt.readText(FileReadWrite.kt:131)
at kotlin.io.FilesKt__FileReadWriteKt.readText$default(FileReadWrite.kt:131)
at com.hady.codeeditor.MainViewmodelTest$openFileFromDrawer_opensNewFile$1.invokeSuspend(mainTests.kt:134)