package pl.firmatpp.kierowca.data

import android.content.Context
import android.net.Uri
import java.io.File
import java.security.MessageDigest
import java.util.UUID
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import pl.firmatpp.kierowca.data.api.ApiFactory
import pl.firmatpp.kierowca.data.api.MobileDriverApi
import pl.firmatpp.kierowca.data.model.BootstrapResponse
import pl.firmatpp.kierowca.data.model.BroadcastAuthBody
import pl.firmatpp.kierowca.data.model.BroadcastAuthResponse
import pl.firmatpp.kierowca.data.model.CancelLeaveRequestBody
import pl.firmatpp.kierowca.data.model.DispatchSheetUploadResponse
import pl.firmatpp.kierowca.data.model.CreateLeaveRequestBody
import pl.firmatpp.kierowca.data.model.DriverDto
import pl.firmatpp.kierowca.data.model.DriverLeaveCalendarEntryDto
import pl.firmatpp.kierowca.data.model.DriverLeaveRequestDto
import pl.firmatpp.kierowca.data.model.FinishRouteBody
import pl.firmatpp.kierowca.data.model.NotificationPreferencesBody
import pl.firmatpp.kierowca.data.model.NotificationPreferencesDto
import pl.firmatpp.kierowca.data.model.OtpResponse
import pl.firmatpp.kierowca.data.model.PhotoUploadResponse
import pl.firmatpp.kierowca.data.model.PushTokenBody
import pl.firmatpp.kierowca.data.model.RealtimeStatusBody
import pl.firmatpp.kierowca.data.model.RequestOtpBody
import pl.firmatpp.kierowca.data.model.RoutePointBatchBody
import pl.firmatpp.kierowca.data.model.RoutePointsResponse
import pl.firmatpp.kierowca.data.model.RouteResponse
import pl.firmatpp.kierowca.data.model.StartRouteBody
import pl.firmatpp.kierowca.data.model.SyncStateResponse
import pl.firmatpp.kierowca.data.model.VerifyOtpBody
import pl.firmatpp.kierowca.data.upload.DispatchSheetUploadEntity
import pl.firmatpp.kierowca.data.upload.PhotoUploadEntity

