package cy.hexor.furumi.data.repository

import cy.hexor.furumi.data.local.AuthSessionStorage
import cy.hexor.furumi.data.local.ConnectedDeviceStorage
import cy.hexor.furumi.data.remote.AppClientInfo
import cy.hexor.furumi.data.remote.AuthApiErrorParser
import cy.hexor.furumi.data.remote.PlayerEndpoints
import cy.hexor.furumi.data.remote.api.PlayerApi
import cy.hexor.furumi.data.remote.model.AddPlaylistTracksRequest
import cy.hexor.furumi.data.remote.model.ConnectedCommandPayloadBody
import cy.hexor.furumi.data.remote.model.CreatePlaylistRequest
import cy.hexor.furumi.data.remote.model.DeviceActiveRequest
import cy.hexor.furumi.data.remote.model.DeviceCommandRequest
import cy.hexor.furumi.data.remote.model.DevicePollRequest
import cy.hexor.furumi.data.remote.model.JamCreateRequest
import cy.hexor.furumi.data.remote.model.JamInviteRequest
import cy.hexor.furumi.data.remote.model.JamJoinRequest
import cy.hexor.furumi.data.remote.model.RecordHistoryRequest
import cy.hexor.furumi.data.remote.model.toDomain
import cy.hexor.furumi.data.remote.model.toBody
import cy.hexor.furumi.data.remote.model.toCommandPayloadBody
import cy.hexor.furumi.data.remote.model.toTrackItemResponse
import cy.hexor.furumi.domain.model.ArtistDetail
import cy.hexor.furumi.domain.model.ArtistPage
import cy.hexor.furumi.domain.model.AuthException
import cy.hexor.furumi.domain.model.ConnectedCommand
import cy.hexor.furumi.domain.model.ConnectedDevicesState
import cy.hexor.furumi.domain.model.ConnectedJamUser
import cy.hexor.furumi.domain.model.ConnectedPlaybackState
import cy.hexor.furumi.domain.model.ListeningHistoryPage
import cy.hexor.furumi.domain.model.PlaylistCard
import cy.hexor.furumi.domain.model.PlaylistDetail
import cy.hexor.furumi.domain.model.ReleaseDetail
import cy.hexor.furumi.domain.model.SearchResults
import cy.hexor.furumi.domain.model.TrackCard
import cy.hexor.furumi.domain.repository.PlayerRepository
import kotlinx.coroutines.CancellationException
import javax.inject.Inject

