package com.tt.honeyDue.network

import com.tt.honeyDue.data.DataManager
import com.tt.honeyDue.models.TokenRefreshResponse
import com.tt.honeyDue.storage.TokenStorage
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.compression.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json

expect fun getLocalhostAddress(): String
expect fun createHttpClient(): HttpClient

/**
 * Get the device's preferred language code (e.g., "en", "es", "fr").
 * This is used to set the Accept-Language header for API requests
 * so the server can return localized error messages and content.
 */
expect fun getDeviceLanguage(): String

/**
 * Get the device's timezone identifier (e.g., "America/Los_Angeles", "Europe/London").
 * This is used to set the X-Timezone header for API requests
 * so the server can calculate overdue tasks correctly based on the user's local time.
 */
expect fun getDeviceTimezone(): String

/**
 * Mutex to prevent multiple concurrent token refresh attempts.
 * When one request triggers a 401, only one refresh call is made;
 * other requests wait for it to complete.
 */
private val tokenRefreshMutex = Mutex()

/**
 * Install common plugins shared across all platform HttpClient instances.
 * This includes retry with exponential backoff, request timeouts,
 * gzip content encoding, and 401 token refresh handling.
 */
fun HttpClientConfig<*>.installCommonPlugins() {
    // Task 1: Gzip support — tells the server we accept compressed responses
    install(ContentEncoding) {
        gzip()
    }

    // Task 2: Retry with exponential backoff for server errors, IO exceptions,
    // and 429 rate-limit responses (using the Retry-After header for delay).
    install(HttpRequestRetry) {
        maxRetries = 3
        retryIf { _, response ->
            response.status.value in 500..599 || response.status.value == 429
        }
        retryOnExceptionIf { _, cause ->
            // Retry on network-level IO errors (connection resets, timeouts, etc.)
            // but not on application-level exceptions like serialization errors
            cause is io.ktor.client.network.sockets.ConnectTimeoutException ||
                cause is io.ktor.client.plugins.HttpRequestTimeoutException ||
                cause.cause is kotlinx.io.IOException ||
                cause is kotlinx.io.IOException
        }
        exponentialDelay(
            base = 1000.0,
            maxDelayMs = 10_000
        )
    }

    // Task 4: Request timeout configuration
    install(HttpTimeout) {
        requestTimeoutMillis = 30_000  // 30 seconds
        connectTimeoutMillis = 10_000  // 10 seconds
        socketTimeoutMillis = 30_000   // 30 seconds
    }

    // Task 3: Token refresh on 401 responses
    HttpResponseValidator {
        validateResponse { response ->
            if (response.status.value == 401) {
                // Check if this is a token_expired error (not invalid credentials)
                val bodyText = response.bodyAsText()
                val isTokenExpired = bodyText.contains("token_expired") ||
                    bodyText.contains("Token has expired") ||
                    bodyText.contains("expired")

                if (isTokenExpired) {
                    val currentToken = DataManager.authToken.value
                    if (currentToken != null) {
                        // Use mutex to prevent concurrent refresh attempts
                        val refreshed = tokenRefreshMutex.withLock {
                            // Double-check: another coroutine may have already refreshed
                            val tokenAfterLock = DataManager.authToken.value
                            if (tokenAfterLock != currentToken) {
                                // Token was already refreshed by another coroutine
                                true
                            } else {
                                attemptTokenRefresh(currentToken)
                            }
                        }
                        if (!refreshed) {
                            // Refresh failed — clear auth and trigger logout
                            DataManager.clear()
                        }
                        // Throw so the caller can retry (or handle the logout)
                        throw TokenExpiredException(refreshed)
                    }
                }
            }
        }
    }
}

/**
 * Attempt to refresh the auth token by calling POST /api/auth/refresh/.
 * Returns true if refresh succeeded and the new token was saved.
 */
private suspend fun attemptTokenRefresh(currentToken: String): Boolean {
    return try {
        // Use a minimal client to avoid recursive interceptor triggers
        val refreshClient = HttpClient {
            install(ContentNegotiation) {
                json(Json {
                    ignoreUnknownKeys = true
                    isLenient = true
                })
            }
            install(HttpTimeout) {
                requestTimeoutMillis = 15_000
                connectTimeoutMillis = 10_000
                socketTimeoutMillis = 15_000
            }
        }
        val baseUrl = ApiConfig.getBaseUrl()
        val response = refreshClient.post("$baseUrl/auth/refresh/") {
            header("Authorization", "Token $currentToken")
            contentType(ContentType.Application.Json)
        }
        refreshClient.close()

        if (response.status.isSuccess()) {
            val tokenResponse = response.body<TokenRefreshResponse>()
            // Save the new token to both DataManager and persistent storage
            DataManager.setAuthToken(tokenResponse.token)
            TokenStorage.saveToken(tokenResponse.token)
            println("[ApiClient] Token refreshed successfully")
            true
        } else {
            println("[ApiClient] Token refresh failed: ${response.status.value}")
            false
        }
    } catch (e: Exception) {
        println("[ApiClient] Token refresh error: ${e.message}")
        false
    }
}

/**
 * Exception thrown when a 401 response indicates an expired token.
 * [refreshed] indicates whether the token was successfully refreshed.
 * Callers can catch this and retry the request if refreshed is true.
 */
class TokenExpiredException(val refreshed: Boolean) : Exception(
    if (refreshed) "Token was expired but has been refreshed — retry the request"
    else "Token expired and refresh failed — user must re-authenticate"
)

object ApiClient {
    val httpClient = createHttpClient()

    /**
     * Get the current base URL based on environment configuration.
     * To change environment, update ApiConfig.CURRENT_ENV
     */
    fun getBaseUrl(): String = ApiConfig.getBaseUrl()

    /**
     * Get the media base URL (without /api suffix) for serving media files
     */
    fun getMediaBaseUrl(): String = ApiConfig.getMediaBaseUrl()

    /**
     * Print current environment configuration
     */
    init {
        println("API Client initialized")
        println("Environment: ${ApiConfig.getEnvironmentName()}")
        println("Base URL: ${getBaseUrl()}")
        println("Media URL: ${getMediaBaseUrl()}")
    }
}
