package com.parentalmonitor.child.api import android.content.Context import com.parentalmonitor.child.BuildConfig import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.parentalmonitor.child.models.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.* import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody import java.io.File import java.io.IOException import java.util.concurrent.TimeUnit class ApiClient(private val context: Context) { private val gson = Gson() private val jsonMediaType = "application/json; charset=utf-8".toMediaType() private val prefs = context.getSharedPreferences("parental_monitor", Context.MODE_PRIVATE) private val client = OkHttpClient.Builder() .connectTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS) .build() var baseUrl: String get() = prefs.getString("api_base_url", null) ?: BuildConfig.API_BASE_URL.trimEnd('/') set(value) = prefs.edit().putString("api_base_url", value.trimEnd('/')).apply() var accessToken: String? get() = prefs.getString("access_token", null) set(value) = prefs.edit().putString("access_token", value).apply() var refreshToken: String? get() = prefs.getString("refresh_token", null) set(value) = prefs.edit().putString("refresh_token", value).apply() var deviceId: Int get() = prefs.getInt("device_id", -1) set(value) = prefs.edit().putInt("device_id", value).apply() var userId: Int get() = prefs.getInt("user_id", -1) set(value) = prefs.edit().putInt("user_id", value).apply() var userName: String? get() = prefs.getString("user_name", null) set(value) = prefs.edit().putString("user_name", value).apply() var userEmail: String? get() = prefs.getString("user_email", null) set(value) = prefs.edit().putString("user_email", value).apply() val isLoggedIn: Boolean get() = accessToken != null val isDeviceLinked: Boolean get() = deviceId > 0 fun clearSession() { prefs.edit() .remove("access_token") .remove("refresh_token") .remove("device_id") .remove("user_id") .remove("user_name") .remove("user_email") .apply() } private fun buildRequest(endpoint: String, method: String = "GET", body: Any? = null): Request { val url = "${baseUrl}/api/$endpoint" val builder = Request.Builder().url(url) accessToken?.let { builder.addHeader("Authorization", "Bearer $it") } if (deviceId > 0) { builder.addHeader("X-Device-ID", deviceId.toString()) } when (method) { "POST" -> { val jsonBody = if (body != null) gson.toJson(body) else "{}" builder.post(jsonBody.toRequestBody(jsonMediaType)) } "PUT" -> { val jsonBody = if (body != null) gson.toJson(body) else "{}" builder.put(jsonBody.toRequestBody(jsonMediaType)) } "DELETE" -> builder.delete() } return builder.build() } private suspend fun execute(request: Request, typeToken: TypeToken>): ApiResponse { return withContext(Dispatchers.IO) { try { val response = client.newCall(request).execute() val responseBody = response.body?.string() ?: "{}" if (response.code == 401 && responseBody.contains("expired")) { val refreshed = refreshAccessToken() if (refreshed) { val newRequest = request.newBuilder() .header("Authorization", "Bearer $accessToken") .build() val retryResponse = client.newCall(newRequest).execute() val retryBody = retryResponse.body?.string() ?: "{}" return@withContext gson.fromJson(retryBody, typeToken.type) as ApiResponse } } gson.fromJson(responseBody, typeToken.type) as ApiResponse } catch (e: IOException) { ApiResponse(false, "Network error: ${e.message}", null) } catch (e: Exception) { ApiResponse(false, "Error: ${e.message}", null) } } } private suspend fun refreshAccessToken(): Boolean { val token = refreshToken ?: return false return withContext(Dispatchers.IO) { try { val request = buildRequest("auth/refresh", "POST", RefreshTokenRequest(token)) val response = client.newCall(request).execute() val body = response.body?.string() ?: return@withContext false val type = object : TypeToken>() {}.type val result: ApiResponse = gson.fromJson(body, type) if (result.success && result.data != null) { accessToken = result.data.accessToken refreshToken = result.data.refreshToken true } else false } catch (e: Exception) { false } } } // Auth suspend fun login(email: String, password: String): ApiResponse { val request = buildRequest("auth/login", "POST", LoginRequest(email, password)) return execute(request, object : TypeToken>() {}) } // Pair Code suspend fun verifyPairCode( pairCode: String, deviceName: String, deviceModel: String, androidVersion: String, appVersion: String ): ApiResponse { val body = VerifyPairCodeRequest(pairCode, deviceName, deviceModel, androidVersion, appVersion) val request = buildRequest("devices/verify-pair-code", "POST", body) return execute(request, object : TypeToken>() {}) } // Check Approval suspend fun checkApproval(deviceId: Int): ApiResponse { val request = buildRequest("devices/check-approval?device_id=$deviceId") return execute(request, object : TypeToken>() {}) } // Sync suspend fun syncDevice(syncData: SyncRequest): ApiResponse { val request = buildRequest("devices/sync", "POST", syncData) return execute(request, object : TypeToken>() {}) } // Update Permissions suspend fun updatePermissions(permissions: PermissionsData): ApiResponse { val request = buildRequest("monitoring/permissions", "POST", permissions) return execute(request, object : TypeToken>() {}) } // Update Command Status suspend fun updateCommandStatus(commandId: Int, status: String, result: String? = null): ApiResponse { val body = CommandStatusUpdate(commandId, status, result) val request = buildRequest("commands/status", "POST", body) return execute(request, object : TypeToken>() {}) } // Upload Screenshot suspend fun uploadScreenshot(file: File): ApiResponse { return withContext(Dispatchers.IO) { try { val requestBody = MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file_type", "screenshot") .addFormDataPart("device_id", deviceId.toString()) .addFormDataPart( "file", file.name, RequestBody.create("image/png".toMediaType(), file) ) .build() val request = Request.Builder() .url("${baseUrl}/api/uploads/upload") .post(requestBody) .apply { accessToken?.let { addHeader("Authorization", "Bearer $it") } if (deviceId > 0) addHeader("X-Device-ID", deviceId.toString()) } .build() val response = client.newCall(request).execute() val body = response.body?.string() ?: "{}" gson.fromJson(body, object : TypeToken>() {}.type) } catch (e: Exception) { ApiResponse(false, "Upload error: ${e.message}", null) } } } // Upload media file (audio, video, camera photo) suspend fun uploadMedia(file: File, fileType: String, mimeType: String): ApiResponse { return withContext(Dispatchers.IO) { try { val requestBody = MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file_type", fileType) .addFormDataPart("device_id", deviceId.toString()) .addFormDataPart( "file", file.name, RequestBody.create(mimeType.toMediaType(), file) ) .build() val request = Request.Builder() .url("${baseUrl}/api/uploads/upload") .post(requestBody) .apply { accessToken?.let { addHeader("Authorization", "Bearer $it") } if (deviceId > 0) addHeader("X-Device-ID", deviceId.toString()) } .build() val response = client.newCall(request).execute() val body = response.body?.string() ?: "{}" gson.fromJson(body, object : TypeToken>() {}.type) } catch (e: Exception) { ApiResponse(false, "Upload error: ${e.message}", null) } } } // Sync contacts suspend fun syncContacts(contacts: List>): ApiResponse { val body = mapOf("contacts" to contacts) val request = buildRequest("monitoring/contacts", "POST", body) return execute(request, object : TypeToken>() {}) } }