class DriverRepository(
    private val context: Context,
    private val api: MobileDriverApi = ApiFactory.mobileDriverApi(),
    private val tokenStore: TokenStore = TokenStore(context),
    private val deviceInfoProvider: DeviceInfoProvider = DeviceInfoProvider(context, tokenStore),
) {
    suspend fun requestOtp(phoneNumber: String): OtpResponse =
        api.requestOtp(RequestOtpBody(phoneNumber))

    suspend fun verifyOtp(phoneNumber: String, code: String, deviceName: String): DriverDto {
        val response = api.verifyOtp(VerifyOtpBody(phoneNumber, code, deviceName, deviceInfoProvider.currentDeviceInfo()))
        val token = response.accessToken ?: error("Brak tokenu sesji.")
        tokenStore.save(token)
        return response.driver ?: error("Brak danych kierowcy.")
    }

    suspend fun bootstrap(date: String? = null): BootstrapResponse =
        api.bootstrap(authHeader(requireToken()), date)

    suspend fun route(routeId: String): RouteResponse =
        api.route(authHeader(requireToken()), routeId)

    suspend fun leaveRequests(): List<DriverLeaveRequestDto> =
        api.leaveRequests(authHeader(requireToken())).data

    suspend fun leaveCalendar(from: String, to: String): List<DriverLeaveCalendarEntryDto> =
        api.leaveCalendar(authHeader(requireToken()), from, to).data

    suspend fun leaveRequest(id: String): DriverLeaveRequestDto =
        api.leaveRequest(authHeader(requireToken()), id).data

    suspend fun createLeaveRequest(dateFrom: String, dateTo: String, type: String, note: String?): DriverLeaveRequestDto =
        api.createLeaveRequest(authHeader(requireToken()), CreateLeaveRequestBody(dateFrom, dateTo, type, note)).data

    suspend fun cancelLeaveRequest(id: String, comment: String? = null): DriverLeaveRequestDto =
        api.cancelLeaveRequest(authHeader(requireToken()), id, CancelLeaveRequestBody(comment)).data

    suspend fun completeRoute(routeId: String) =
        api.completeRoute(authHeader(requireToken()), routeId)

    suspend fun startRoute(routeId: String, body: StartRouteBody) =
        api.startRoute(authHeader(requireToken()), routeId, body)

    suspend fun finishRoute(routeId: String, body: FinishRouteBody) =
        api.finishRoute(authHeader(requireToken()), routeId, body)

    suspend fun storeRoutePoints(routeId: String, body: RoutePointBatchBody): RoutePointsResponse =
        api.storeRoutePoints(authHeader(requireToken()), routeId, body)

    suspend fun updateNotificationPreferences(notifyNewRoutes: Boolean): NotificationPreferencesDto =
        api.updateNotificationPreferences(authHeader(requireToken()), NotificationPreferencesBody(notifyNewRoutes))

    suspend fun syncState(date: String?, routeId: String? = null): SyncStateResponse =
        api.syncState(authHeader(requireToken()), date, routeId)

    suspend fun broadcastAuth(socketId: String, channelName: String): BroadcastAuthResponse =
        api.broadcastAuth(authHeader(requireToken()), BroadcastAuthBody(socketId, channelName))

    suspend fun storePushToken(token: String) {
        api.storePushToken(authHeader(requireToken()), PushTokenBody(token))
    }

    suspend fun storeRealtimeStatus(status: String, socketId: String? = null, error: String? = null) {
        api.storeRealtimeStatus(authHeader(requireToken()), RealtimeStatusBody(status, socketId, error))
    }

    suspend fun deletePushToken() {
        val token = tokenStore.read() ?: return
        runCatching { api.deletePushToken(authHeader(token)) }
    }

    suspend fun uploadPhoto(
        routeId: String,
        uri: Uri,
        source: String,
        stage: String = "other",
        metadata: PhotoUploadMetadata = PhotoUploadMetadata(),
    ): PhotoUploadResponse {
        val resolver = context.contentResolver
        val mimeType = resolver.getType(uri) ?: "image/jpeg"
        val bytes = resolver.openInputStream(uri)?.use { it.readBytes() } ?: error("Nie mozna odczytac zdjecia.")
        val clientRequestId = UUID.randomUUID().toString()
        val contentSha256 = sha256(bytes)
        val body = bytes.toRequestBody(mimeType.toMediaTypeOrNull())
        val photo = MultipartBody.Part.createFormData("photo", "ladunek.jpg", body)
        val sourceBody = source.toRequestBody("text/plain".toMediaTypeOrNull())
        val metadataParts = metadata.toMultipartTextParts()

        return api.uploadPhoto(
            authHeader(requireToken()),
            clientRequestId,
            routeId,
            photo,
            clientRequestId.toPlainTextBody(),
            contentSha256.toPlainTextBody(),
            sourceBody,
            stage.toPlainTextBody(),
            metadataParts["takenAt"],
            metadataParts["latitude"],
            metadataParts["longitude"],
            metadataParts["locationAccuracyMeters"],
        )
    }

    suspend fun uploadQueuedPhoto(upload: PhotoUploadEntity): PhotoUploadResponse {
        val file = File(upload.localPath)
        val body = file.readBytes().toRequestBody(upload.mimeType.toMediaTypeOrNull())
        val photo = MultipartBody.Part.createFormData("photo", file.name, body)
        val metadataParts = PhotoUploadMetadata(
            takenAt = upload.takenAt,
            latitude = upload.latitude,
            longitude = upload.longitude,
            locationAccuracyMeters = upload.locationAccuracyMeters,
        ).toMultipartTextParts()

        return api.uploadPhoto(
            authHeader(requireToken()),
            upload.clientRequestId,
            upload.routeId,
            photo,
            upload.clientRequestId.toPlainTextBody(),
            upload.contentSha256.toPlainTextBody(),
            upload.source.toPlainTextBody(),
            upload.stage.toPlainTextBody(),
            metadataParts["takenAt"],
            metadataParts["latitude"],
            metadataParts["longitude"],
            metadataParts["locationAccuracyMeters"],
        )
    }

    suspend fun uploadQueuedDispatchSheetPhoto(upload: DispatchSheetUploadEntity): DispatchSheetUploadResponse {
        val file = File(upload.localPath)
        val body = file.readBytes().toRequestBody(upload.mimeType.toMediaTypeOrNull())
        val photo = MultipartBody.Part.createFormData("photo", file.name, body)
        val metadataParts = PhotoUploadMetadata(
            takenAt = upload.takenAt,
            latitude = upload.latitude,
            longitude = upload.longitude,
            locationAccuracyMeters = upload.locationAccuracyMeters,
        ).toMultipartTextParts()

        return api.uploadDispatchSheetPhoto(
            authHeader(requireToken()),
            upload.clientRequestId,
            photo,
            upload.clientRequestId.toPlainTextBody(),
            upload.contentSha256.toPlainTextBody(),
            upload.source.toPlainTextBody(),
            metadataParts["takenAt"],
            metadataParts["latitude"],
            metadataParts["longitude"],
            metadataParts["locationAccuracyMeters"],
            upload.replacePhotoId?.toPlainTextBody(),
        )
    }

    suspend fun deletePhoto(photoId: String) {
        api.deletePhoto(authHeader(requireToken()), photoId)
    }

    suspend fun logout() {
        val token = tokenStore.read()
        if (token != null) {
            runCatching { api.deletePushToken(authHeader(token)) }
            runCatching { api.logout(authHeader(token)) }
        }
        tokenStore.clear()
    }

    suspend fun hasToken(): Boolean = tokenStore.read() != null

    suspend fun imageAuthHeader(): String? = tokenStore.read()?.let(::authHeader)

    private suspend fun requireToken(): String = tokenStore.read() ?: error("Brak aktywnej sesji.")

    private fun authHeader(token: String): String = "Bearer $token"

    private fun String.toPlainTextBody(): RequestBody = toRequestBody("text/plain".toMediaTypeOrNull())

    private fun sha256(bytes: ByteArray): String =
        MessageDigest.getInstance("SHA-256")
            .digest(bytes)
            .joinToString("") { "%02x".format(it) }
}
