package de.jadehs.strassenschadenpro2

import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.LoadStatus
import com.arcgismaps.data.ArcGISFeature
import com.arcgismaps.data.CodedValueDomain
import com.arcgismaps.data.QueryParameters
import com.arcgismaps.data.ServiceFeatureTable
import com.arcgismaps.geometry.GeometryEngine
import com.arcgismaps.geometry.Point
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.layers.FeatureLayer
import com.arcgismaps.mapping.view.ScreenCoordinate
import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
import de.jadehs.strassenschadenpro2.components.album.PhotoData
import kotlinx.coroutines.launch

class MapViewModel(application: Application): AndroidViewModel(application) {
    val map: ArcGISMap = ArcGISMap(BasemapStyle.OpenOsmStyle).apply {
        initialViewpoint = Viewpoint(53.14, 8.20, 20000.0)
    }
    // Hold a reference to the selected feature.
    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
    val mapViewProxy = MapViewProxy()

    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)

    lateinit var featureLayer: FeatureLayer

    // Create a snackbar message to display the result of feature operations.
    var snackBarMessage: String by mutableStateOf("")

    lateinit var serviceFeatureTable: ServiceFeatureTable

    var currentDamageType by mutableStateOf("")

    // The list of damage types to update the feature attribute.
    var damageTypeList: List<String> = mutableListOf()

    init {
        viewModelScope.launch {
            serviceFeatureTable = ServiceFeatureTable("https://services9.arcgis.com/UVxdrlZq3S3gqt7w/ArcGIS/rest/services/StrassenSchaeden/FeatureServer/0")
            serviceFeatureTable.load().onSuccess {
                // Get the field from the feature table that will be updated.
                val typeDamageField = serviceFeatureTable.fields.first { it.name == "Typ" }
                // Get the coded value domain for the field.
                val attributeDomain = typeDamageField.domain as CodedValueDomain
                // Add the damage types to the list.
                attributeDomain.codedValues.forEach {
                    damageTypeList += it.name
                }
            }
            featureLayer = FeatureLayer.createWithFeatureTable(serviceFeatureTable)
            map.operationalLayers.add(featureLayer)
        }
    }

    /**
     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
     * selection.
     */
    fun onFeatureOperationSelected(index: Int) {
        currentFeatureOperation = FeatureOperationType.entries[index]
        // Reset the selected feature when the operation changes.
        featureLayer?.clearSelection()
        selectedFeature = null
    }

    /**
     * Directs the behaviour of tap's on the map view.
     */
    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
        if (featureLayer?.loadStatus?.value != LoadStatus.Loaded) {
            snackBarMessage = "Layer not loaded!"
            return
        }
        when (currentFeatureOperation) {
            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
            else -> {}
        }
    }

    /**
     * Create a new feature at the tapped location with some default attributes
     */
    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
        // Create the feature.
        val feature = serviceFeatureTable?.createFeature()?.apply {
            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
                // Set feature attributes.
                attributes["Typ"] = "SL"
                attributes["Beschreibung"] = "Earthquake"
            }
        }
        feature?.let {
            viewModelScope.launch {
                // Add the feature to the table.
                serviceFeatureTable?.addFeature(it)
                // Apply the edits to the service on the service geodatabase.
                serviceFeatureTable?.applyEdits()
                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
                it.refresh()
                // Confirm feature addition.
                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
            }
        }
    }

    fun createFeatureWithAttributesAt(screenCoordinate: ScreenCoordinate, beschreibung: String, typ: String,photosFromAlbum: List<PhotoData>) {
        // Create the feature.
        val feature = serviceFeatureTable?.createFeature()?.apply {
            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
                // Set feature attributes.
                attributes["Typ"] = typ
                attributes["Beschreibung"] = beschreibung
            }
        }
        feature?.let {
            viewModelScope.launch {
                // Add the feature to the table.
                serviceFeatureTable?.addFeature(it)
                // Apply the edits to the service on the service geodatabase.
                serviceFeatureTable?.applyEdits()
                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
                it.refresh()

                var arcgisFeature = it as? ArcGISFeature
                if (arcgisFeature != null && photosFromAlbum.size>0){
                    for (photoData in photosFromAlbum){
                        arcgisFeature.addAttachment(photoData.name,photoData.contentType,photoData.data)
                    }
                    serviceFeatureTable?.updateFeature(arcgisFeature)
                    serviceFeatureTable?.applyEdits()
                }

                // Confirm feature addition.
                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
            }
        }
    }

    fun createFeatureWithAttributesAtPoint(
        point: Point,
        beschreibung: String,
        typ: String,
        photoDataList: List<PhotoData>
    ) {
        // Create the feature.
        val feature = serviceFeatureTable?.createFeature()?.apply {
            // Get the normalized geometry for the tapped location and use it as the feature's geometry.

                geometry = GeometryEngine.normalizeCentralMeridian(point)
                // Set feature attributes.
                attributes["Typ"] = typ
                attributes["Beschreibung"] = beschreibung

        }
        feature?.let {
            viewModelScope.launch {
                // Add the feature to the table.
                serviceFeatureTable?.addFeature(it)
                // Apply the edits to the service on the service geodatabase.
                serviceFeatureTable?.applyEdits()
                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
                it.refresh()

                var arcgisFeature = it as? ArcGISFeature
                if (arcgisFeature != null && photoDataList.size>0){
                    for (photoData in photoDataList){
                        arcgisFeature.addAttachment(photoData.name,photoData.contentType,photoData.data)
                    }
                    serviceFeatureTable?.updateFeature(arcgisFeature)
                    serviceFeatureTable?.applyEdits()
                }

                // Confirm feature addition.
                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
            }
        }
    }

    /**
     * Selects a feature at the tapped location in preparation for deletion.
     */
    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
        featureLayer?.let { featureLayer ->
            // Clear any existing selection.
            featureLayer.clearSelection()
            selectedFeature = null
            viewModelScope.launch {
                // Determine if a user tapped on a feature.
                mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
                        featureLayer.selectFeature(it)
                    }
                }
            }
        }
    }

    /**
     * Delete the selected feature from the feature table and service geodatabase.
     */
    fun deleteSelectedFeature() {
        selectedFeature?.let {
            // Get the feature's object id.
            val featureId = it.attributes["objectid"] as Long
            viewModelScope.launch {
                // Delete the feature from the feature table.
                serviceFeatureTable?.deleteFeature(it)?.onSuccess {
                    snackBarMessage = "Deleted feature $featureId"
                    // Apply the edits to the service geodatabase.
                    serviceFeatureTable?.applyEdits()
                    selectedFeature = null
                }?.onFailure {
                    snackBarMessage = "Failed to delete feature $featureId"
                }
            }
        }
    }

    /**
     * Selects a feature at the tapped location in preparation for attribute editing.
     */
    private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
        featureLayer?.let { featureLayer ->
            // Clear any existing selection.
            featureLayer.clearSelection()
            selectedFeature = null
            viewModelScope.launch {
                // Determine if a user tapped on a feature.
                mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
                    // Get the identified feature.
                    val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
                    identifiedFeature?.let {
                        val currentAttributeValue = it.attributes["Typ"] as String
                        currentDamageType = currentAttributeValue
                        selectedFeature = it.also {
                            featureLayer.selectFeature(it)
                        }
                    } ?: run {
                        // Reset damage type if no feature identified.
                        currentDamageType = ""
                    }
                }
            }
        }
    }

    /**
     * Update the attribute value of the selected feature to the new value from the new damage type.
     */
    fun onDamageTypeSelected(index: Int) {
        // Get the new value.
        currentDamageType = damageTypeList[index]
        selectedFeature?.let { selectedFeature ->
            viewModelScope.launch {
                // Load the feature.
                selectedFeature.load().onSuccess {
                    // Update the attribute value.
                    selectedFeature.attributes["Typ"] = currentDamageType
                    // Update the table.
                    serviceFeatureTable?.updateFeature(selectedFeature)
                    // Update the service on the service geodatabase.
                    serviceFeatureTable?.applyEdits()?.onSuccess {
                        snackBarMessage =
                            "Updated feature ${selectedFeature.attributes["objectid"]} to $currentDamageType"
                    }
                }
            }
        }
    }

    /**
     * Select a feature, if none is selected. If a feature is selected, update its geometry to the tapped location.
     */
    private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) {

        featureLayer?.let { featureLayer ->
            when (selectedFeature) {
                // When no feature is selected.
                null -> {
                    viewModelScope.launch {
                        // Determine if a user tapped on a feature.
                        mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
                            // Get the identified feature.
                            val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
                            identifiedFeature?.let {
                                selectedFeature = it.also {
                                    featureLayer.selectFeature(it)
                                }
                            }
                        }
                    }
                }
                // When a feature is selected, update its geometry to the tapped location.
                else -> {
                    mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
                        // Normalize the point - needed when the tapped location is over the international date line.
                        val destinationPoint = GeometryEngine.normalizeCentralMeridian(mapPoint)
                        viewModelScope.launch {
                            selectedFeature?.let { selectedFeature ->
                                // Load the feature.
                                selectedFeature.load().onSuccess {
                                    // Update the geometry of the selected feature.
                                    selectedFeature.geometry = destinationPoint
                                    // Apply the edit to the feature table.
                                    serviceFeatureTable?.updateFeature(selectedFeature)
                                    // Push the update to the service with the service geodatabase.
                                    serviceFeatureTable?.applyEdits()?.onSuccess {
                                        snackBarMessage = "Moved feature ${selectedFeature.attributes["objectid"]}"
                                    }?.onFailure {
                                        snackBarMessage =
                                            "Failed to move feature ${selectedFeature.attributes["objectid"]}"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    fun zoomToFeature(objectId: Int){
        viewModelScope.launch {
            val queryParameters = QueryParameters().apply {
                whereClause = "OBJECTID = ${objectId}"
            }
            serviceFeatureTable.queryFeatures(queryParameters).onSuccess {
                featureResult ->
                val feature = featureResult.firstOrNull() as? ArcGISFeature
                feature?.let{ feature->
                    feature.geometry?.let{ geometry ->
                        mapViewProxy.setViewpointGeometry(geometry,50.0)
                    }
                    featureLayer.clearSelection()
                    featureLayer.selectFeature(feature)
                    selectedFeature = feature
                }
            }.onFailure { error ->
                snackBarMessage = "Feature nicht gefunden!"
            }
        }
    }
}

enum class FeatureOperationType(val operationName: String, val instruction: String) {
    CREATE("Create feature", "Tap on the map to create a new feature."),
    DELETE("Delete feature", "Select an existing feature to delete it."),
    UPDATE_ATTRIBUTE("Update attribute", "Select an existing feature to edit its attribute."),
    UPDATE_GEOMETRY("Update geometry", "Select an existing feature and tap the map to move it to a new position.")
}