package cy.hexor.furumi.data.remote.model

import cy.hexor.furumi.domain.model.ConnectedCommand
import cy.hexor.furumi.domain.model.ConnectedCommandPayload
import cy.hexor.furumi.domain.model.ConnectedDevice
import cy.hexor.furumi.domain.model.ConnectedDevicesState
import cy.hexor.furumi.domain.model.ConnectedJam
import cy.hexor.furumi.domain.model.ConnectedJamMember
import cy.hexor.furumi.domain.model.ConnectedJamUser
import cy.hexor.furumi.domain.model.ConnectedPlaybackState
import cy.hexor.furumi.domain.model.TrackCard
import com.squareup.moshi.Json

data class ConnectedPlaybackStateBody(
    @param:Json(name = "track") val track: TrackItemResponse? = null,
    @param:Json(name = "tracks") val tracks: List<TrackItemResponse> = emptyList(),
    @param:Json(name = "index") val index: Int = 0,
    @param:Json(name = "position_seconds") val positionSeconds: Double = 0.0,
    @param:Json(name = "duration_seconds") val durationSeconds: Double = 0.0,
    @param:Json(name = "paused") val paused: Boolean = true,
    @param:Json(name = "shuffle") val shuffle: Boolean = false,
    @param:Json(name = "repeat_mode") val repeatMode: String = "off",
    @param:Json(name = "volume") val volume: Double = 1.0
)

data class DevicePollRequest(
    @param:Json(name = "device_id") val deviceId: String,
    @param:Json(name = "user_agent") val userAgent: String,
    @param:Json(name = "current_jam_id") val currentJamId: String?,
    @param:Json(name = "playback_state") val playbackState: ConnectedPlaybackStateBody?
)

data class DevicePollResponse(
    @param:Json(name = "device_id") val deviceId: String,
    @param:Json(name = "active_device_id") val activeDeviceId: String? = null,
    @param:Json(name = "devices") val devices: List<ConnectedDeviceResponse> = emptyList(),
    @param:Json(name = "jams") val jams: List<JamResponse> = emptyList(),
    @param:Json(name = "current_jam_id") val currentJamId: String? = null,
    @param:Json(name = "commands") val commands: List<ConnectedCommandResponse> = emptyList(),
    @param:Json(name = "playback_state") val playbackState: ConnectedPlaybackStateBody? = null
)

data class ConnectedDeviceResponse(
    @param:Json(name = "id") val id: String,
    @param:Json(name = "name") val name: String,
    @param:Json(name = "kind") val kind: String,
    @param:Json(name = "is_current") val isCurrent: Boolean = false,
    @param:Json(name = "is_active") val isActive: Boolean = false,
    @param:Json(name = "last_seen_ms") val lastSeenMs: Long = 0L
)

data class JamResponse(
    @param:Json(name = "id") val id: String,
    @param:Json(name = "name") val name: String,
    @param:Json(name = "host_user_id") val hostUserId: Long,
    @param:Json(name = "host_name") val hostName: String,
    @param:Json(name = "is_owner") val isOwner: Boolean = false,
    @param:Json(name = "is_member") val isMember: Boolean = false,
    @param:Json(name = "is_pending") val isPending: Boolean = false,
    @param:Json(name = "is_active") val isActive: Boolean = false,
    @param:Json(name = "member_count") val memberCount: Long = 0L,
    @param:Json(name = "host_last_seen_ms") val hostLastSeenMs: Long = 0L,
    @param:Json(name = "host_device_online") val hostDeviceOnline: Boolean = false,
    @param:Json(name = "members") val members: List<JamMemberResponse> = emptyList()
)

data class JamMemberResponse(
    @param:Json(name = "user_id") val userId: Long,
    @param:Json(name = "name") val name: String,
    @param:Json(name = "is_joined") val isJoined: Boolean = false,
    @param:Json(name = "is_current_user") val isCurrentUser: Boolean = false,
    @param:Json(name = "last_seen_ms") val lastSeenMs: Long = 0L
)

data class JamUserResponse(
    @param:Json(name = "id") val id: Long,
    @param:Json(name = "username") val username: String,
    @param:Json(name = "display_name") val displayName: String? = null,
    @param:Json(name = "email") val email: String? = null
)

