package de.krisenvorrat.app.data.sync

import de.krisenvorrat.app.domain.model.SettingsKeys
import de.krisenvorrat.app.domain.model.SyncError
import de.krisenvorrat.app.domain.repository.SettingsRepository
import de.krisenvorrat.app.domain.repository.SyncService
import de.krisenvorrat.shared.model.InventoryDto
import de.krisenvorrat.shared.model.ItemDto
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.patch
import io.ktor.client.request.post
import io.ktor.client.request.put
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.ConnectException
import java.net.SocketTimeoutException
import javax.inject.Inject

internal class SyncServiceImpl @Inject constructor(
    private val httpClient: HttpClient,
    private val settingsRepository: SettingsRepository
) : SyncService {

    override suspend fun downloadInventory(since: Long?): Result<InventoryDto> =
        executeRequest { serverUrl, token ->
            val url = buildString {
                append("$serverUrl/api/inventory")
                if (since != null) append("?since=$since")
            }
            val response = httpClient.get(url) {
                header("Authorization", "Bearer $token")
            }
            handleResponse(response)
        }

    override suspend fun uploadInventory(inventory: InventoryDto): Result<InventoryDto> =
        executeRequest { serverUrl, token ->
            val response = httpClient.put("$serverUrl/api/inventory") {
                header("Authorization", "Bearer $token")
                contentType(ContentType.Application.Json)
                setBody(inventory)
            }
            handleResponse(response)
        }

    override suspend fun patchItem(itemId: String, item: ItemDto): Result<Unit> =
        executeItemRequest { serverUrl, token ->
            val response = httpClient.patch("$serverUrl/api/inventory/items/$itemId") {
                header("Authorization", "Bearer $token")
                contentType(ContentType.Application.Json)
                setBody(item)
            }
            handleUnitResponse(response)
        }

    override suspend fun deleteItem(itemId: String): Result<Unit> =
        executeItemRequest { serverUrl, token ->
            val response = httpClient.delete("$serverUrl/api/inventory/items/$itemId") {
                header("Authorization", "Bearer $token")
            }
            handleUnitResponse(response)
        }

    override suspend fun login(
        serverUrl: String,
        username: String,
        password: String
    ): Result<Unit> = withContext(Dispatchers.IO) {
        try {
            val response = httpClient.post("${serverUrl.trimEnd('/')}/api/auth/login") {
                contentType(ContentType.Application.Json)
                setBody(LoginRequest(username, password))
            }
            when (response.status) {
                HttpStatusCode.OK -> {
                    val loginResponse: LoginResponse = response.body()
                    settingsRepository.setValue(KEY_SERVER_URL, serverUrl)
                    settingsRepository.setValue(KEY_ACCESS_TOKEN, loginResponse.accessToken)
                    settingsRepository.setValue(KEY_REFRESH_TOKEN, loginResponse.refreshToken)
                    settingsRepository.setValue(KEY_AUTH_USERNAME, username)
                    settingsRepository.setValue(KEY_AUTH_USER_ID, loginResponse.userId)
                    Result.success(Unit)
                }
                HttpStatusCode.Unauthorized -> Result.failure(SyncError.AuthError())
                else -> Result.failure(
                    SyncError.ServerError(response.status.value, response.status.description)
                )
            }
        } catch (e: SocketTimeoutException) {
            Result.failure(SyncError.Timeout(e))
        } catch (e: ConnectException) {
            Result.failure(SyncError.ConnectionError(e))
        } catch (e: Exception) {
            Result.failure(SyncError.Unknown(e))
        }
    }

    override suspend fun logout() {
        settingsRepository.setValue(KEY_ACCESS_TOKEN, "")
        settingsRepository.setValue(KEY_REFRESH_TOKEN, "")
        settingsRepository.setValue(KEY_AUTH_USERNAME, "")
    }

    private suspend fun executeRequest(
        block: suspend (serverUrl: String, token: String) -> Result<InventoryDto>
    ): Result<InventoryDto> = withContext(Dispatchers.IO) {
        val serverUrl = settingsRepository.getValue(KEY_SERVER_URL)
        if (serverUrl.isNullOrBlank()) {
            return@withContext Result.failure(SyncError.NotConfigured("Server-URL nicht gesetzt"))
        }
        val token = settingsRepository.getValue(KEY_ACCESS_TOKEN)
        if (token.isNullOrBlank()) {
            return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet"))
        }
        try {
            val result = block(serverUrl.trimEnd('/'), token)
            // Auto-refresh on 401
            if (result.exceptionOrNull() is SyncError.AuthError) {
                val refreshed = refreshToken(serverUrl.trimEnd('/'))
                if (refreshed) {
                    val newToken = settingsRepository.getValue(KEY_ACCESS_TOKEN)
                        ?: return@withContext result
                    block(serverUrl.trimEnd('/'), newToken)
                } else {
                    result
                }
            } else {
                result
            }
        } catch (e: SocketTimeoutException) {
            Result.failure(SyncError.Timeout(e))
        } catch (e: ConnectException) {
            Result.failure(SyncError.ConnectionError(e))
        } catch (e: Exception) {
            Result.failure(SyncError.Unknown(e))
        }
    }

    private suspend fun executeItemRequest(
        block: suspend (serverUrl: String, token: String) -> Result<Unit>
    ): Result<Unit> = withContext(Dispatchers.IO) {
        val serverUrl = settingsRepository.getValue(KEY_SERVER_URL)
        if (serverUrl.isNullOrBlank()) {
            return@withContext Result.failure(SyncError.NotConfigured("Server-URL nicht gesetzt"))
        }
        val token = settingsRepository.getValue(KEY_ACCESS_TOKEN)
        if (token.isNullOrBlank()) {
            return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet"))
        }
        try {
            block(serverUrl.trimEnd('/'), token)
        } catch (e: SocketTimeoutException) {
            Result.failure(SyncError.Timeout(e))
        } catch (e: ConnectException) {
            Result.failure(SyncError.ConnectionError(e))
        } catch (e: Exception) {
            Result.failure(SyncError.Unknown(e))
        }
    }

    private suspend fun refreshToken(serverUrl: String): Boolean {
        val refreshToken = settingsRepository.getValue(KEY_REFRESH_TOKEN)
        if (refreshToken.isNullOrBlank()) return false
        return try {
            val response = httpClient.post("$serverUrl/api/auth/refresh") {
                contentType(ContentType.Application.Json)
                setBody(RefreshRequest(refreshToken))
            }
            if (response.status == HttpStatusCode.OK) {
                val loginResponse: LoginResponse = response.body()
                settingsRepository.setValue(KEY_ACCESS_TOKEN, loginResponse.accessToken)
                settingsRepository.setValue(KEY_REFRESH_TOKEN, loginResponse.refreshToken)
                true
            } else {
                false
            }
        } catch (_: Exception) {
            false
        }
    }

    private suspend fun handleResponse(response: HttpResponse): Result<InventoryDto> =
        when (response.status) {
            HttpStatusCode.OK -> Result.success(response.body())
            HttpStatusCode.Unauthorized -> Result.failure(SyncError.AuthError())
            else -> Result.failure(
                SyncError.ServerError(
                    statusCode = response.status.value,
                    message = response.status.description
                )
            )
        }

    private fun handleUnitResponse(response: HttpResponse): Result<Unit> =
        when (response.status) {
            HttpStatusCode.OK, HttpStatusCode.NoContent -> Result.success(Unit)
            HttpStatusCode.Unauthorized -> Result.failure(SyncError.AuthError())
            else -> Result.failure(
                SyncError.ServerError(
                    statusCode = response.status.value,
                    message = response.status.description
                )
            )
        }

    private companion object {
        val KEY_SERVER_URL = SettingsKeys.SERVER_URL
        val KEY_ACCESS_TOKEN = SettingsKeys.AUTH_ACCESS_TOKEN
        val KEY_REFRESH_TOKEN = SettingsKeys.AUTH_REFRESH_TOKEN
        val KEY_AUTH_USERNAME = SettingsKeys.AUTH_USERNAME
        val KEY_AUTH_USER_ID = SettingsKeys.AUTH_USER_ID
    }
}
