package com.humand.demo.modules.CallService

import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.Log
import com.humand.demo.BuildConfig
import com.humand.demo.modules.IncomingCallMessagingService.common.Constants
import com.reactnativekeysjsi.KeysModule
import com.tencent.mmkv.MMKV
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Headers
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONObject
import java.util.concurrent.TimeUnit

/**
 * API service for call-related backend requests.
 */
@SuppressLint("StaticFieldLeak")
object CallApiService {
    private const val TAG = "CallApiService"
    private val baseUrl = BuildConfig.API_URL
    private lateinit var context: Context

    private val client: OkHttpClient by lazy {
        OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .build()
    }

    private val mmkv by lazy {
        val encryptionKey = getKeychainKey()
        MMKV.mmkvWithID("user-storage", MMKV.MULTI_PROCESS_MODE, encryptionKey)
    }

    var streamApiKey: String? = null
        private set

    fun init(appContext: Context) {
        context = appContext.applicationContext
        MMKV.initialize(context)
        streamApiKey = KeysModule.getSecureFor("STREAMIO_API_KEY")
        apiLog("CallApiService initialized with baseUrl: $baseUrl")
    }

    private fun getKeychainKey(): String {
        return KeysModule.getSecureFor(Constants.API.KEYCHAIN_KEY)
    }

    private fun getAuthTokens(): AuthTokens? {
        val value = mmkv.decodeString(Constants.API.TOKENS) ?: return null
        return try {
            val json = JSONObject(value)
            val accessToken = json.optString(Constants.API.ACCESS_TOKEN, "")
            val refreshToken = json.optString(Constants.API.REFRESH_TOKEN, "")
            AuthTokens(accessToken, refreshToken)
        } catch (e: Exception) {
            apiLog("Failed to parse TOKENS: ${e.message}")
            null
        }
    }

    private fun getHeaders(authToken: String): Headers {
        val osVersion = Build.VERSION.RELEASE
        val versionName = try {
            context.packageManager.getPackageInfo(context.packageName, 0).versionName
        } catch (e: Exception) {
            "unknown"
        }

        return Headers.Builder()
            .add("Authorization", "Bearer $authToken")
            .add("Content-Type", "application/json")
            .add("Accept", "*/*")
            .add("x-device-type", "android")
            .add(Constants.API.Headers.ORIGIN, Constants.API.Headers.ORIGIN_ANDROID)
            .add("x-humand-device-version", versionName.toString())
            .add("x-device-version", osVersion)
            .build()
    }

    // MARK: - Authenticated request wrapper

    /**
     * Execute an HTTP request with automatic token retrieval and 401 refresh.
     * Returns the successful response body as String, or null on any failure.
     */
    private suspend fun executeWithAuth(
        tag: String,
        request: (accessToken: String) -> Response
    ): String? = withContext(Dispatchers.IO) {
        try {
            val tokens = getAuthTokens()
            if (tokens?.accessToken.isNullOrBlank()) {
                apiLog("$tag: no access token")
                return@withContext null
            }

            val response = request(tokens.accessToken!!)
            response.use {
                if (it.isSuccessful) {
                    return@withContext it.body?.string()
                }

                apiLog("$tag failed: HTTP ${it.code}")

                if (it.code == 401 && !tokens.refreshToken.isNullOrBlank()) {
                    val nextTokens = refreshToken(tokens.refreshToken!!) ?: return@withContext null
                    if (nextTokens.accessToken.isNullOrBlank()) return@withContext null

                    val retryResponse = request(nextTokens.accessToken!!)
                    retryResponse.use { retry ->
                        if (retry.isSuccessful) {
                            return@withContext retry.body?.string()
                        }
                        apiLog("$tag retry failed: HTTP ${retry.code}")
                    }
                }
                null
            }
        } catch (e: Exception) {
            apiLog("$tag exception: ${e.message}")
            null
        }
    }

    // MARK: - Public API

    /**
     * Set auth tokens from React Native.
     */
    fun setAuthTokens(tokens: Map<String, Any>?) {
        if (tokens == null) {
            apiLog("Clearing auth tokens")
            return
        }
        try {
            val json = JSONObject().apply {
                put(Constants.API.ACCESS_TOKEN, tokens["accessToken"])
                put(Constants.API.REFRESH_TOKEN, tokens["refreshToken"])
            }
            mmkv.encode(Constants.API.TOKENS, json.toString())
            apiLog("Auth tokens updated from RN")
        } catch (e: Exception) {
            apiLog("Failed to save tokens from RN: ${e.message}")
        }
    }