data class ConnectedCommandResponse(
    @param:Json(name = "id") val id: String? = null,
    @param:Json(name = "command") val command: String,
    @param:Json(name = "payload") val payload: ConnectedCommandPayloadResponse = ConnectedCommandPayloadResponse()
)

data class ConnectedCommandPayloadResponse(
    @param:Json(name = "track") val track: TrackItemResponse? = null,
    @param:Json(name = "tracks") val tracks: List<TrackItemResponse> = emptyList(),
    @param:Json(name = "index") val index: Int? = null,
    @param:Json(name = "position_seconds") val positionSeconds: Double? = null,
    @param:Json(name = "duration_seconds") val durationSeconds: Double? = null,
    @param:Json(name = "paused") val paused: Boolean? = null,
    @param:Json(name = "shuffle") val shuffle: Boolean? = null,
    @param:Json(name = "repeat_mode") val repeatMode: String? = null,
    @param:Json(name = "volume") val volume: Double? = null,
    @param:Json(name = "time") val time: Double? = null,
    @param:Json(name = "from_index") val fromIndex: Int? = null,
    @param:Json(name = "to_index") val toIndex: Int? = null
)

data class DeviceActiveRequest(
    @param:Json(name = "device_id") val deviceId: String,
    @param:Json(name = "current_device_id") val currentDeviceId: String
)

data class DeviceCommandRequest(
    @param:Json(name = "target_device_id") val targetDeviceId: String?,
    @param:Json(name = "jam_id") val jamId: String?,
    @param:Json(name = "command") val command: String,
    @param:Json(name = "payload") val payload: ConnectedCommandPayloadBody = ConnectedCommandPayloadBody()
)

data class JamCreateRequest(
    @param:Json(name = "device_id") val deviceId: String,
    @param:Json(name = "invitee_user_ids") val inviteeUserIds: List<Long> = emptyList()
)

data class JamJoinRequest(
    @param:Json(name = "jam_id") val jamId: String,
    @param:Json(name = "device_id") val deviceId: String
)

data class JamInviteRequest(
    @param:Json(name = "jam_id") val jamId: String,
    @param:Json(name = "device_id") val deviceId: String,
    @param:Json(name = "invitee_user_ids") val inviteeUserIds: List<Long> = emptyList()
)

data class ConnectedCommandPayloadBody(
    @param:Json(name = "track") val track: TrackItemResponse? = null,
    @param:Json(name = "tracks") val tracks: List<TrackItemResponse>? = null,
    @param:Json(name = "index") val index: Int? = null,
    @param:Json(name = "position_seconds") val positionSeconds: Double? = null,
    @param:Json(name = "duration_seconds") val durationSeconds: Double? = null,
    @param:Json(name = "paused") val paused: Boolean? = null,
    @param:Json(name = "shuffle") val shuffle: Boolean? = null,
    @param:Json(name = "repeat_mode") val repeatMode: String? = null,
    @param:Json(name = "volume") val volume: Double? = null,
    @param:Json(name = "time") val time: Double? = null,
    @param:Json(name = "from_index") val fromIndex: Int? = null,
    @param:Json(name = "to_index") val toIndex: Int? = null
)

fun ConnectedPlaybackState.toBody(): ConnectedPlaybackStateBody {
    return ConnectedPlaybackStateBody(
        track = track?.toTrackItemResponse(),
        tracks = tracks.map { it.toTrackItemResponse() },
        index = index,
        positionSeconds = positionSeconds,
        durationSeconds = durationSeconds,
        paused = paused,
        shuffle = shuffle,
        repeatMode = repeatMode,
        volume = volume
    )
}

fun ConnectedPlaybackState.toCommandPayloadBody(): ConnectedCommandPayloadBody {
    return ConnectedCommandPayloadBody(
        track = track?.toTrackItemResponse(),
        tracks = tracks.map { it.toTrackItemResponse() },
        index = index,
        positionSeconds = positionSeconds,
        durationSeconds = durationSeconds,
        paused = paused,
        shuffle = shuffle,
        repeatMode = repeatMode,
        volume = volume
    )
}

fun DevicePollResponse.toDomain(baseUrl: String): ConnectedDevicesState {
    return ConnectedDevicesState(
        deviceId = deviceId,
        activeDeviceId = activeDeviceId,
        devices = devices.map { it.toDomain() },
        jams = jams.map { it.toDomain() },
        currentJamId = currentJamId,
        remotePlaybackState = playbackState?.toDomain(baseUrl)
    )
}

