package com.sunzones.ui.addlocation

import android.annotation.SuppressLint
import android.icu.util.TimeZone as IcuTimeZone
import android.location.Address
import android.location.Geocoder
import android.os.Build
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.Priority
import com.google.android.gms.tasks.CancellationTokenSource
import com.sunzones.data.local.LocationEntity
import com.sunzones.data.repository.LocationRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import java.time.ZoneId
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.math.abs
import kotlin.math.roundToInt

data class AddLocationUiState(
    val query: String = "",
    val searchResults: List<SearchResult> = emptyList(),
    val isSearching: Boolean = false,
    val isLocating: Boolean = false,
    val isSaved: Boolean = false,
    val error: String? = null
)

data class SearchResult(
    val name: String,
    val latitude: Double,
    val longitude: Double,
    val countryCode: String?
)

@HiltViewModel
class AddLocationViewModel @Inject constructor(
    private val geocoder: Geocoder,
    private val fusedLocationClient: FusedLocationProviderClient,
    private val repository: LocationRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(AddLocationUiState())
    val uiState: StateFlow<AddLocationUiState> = _uiState.asStateFlow()

    fun resetState() {
        _uiState.value = AddLocationUiState()
    }

    fun onQueryChanged(query: String) {
        _uiState.value = _uiState.value.copy(query = query)
        if (query.length >= 2) {
            searchLocations(query)
        } else {
            _uiState.value = _uiState.value.copy(searchResults = emptyList())
        }
    }

    private fun searchLocations(query: String) {
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(isSearching = true)
            try {
                val addresses = geocodeQuery(query)
                val results = addresses.mapNotNull { address ->
                    val name = buildLocationName(address) ?: return@mapNotNull null
                    SearchResult(
                        name = name,
                        latitude = address.latitude,
                        longitude = address.longitude,
                        countryCode = address.countryCode
                    )
                }
                _uiState.value = _uiState.value.copy(
                    searchResults = results,
                    isSearching = false
                )
            } catch (e: Exception) {
                _uiState.value = _uiState.value.copy(
                    searchResults = emptyList(),
                    isSearching = false
                )
            }
        }
    }

    @Suppress("DEPRECATION")
    private suspend fun geocodeQuery(query: String): List<Address> {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            suspendCancellableCoroutine { continuation ->
                geocoder.getFromLocationName(query, 5) { addresses ->
                    continuation.resume(addresses)
                }
            }
        } else {
            geocoder.getFromLocationName(query, 5) ?: emptyList()
        }
    }

    fun selectSearchResult(result: SearchResult) {
        viewModelScope.launch {
            val timezoneId = lookupTimezone(result.latitude, result.longitude, result.countryCode)
            val entity = LocationEntity(
                name = result.name,
                latitude = result.latitude,
                longitude = result.longitude,
                timezoneId = timezoneId,
                isCurrentLocation = false,
                displayOrder = repository.getCount()
            )
            repository.addLocation(entity)
            _uiState.value = _uiState.value.copy(isSaved = true)
        }
    }

    @SuppressLint("MissingPermission")
    fun useCurrentLocation() {
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(isLocating = true)
            try {
                val location = suspendCancellableCoroutine { continuation ->
                    fusedLocationClient.getCurrentLocation(
                        Priority.PRIORITY_HIGH_ACCURACY,
                        CancellationTokenSource().token
                    ).addOnSuccessListener { loc ->
                        continuation.resume(loc)
                    }.addOnFailureListener {
                        continuation.resume(null)
                    }
                }

                if (location != null) {
                    val address = reverseGeocodeAddress(location.latitude, location.longitude)
                    val name = address?.let { buildLocationName(it) }
                        ?: "${String.format("%.2f", location.latitude)}, ${String.format("%.2f", location.longitude)}"
                    val timezoneId = lookupTimezone(
                        location.latitude,
                        location.longitude,
                        address?.countryCode
                    )

                    val entity = LocationEntity(
                        name = name,
                        latitude = location.latitude,
                        longitude = location.longitude,
                        timezoneId = timezoneId,
                        isCurrentLocation = false,
                        displayOrder = repository.getCount()
                    )
                    repository.addLocation(entity)
                    _uiState.value = _uiState.value.copy(isLocating = false, isSaved = true)
                } else {
                    _uiState.value = _uiState.value.copy(
                        isLocating = false,
                        error = "Could not get current location"
                    )
                }
            } catch (e: Exception) {
                _uiState.value = _uiState.value.copy(
                    isLocating = false,
                    error = e.message
                )
            }
        }
    }

    @Suppress("DEPRECATION")
    private suspend fun reverseGeocodeAddress(lat: Double, lng: Double): Address? {
        return try {
            val addresses = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                suspendCancellableCoroutine { continuation ->
                    geocoder.getFromLocation(lat, lng, 1) { addresses ->
                        continuation.resume(addresses)
                    }
                }
            } else {
                geocoder.getFromLocation(lat, lng, 1) ?: emptyList()
            }
            addresses.firstOrNull()
        } catch (_: Exception) {
            null
        }
    }

    private fun buildLocationName(address: Address): String? {
        return address.locality
            ?: address.subAdminArea
            ?: address.adminArea
            ?: address.featureName
    }

    private fun lookupTimezone(lat: Double, lng: Double, countryCode: String?): String {
        if (countryCode != null) {
            val ids = IcuTimeZone.getAvailableIDs(countryCode)
            if (ids != null && ids.isNotEmpty()) {
                if (ids.size == 1) return ids[0]
                return findClosestTimezone(ids, lng)
            }
        }
        // Fallback: estimate timezone from longitude
        val offsetHours = (lng / 15.0).roundToInt()
        val sign = if (offsetHours >= 0) "+" else ""
        return "GMT$sign$offsetHours"
    }

    private fun findClosestTimezone(ids: Array<String>, longitude: Double): String {
        val expectedOffsetMs = (longitude / 15.0 * 3_600_000).toLong()
        return ids.minByOrNull { id ->
            val tz = IcuTimeZone.getTimeZone(id)
            abs(tz.rawOffset.toLong() - expectedOffsetMs)
        } ?: ids[0]
    }
}