    /**
     * Get call token for joining a stream call.
     */
    suspend fun getCallToken(): CallTokenResponse? {
        val body = executeWithAuth("getCallToken") { token ->
            post("/calls/token", null, token)
        } ?: return null

        return try {
            apiLog("getCallToken response: $body")
            val json = JSONObject(body)
            CallTokenResponse(
                token = json.getString("token"),
                participantId = json.getString("participantId")
            )
        } catch (e: Exception) {
            apiLog("getCallToken parse error: ${e.message}")
            null
        }
    }

    /**
     * Get current user info from /janus/me endpoint.
     */
    suspend fun getCurrentUser(): UserResponse? {
        val body = executeWithAuth("getCurrentUser") { token ->
            get("/janus/me", token)
        } ?: return null

        return try {
            val json = JSONObject(body)
            val userJson = json.optJSONObject("user") ?: return null
            UserResponse(
                firstName = userJson.optString("firstName", null),
                lastName = userJson.optString("lastName", null),
                fullName = userJson.optString("fullName", null)
            )
        } catch (e: Exception) {
            apiLog("getCurrentUser parse error: ${e.message}")
            null
        }
    }

    /**
     * Get livestream token for host or viewer.
     */
    suspend fun getLivestreamToken(isHost: Boolean): LivestreamTokenResponse? {
        apiLog("getLivestreamToken: requesting as ${if (isHost) "host" else "viewer"}")
        val requestBody = if (isHost) null else JSONObject().apply { put("role", "viewer") }

        val body = executeWithAuth("getLivestreamToken") { token ->
            post("/livestreams/token", requestBody, token)
        } ?: return null

        return try {
            val json = JSONObject(body)
            val tokenResponse = LivestreamTokenResponse(token = json.getString("token"))
            apiLog("getLivestreamToken: got token for participantId=${tokenResponse.participantId}")
            tokenResponse
        } catch (e: Exception) {
            apiLog("getLivestreamToken parse error: ${e.message}")
            null
        }
    }

    suspend fun accept(id: String): Boolean {
        return executeWithAuth("accept") { token ->
            post("/calls/$id/accept", null, token)
        } != null
    }

    suspend fun reject(id: String): Boolean {
        return executeWithAuth("reject") { token ->
            post("/calls/$id/reject", null, token)
        } != null
    }

    suspend fun end(id: String): Boolean {
        return executeWithAuth("end") { token ->
            post("/calls/$id/end", null, token)
        } != null
    }

    suspend fun miss(id: String, participantUserIds: List<Int>): Boolean {
        val body = JSONObject().apply {
            val arr = org.json.JSONArray()
            participantUserIds.forEach { arr.put(it) }
            put("participantUserIds", arr)
        }
        return executeWithAuth("miss") { token ->
            post("/calls/$id/miss", body, token)
        } != null
    }

    // MARK: - HTTP helpers

    private fun post(path: String, body: JSONObject?, accessToken: String): Response {
        val headers = getHeaders(accessToken)
        val mediaType = "application/json".toMediaType()
        val requestBody = (body?.toString() ?: "").toRequestBody(mediaType)

        val request = Request.Builder()
            .url("$baseUrl$path")
            .post(requestBody)
            .headers(headers)
            .build()

        return client.newCall(request).execute()
    }

    private fun get(path: String, accessToken: String): Response {
        val headers = getHeaders(accessToken)

        val request = Request.Builder()
            .url("$baseUrl$path")
            .get()
            .headers(headers)
            .build()

        return client.newCall(request).execute()
    }

    // MARK: - Token management

    private fun refreshToken(token: String): AuthTokens? {
        val headers = getHeaders(token)
        val request = Request.Builder()
            .url("$baseUrl/auth/refresh")
            .headers(headers)
            .build()

        val response = client.newCall(request).execute()
        response.use {
            val bodyString = it.body?.string()

            if (!it.isSuccessful) {
                apiLog("Refresh token failed: HTTP ${it.code}")
                return null
            }

            return try {
                val json = JSONObject(bodyString ?: "")
                val accessToken = json.optString("accessToken", "")
                val refreshToken = json.optString("refreshToken", "")

                if (accessToken.isNotEmpty() && refreshToken.isNotEmpty()) {
                    val tokens = AuthTokens(accessToken, refreshToken)
                    updateTokens(tokens)
                    tokens
                } else {
                    apiLog("Missing tokens in refresh response")
                    null
                }
            } catch (e: Exception) {
                apiLog("Failed to parse refresh response: ${e.message}")
                null
            }
        }
    }

    private fun updateTokens(tokens: AuthTokens) {
        try {
            val json = JSONObject().apply {
                put(Constants.API.ACCESS_TOKEN, tokens.accessToken)
                put(Constants.API.REFRESH_TOKEN, tokens.refreshToken)
            }
            mmkv.encode(Constants.API.TOKENS, json.toString())
            apiLog("Tokens updated successfully")
        } catch (e: Exception) {
            apiLog("Failed to save tokens: ${e.message}")
        }
    }
}