private fun JamResponse.toDomain(): ConnectedJam {
    return ConnectedJam(
        id = id,
        name = name,
        hostUserId = hostUserId,
        hostName = hostName,
        isOwner = isOwner,
        isMember = isMember,
        isPending = isPending,
        isActive = isActive,
        memberCount = memberCount,
        hostLastSeenMs = hostLastSeenMs,
        hostDeviceOnline = hostDeviceOnline,
        members = members.map { it.toDomain() }
    )
}

private fun JamMemberResponse.toDomain(): ConnectedJamMember {
    return ConnectedJamMember(
        userId = userId,
        name = name,
        isJoined = isJoined,
        isCurrentUser = isCurrentUser,
        lastSeenMs = lastSeenMs
    )
}

fun JamUserResponse.toDomain(): ConnectedJamUser {
    return ConnectedJamUser(
        id = id,
        username = username,
        displayName = displayName,
        email = email
    )
}

fun ConnectedCommandResponse.toDomain(baseUrl: String): ConnectedCommand {
    return ConnectedCommand(
        id = id,
        command = command,
        payload = payload.toDomain(baseUrl)
    )
}

private fun ConnectedDeviceResponse.toDomain(): ConnectedDevice {
    return ConnectedDevice(
        id = id,
        name = name,
        kind = kind,
        isCurrent = isCurrent,
        isActive = isActive,
        lastSeenMs = lastSeenMs
    )
}

private fun ConnectedCommandPayloadResponse.toDomain(baseUrl: String): ConnectedCommandPayload {
    val playbackState = toPlaybackState(baseUrl)
    return ConnectedCommandPayload(
        playbackState = playbackState,
        tracks = tracks.map { it.toDomain(baseUrl) },
        index = index,
        time = time,
        volume = volume,
        shuffle = shuffle,
        repeatMode = repeatMode,
        fromIndex = fromIndex,
        toIndex = toIndex
    )
}

private fun ConnectedCommandPayloadResponse.toPlaybackState(baseUrl: String): ConnectedPlaybackState? {
    val playbackTracks = tracks.map { it.toDomain(baseUrl) }
    val playbackTrack = track?.toDomain(baseUrl) ?: playbackTracks.getOrNull(index ?: 0)
    if (playbackTrack == null && playbackTracks.isEmpty()) return null

    return ConnectedPlaybackState(
        track = playbackTrack,
        tracks = playbackTracks.ifEmpty { playbackTrack?.let(::listOf).orEmpty() },
        index = index ?: playbackTracks.indexOfFirst { it.id == playbackTrack?.id }.coerceAtLeast(0),
        positionSeconds = positionSeconds ?: time ?: 0.0,
        durationSeconds = durationSeconds ?: playbackTrack?.durationSeconds?.toDouble() ?: 0.0,
        paused = paused ?: false,
        shuffle = shuffle ?: false,
        repeatMode = repeatMode ?: "off",
        volume = volume ?: 1.0
    )
}

private fun ConnectedPlaybackStateBody.toDomain(baseUrl: String): ConnectedPlaybackState {
    val mappedTracks = tracks.map { it.toDomain(baseUrl) }
    return ConnectedPlaybackState(
        track = track?.toDomain(baseUrl) ?: mappedTracks.getOrNull(index),
        tracks = mappedTracks,
        index = index,
        positionSeconds = positionSeconds,
        durationSeconds = durationSeconds,
        paused = paused,
        shuffle = shuffle,
        repeatMode = repeatMode,
        volume = volume
    )
}

fun AudioPlaybackStateSnapshot.toDomain(): ConnectedPlaybackState {
    return ConnectedPlaybackState(
        track = track,
        tracks = tracks,
        index = index,
        positionSeconds = positionSeconds,
        durationSeconds = durationSeconds,
        paused = paused,
        shuffle = shuffle,
        repeatMode = repeatMode,
        volume = volume
    )
}

data class AudioPlaybackStateSnapshot(
    val track: TrackCard?,
    val tracks: List<TrackCard>,
    val index: Int,
    val positionSeconds: Double,
    val durationSeconds: Double,
    val paused: Boolean,
    val shuffle: Boolean,
    val repeatMode: String,
    val volume: Double
)