class PlayerRepositoryImpl @Inject constructor(
    private val playerApi: PlayerApi,
    private val sessionStorage: AuthSessionStorage,
    private val connectedDeviceStorage: ConnectedDeviceStorage,
    private val appClientInfo: AppClientInfo,
    private val playerEndpoints: PlayerEndpoints,
    private val errorParser: AuthApiErrorParser
) : PlayerRepository {

    override suspend fun getArtists(page: Int, limit: Int, mine: Boolean): Result<ArtistPage> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.artists(
                url = playerEndpoints.artists(baseUrl),
                page = page,
                limit = limit,
                mine = mine
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Artists response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun getArtistDetail(artistId: Long): Result<ArtistDetail> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.artistDetail(
                url = playerEndpoints.artistDetail(baseUrl, artistId)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Artist response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun getReleaseDetail(releaseId: Long): Result<ReleaseDetail> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.releaseDetail(
                url = playerEndpoints.releaseDetail(baseUrl, releaseId)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Release response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun search(query: String, limit: Int): Result<SearchResults> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.search(
                url = playerEndpoints.search(baseUrl),
                query = query,
                limit = limit
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Search response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun getListeningHistory(page: Int, limit: Int): Result<ListeningHistoryPage> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.history(
                url = playerEndpoints.history(baseUrl),
                page = page,
                limit = limit
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Listening history response is empty")
            Result.success(body.toDomain())
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun toggleLike(trackId: Long): Result<Boolean> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.toggleLike(
                url = playerEndpoints.likesToggle(baseUrl, trackId)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Like toggle response is empty")
            Result.success(body.liked)
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun getLikedTrackIds(): Result<Set<Long>> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.likes(
                url = playerEndpoints.likes(baseUrl)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Likes response is empty")
            Result.success(body.trackIds.toSet())
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun recordListeningHistory(
        trackId: Long,
        startedAt: Long,
        durationListened: Int,
        completed: Boolean
    ): Result<Unit> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.recordHistory(
                url = playerEndpoints.recordHistory(baseUrl),
                body = RecordHistoryRequest(
                    trackId = trackId,
                    startedAt = startedAt,
                    durationListened = durationListened,
                    completed = completed
                )
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            Result.success(Unit)
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun getPlaylists(): Result<List<PlaylistCard>> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.playlists(
                url = playerEndpoints.playlists(baseUrl)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Playlists response is empty")
            Result.success(body.toDomain())
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun getPlaylistDetail(playlistId: Long): Result<PlaylistDetail> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.playlistDetail(
                url = playerEndpoints.playlistDetail(baseUrl, playlistId)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Playlist response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun createPlaylist(title: String): Result<PlaylistCard> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.createPlaylist(
                url = playerEndpoints.playlists(baseUrl),
                body = CreatePlaylistRequest(title = title)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Create playlist response is empty")
            Result.success(body.toDomain())
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun deletePlaylist(playlistId: Long): Result<Unit> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.deletePlaylist(
                url = playerEndpoints.playlistDetail(baseUrl, playlistId)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            Result.success(Unit)
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun addTracksToPlaylist(playlistId: Long, trackIds: List<Long>): Result<Unit> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.addTracksToPlaylist(
                url = playerEndpoints.addPlaylistTracks(baseUrl, playlistId),
                body = AddPlaylistTracksRequest(trackIds = trackIds)
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            Result.success(Unit)
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun shareTrack(trackId: Long, title: String): Result<String> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            Result.success(playerEndpoints.shareTrack(baseUrl, trackId))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun pollConnectedDevice(
        playbackState: ConnectedPlaybackState?,
        currentJamId: String?
    ): Result<Pair<ConnectedDevicesState, List<ConnectedCommand>>> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.pollDevice(
                url = playerEndpoints.devicesPoll(baseUrl),
                body = DevicePollRequest(
                    deviceId = connectedDeviceStorage.getOrCreateDeviceId(),
                    userAgent = appClientInfo.userAgent,
                    currentJamId = currentJamId,
                    playbackState = playbackState?.toBody()
                )
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Device poll response is empty")
            Result.success(body.toDomain(baseUrl) to body.commands.map { it.toDomain(baseUrl) })
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun setActiveDevice(deviceId: String): Result<ConnectedDevicesState> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.setActiveDevice(
                url = playerEndpoints.devicesActive(baseUrl),
                body = DeviceActiveRequest(
                    deviceId = deviceId,
                    currentDeviceId = connectedDeviceStorage.getOrCreateDeviceId()
                )
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Active device response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun sendConnectedDeviceCommand(
        targetDeviceId: String?,
        jamId: String?,
        command: String,
        payload: ConnectedPlaybackState?,
        tracks: List<TrackCard>,
        index: Int?,
        time: Double?,
        volume: Double?,
        shuffle: Boolean?,
        repeatMode: String?,
        fromIndex: Int?,
        toIndex: Int?
    ): Result<Unit> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val commandPayload = payload?.toCommandPayloadBody() ?: ConnectedCommandPayloadBody(
                tracks = tracks.takeIf { it.isNotEmpty() }?.map { it.toTrackItemResponse() },
                index = index,
                time = time,
                volume = volume,
                shuffle = shuffle,
                repeatMode = repeatMode,
                fromIndex = fromIndex,
                toIndex = toIndex
            )
            val response = playerApi.sendDeviceCommand(
                url = playerEndpoints.devicesCommand(baseUrl),
                body = DeviceCommandRequest(
                    targetDeviceId = targetDeviceId,
                    jamId = jamId,
                    command = command,
                    payload = commandPayload
                )
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            Result.success(Unit)
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun createJam(): Result<ConnectedDevicesState> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.createJam(
                url = playerEndpoints.jams(baseUrl),
                body = JamCreateRequest(deviceId = connectedDeviceStorage.getOrCreateDeviceId())
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Jam response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun joinJam(jamId: String): Result<ConnectedDevicesState> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.joinJam(
                url = playerEndpoints.jamsJoin(baseUrl),
                body = JamJoinRequest(
                    jamId = jamId,
                    deviceId = connectedDeviceStorage.getOrCreateDeviceId()
                )
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Jam response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun leaveJam(jamId: String): Result<ConnectedDevicesState> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.leaveJam(
                url = playerEndpoints.jamsLeave(baseUrl),
                body = JamJoinRequest(
                    jamId = jamId,
                    deviceId = connectedDeviceStorage.getOrCreateDeviceId()
                )
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Jam response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun searchJamUsers(query: String, limit: Int): Result<List<ConnectedJamUser>> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.searchJamUsers(
                url = playerEndpoints.jamsUsers(baseUrl),
                query = query,
                limit = limit
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Jam users response is empty")
            Result.success(body.map { it.toDomain() })
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    override suspend fun inviteToJam(jamId: String, inviteeUserIds: List<Long>): Result<ConnectedDevicesState> {
        return try {
            val baseUrl = sessionStorage.getBaseUrl()
                ?: throw AuthException("Server URL is missing")
            val response = playerApi.inviteToJam(
                url = playerEndpoints.jamsInvite(baseUrl),
                body = JamInviteRequest(
                    jamId = jamId,
                    deviceId = connectedDeviceStorage.getOrCreateDeviceId(),
                    inviteeUserIds = inviteeUserIds
                )
            )

            if (!response.isSuccessful) {
                throw AuthException(errorParser.messageFrom(response))
            }

            val body = response.body() ?: throw AuthException("Jam response is empty")
            Result.success(body.toDomain(baseUrl))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}
