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

import cy.hexor.furumi.domain.model.ArtistCard
import cy.hexor.furumi.domain.model.ArtistDetail
import cy.hexor.furumi.domain.model.ArtistPage
import cy.hexor.furumi.domain.model.ArtistRef
import cy.hexor.furumi.domain.model.ListeningHistoryItem
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.ReleaseCard
import cy.hexor.furumi.domain.model.ReleaseDetail
import cy.hexor.furumi.domain.model.SearchResults
import cy.hexor.furumi.domain.model.TrackCard
import com.squareup.moshi.Json

data class ArtistRefResponse(
    @param:Json(name = "id") val id: Long? = null,
    @param:Json(name = "name") val name: String
)

data class TrackItemResponse(
    @param:Json(name = "id") val id: Long? = null,
    @param:Json(name = "title") val title: String,
    @param:Json(name = "track_number") val trackNumber: Int? = null,
    @param:Json(name = "disc_number") val discNumber: Int? = null,
    @param:Json(name = "duration_seconds") val durationSeconds: Double? = null,
    @param:Json(name = "artists") val artists: List<ArtistRefResponse> = emptyList(),
    @param:Json(name = "featured_artists") val featuredArtists: List<ArtistRefResponse> = emptyList(),
    @param:Json(name = "release_id") val releaseId: Long? = null,
    @param:Json(name = "release_title") val releaseTitle: String? = null,
    @param:Json(name = "release_year") val releaseYear: Int? = null,
    @param:Json(name = "cover_url") val coverUrl: String? = null,
    @param:Json(name = "stream_url") val streamUrl: String? = null
)

data class TracksByIdsRequest(
    @param:Json(name = "ids") val ids: List<Long>
)

data class OfflineManifestResponse(
    @param:Json(name = "generated_at") val generatedAt: String,
    @param:Json(name = "tracks") val tracks: List<OfflineManifestTrackResponse> = emptyList(),
    @param:Json(name = "playlists") val playlists: List<OfflineManifestPlaylistResponse> = emptyList(),
    @param:Json(name = "liked_track_ids") val likedTrackIds: List<Long> = emptyList(),
    @param:Json(name = "followed_artist_ids") val followedArtistIds: List<Long> = emptyList()
)

data class OfflineManifestTrackResponse(
    @param:Json(name = "id") val id: Long,
    @param:Json(name = "updated_at") val updatedAt: String? = null,
    @param:Json(name = "stream_url") val streamUrl: String,
    @param:Json(name = "audio_file_id") val audioFileId: Long? = null,
    @param:Json(name = "audio_hash") val audioHash: String? = null,
    @param:Json(name = "audio_size_bytes") val audioSizeBytes: Long? = null,
    @param:Json(name = "audio_mime_type") val audioMimeType: String? = null,
    @param:Json(name = "audio_updated_at") val audioUpdatedAt: String? = null,
    @param:Json(name = "cover_file_id") val coverFileId: Long? = null,
    @param:Json(name = "cover_url") val coverUrl: String? = null,
    @param:Json(name = "cover_hash") val coverHash: String? = null,
    @param:Json(name = "cover_updated_at") val coverUpdatedAt: String? = null
)

data class OfflineManifestPlaylistResponse(
    @param:Json(name = "id") val id: Long,
    @param:Json(name = "title") val title: String,
    @param:Json(name = "description") val description: String? = null,
    @param:Json(name = "updated_at") val updatedAt: String? = null,
    @param:Json(name = "is_own") val isOwn: Boolean = false,
    @param:Json(name = "owner_name") val ownerName: String? = null,
    @param:Json(name = "is_public") val isPublic: Boolean = false,
    @param:Json(name = "is_saved") val isSaved: Boolean = false,
    @param:Json(name = "kind") val kind: String? = null,
    @param:Json(name = "track_ids") val trackIds: List<Long> = emptyList()
)

data class PlayHistoryItemResponse(
    @param:Json(name = "id") val id: Long,
    @param:Json(name = "track_id") val trackId: Long,
    @param:Json(name = "track_title") val trackTitle: String,
    @param:Json(name = "release_title") val releaseTitle: String? = null,
    @param:Json(name = "track") val track: TrackItemResponse,
    @param:Json(name = "played_at") val playedAt: String,
    @param:Json(name = "duration_listened") val durationListened: Double? = null,
    @param:Json(name = "completed") val completed: Boolean
)


data class PlayHistoryPageResponse(
    @param:Json(name = "items") val items: List<PlayHistoryItemResponse>,
    @param:Json(name = "total") val total: Long,
    @param:Json(name = "page") val page: Int,
    @param:Json(name = "per_page") val perPage: Int
)

data class ArtistCardResponse(
    @param:Json(name = "id") val id: Long,
    @param:Json(name = "name") val name: String,
    @param:Json(name = "image_url") val imageUrl: String? = null,
    @param:Json(name = "release_count") val releaseCount: Int = 0,
    @param:Json(name = "track_count") val trackCount: Int = 0
)

data class ArtistPageResponse(
    @param:Json(name = "items") val items: List<ArtistCardResponse>,
    @param:Json(name = "total") val total: Long,
    @param:Json(name = "page") val page: Int,
    @param:Json(name = "per_page") val perPage: Int,
    @param:Json(name = "has_more") val hasMore: Boolean
)

data class ReleaseCardResponse(
    @param:Json(name = "id") val id: Long,
    @param:Json(name = "title") val title: String,
    @param:Json(name = "release_type") val releaseType: String? = null,
    @param:Json(name = "year") val year: Int? = null,
    @param:Json(name = "cover_url") val coverUrl: String? = null,
    @param:Json(name = "track_count") val trackCount: Int = 0
)

data class ArtistDetailResponse(
    @param:Json(name = "artist") val artist: ArtistCardResponse? = null,
    @param:Json(name = "id") val id: Long? = null,
    @param:Json(name = "name") val name: String? = null,
    @param:Json(name = "image_url") val imageUrl: String? = null,
    @param:Json(name = "release_count") val releaseCount: Int? = null,
    @param:Json(name = "track_count") val trackCount: Int? = null,
    @param:Json(name = "top_tracks") val topTracks: List<TrackItemResponse> = emptyList(),
    @param:Json(name = "releases") val releases: List<ReleaseCardResponse> = emptyList(),
    @param:Json(name = "featured_tracks") val featuredTracks: List<TrackItemResponse> = emptyList()
)

data class SearchResponse(
    @param:Json(name = "artists") val artists: List<ArtistCardResponse> = emptyList(),
    @param:Json(name = "releases") val releases: List<ReleaseCardResponse> = emptyList(),
    @param:Json(name = "tracks") val tracks: List<TrackItemResponse> = emptyList()
)

data class LikeToggleRequest(
    @param:Json(name = "track_id") val trackId: Long
)

data class LikeToggleResponse(
    @param:Json(name = "liked") val liked: Boolean
)

data class LikedTrackIdsResponse(
    @param:Json(name = "track_ids") val trackIds: List<Long>
)

data class RecordHistoryRequest(
    @param:Json(name = "track_id") val trackId: Long,
    @param:Json(name = "started_at") val startedAt: Long,
    @param:Json(name = "duration_listened") val durationListened: Int,
    @param:Json(name = "completed") val completed: Boolean
)

data class PlaylistCardResponse(
    @param:Json(name = "id") val id: Long,
    @param:Json(name = "title") val title: String,
    @param:Json(name = "track_count") val trackCount: Int = 0,
    @param:Json(name = "is_own") val isOwn: Boolean = false,
    @param:Json(name = "owner_name") val ownerName: String? = null,
    @param:Json(name = "is_public") val isPublic: Boolean = false,
    @param:Json(name = "is_saved") val isSaved: Boolean = false,
    @param:Json(name = "kind") val kind: String? = null
)

data class PlaylistListResponse(
    @param:Json(name = "playlists") val playlists: List<PlaylistCardResponse>
)

data class CreatePlaylistRequest(
    @param:Json(name = "title") val title: String
)

data class AddPlaylistTracksRequest(
    @param:Json(name = "track_ids") val trackIds: List<Long>
)

data class SharePlaylistRequest(
    @param:Json(name = "track_ids") val trackIds: List<Long>,
    @param:Json(name = "title") val title: String
)

data class SharePlaylistResponse(
    @param:Json(name = "token") val token: String,
    @param:Json(name = "url") val url: String
)

data class ReleaseDetailResponse(
    @param:Json(name = "release") val release: ReleaseCardResponse? = null,
    @param:Json(name = "id") val id: Long? = null,
    @param:Json(name = "title") val title: String? = null,
    @param:Json(name = "release_type") val releaseType: String? = null,
    @param:Json(name = "year") val year: Int? = null,
    @param:Json(name = "cover_url") val coverUrl: String? = null,
    @param:Json(name = "track_count") val trackCount: Int? = null,
    @param:Json(name = "artists") val artists: List<ArtistCardResponse> = emptyList(),
    @param:Json(name = "tracks") val tracks: List<TrackItemResponse> = emptyList()
)

data class PlaylistDetailResponse(
    @param:Json(name = "playlist") val playlist: PlaylistCardResponse? = null,
    @param:Json(name = "id") val id: Long? = null,
    @param:Json(name = "title") val title: String? = null,
    @param:Json(name = "description") val description: String? = null,
    @param:Json(name = "track_count") val trackCount: Int? = null,
    @param:Json(name = "is_own") val isOwn: Boolean? = null,
    @param:Json(name = "owner_name") val ownerName: String? = null,
    @param:Json(name = "is_public") val isPublic: Boolean? = null,
    @param:Json(name = "is_saved") val isSaved: Boolean? = null,
    @param:Json(name = "kind") val kind: String? = null,
    @param:Json(name = "tracks") val tracks: List<TrackItemResponse> = emptyList()
)

fun PlayHistoryPageResponse.toDomain(): ListeningHistoryPage {
    return ListeningHistoryPage(
        items = items.map { it.toDomain() },
        total = total,
        page = page,
        perPage = perPage
    )
}

private fun PlayHistoryItemResponse.toDomain(): ListeningHistoryItem {
    val artistNames = track.artists
        .ifEmpty { track.featuredArtists }
        .map { it.name }

    return ListeningHistoryItem(
        id = id,
        trackId = trackId,
        title = trackTitle.ifBlank { track.title },
        artists = artistNames,
        releaseTitle = releaseTitle ?: track.releaseTitle,
        playedAt = playedAt,
        durationListenedSeconds = durationListened?.toInt(),
        completed = completed
    )

}

fun ArtistPageResponse.toDomain(baseUrl: String): ArtistPage {
    return ArtistPage(
        items = items.map { it.toDomain(baseUrl) },
        total = total,
        page = page,
        perPage = perPage,
        hasMore = hasMore
    )
}

fun ArtistDetailResponse.toDomain(baseUrl: String): ArtistDetail {
    val mappedReleases = releases.map { it.toDomain(baseUrl) }
    val mappedTopTracks = topTracks.map { it.toDomain(baseUrl) }
    val mappedFeaturedTracks = featuredTracks.map { it.toDomain(baseUrl) }
    val mappedArtist = artist?.toDomain(baseUrl) ?: ArtistCard(
        id = id ?: 0L,
        name = name ?: "Unknown artist",
        imageUrl = imageUrl.toAbsoluteMediaUrl(baseUrl),
        releaseCount = releaseCount ?: mappedReleases.size,
        trackCount = trackCount ?: (mappedTopTracks.size + mappedFeaturedTracks.size)
    )

    return ArtistDetail(
        artist = mappedArtist,
        releases = mappedReleases,
        topTracks = mappedTopTracks,
        featuredTracks = mappedFeaturedTracks
    )
}

fun ReleaseDetailResponse.toDomain(baseUrl: String): ReleaseDetail {
    val mappedTracks = tracks.map { it.toDomain(baseUrl) }
    val mappedRelease = release?.toDomain(baseUrl) ?: ReleaseCard(
        id = id ?: 0L,
        title = title ?: "Unknown release",
        releaseType = releaseType,
        year = year,
        coverUrl = coverUrl.toAbsoluteMediaUrl(baseUrl),
        trackCount = trackCount ?: mappedTracks.size
    )

    return ReleaseDetail(
        release = mappedRelease,
        artists = artists.map { it.toDomain(baseUrl) },
        tracks = mappedTracks
    )
}

fun SearchResponse.toDomain(baseUrl: String): SearchResults {
    val trackArtists = tracks
        .flatMap { track -> track.artists.ifEmpty { track.featuredArtists } }
        .distinctBy { it.id ?: it.name.hashCode().toLong() }
        .map { artist ->
            ArtistCard(
                id = artist.id ?: artist.name.hashCode().toLong(),
                name = artist.name,
                imageUrl = null,
                releaseCount = 0,
                trackCount = 0
            )
        }

    return SearchResults(
        artistMatches = artists.map { it.toDomain(baseUrl) },
        trackArtists = trackArtists,
        tracks = tracks.map { it.toDomain(baseUrl) }
    )
}

private fun ArtistCardResponse.toDomain(baseUrl: String): ArtistCard {
    return ArtistCard(
        id = id,
        name = name,
        imageUrl = imageUrl.toAbsoluteMediaUrl(baseUrl),
        releaseCount = releaseCount,
        trackCount = trackCount
    )
}

private fun ReleaseCardResponse.toDomain(baseUrl: String): ReleaseCard {
    return ReleaseCard(
        id = id,
        title = title,
        releaseType = releaseType,
        year = year,
        coverUrl = coverUrl.toAbsoluteMediaUrl(baseUrl),
        trackCount = trackCount
    )
}

fun TrackItemResponse.toDomain(baseUrl: String): TrackCard {
    val mappedArtists = artists.map { it.toDomain() }
    val mappedFeaturedArtists = featuredArtists.map { it.toDomain() }
    val artistNames = mappedArtists
        .ifEmpty { mappedFeaturedArtists }
        .map { it.name }

    val absoluteCoverUrl = coverUrl.toAbsoluteMediaUrl(baseUrl)
    val absoluteStreamUrl = streamUrl.toAbsoluteMediaUrl(baseUrl).orEmpty()

    return TrackCard(
        id = id ?: 0L,
        title = title,
        trackNumber = trackNumber,
        discNumber = discNumber,
        durationSeconds = durationSeconds?.toInt() ?: 0,
        artists = artistNames,
        artistRefs = mappedArtists,
        featuredArtistRefs = mappedFeaturedArtists,
        releaseId = releaseId,
        releaseYear = releaseYear,
        releaseTitle = releaseTitle,
        coverUrl = absoluteCoverUrl,
        streamUrl = absoluteStreamUrl,
        remoteCoverUrl = absoluteCoverUrl,
        remoteStreamUrl = absoluteStreamUrl
    )
}

fun TrackCard.toTrackItemResponse(): TrackItemResponse {
    val primaryArtists = artistRefs.ifEmpty {
        artists.mapIndexed { index, name ->
            ArtistRef(id = -(index + 1L), name = name)
        }
    }

    return TrackItemResponse(
        id = id,
        title = title,
        trackNumber = trackNumber,
        discNumber = discNumber,
        durationSeconds = durationSeconds?.toDouble() ?: 0.0,
        artists = primaryArtists.map { it.toResponse() },
        featuredArtists = featuredArtistRefs.map { it.toResponse() },
        releaseId = releaseId,
        releaseTitle = releaseTitle,
        releaseYear = releaseYear,
        coverUrl = remoteCoverUrl ?: coverUrl,
        streamUrl = remoteStreamUrl.ifBlank { streamUrl }
    )
}

fun OfflineManifestTrackResponse.withAbsoluteUrls(baseUrl: String): OfflineManifestTrackResponse {
    return copy(
        streamUrl = streamUrl.toAbsoluteMediaUrl(baseUrl).orEmpty(),
        coverUrl = coverUrl.toAbsoluteMediaUrl(baseUrl)
    )
}

fun TrackCard.withOfflineManifest(manifest: OfflineManifestTrackResponse): TrackCard {
    return copy(
        coverUrl = manifest.coverUrl ?: coverUrl,
        streamUrl = manifest.streamUrl.ifBlank { streamUrl },
        remoteCoverUrl = manifest.coverUrl ?: remoteCoverUrl,
        remoteStreamUrl = manifest.streamUrl.ifBlank { remoteStreamUrl },
        metadataUpdatedAt = manifest.updatedAt ?: metadataUpdatedAt,
        audioHash = manifest.audioHash ?: audioHash,
        audioSizeBytes = manifest.audioSizeBytes ?: audioSizeBytes,
        audioMimeType = manifest.audioMimeType ?: audioMimeType,
        audioUpdatedAt = manifest.audioUpdatedAt ?: audioUpdatedAt,
        coverHash = manifest.coverHash ?: coverHash,
        coverUpdatedAt = manifest.coverUpdatedAt ?: coverUpdatedAt
    )
}

private fun ArtistRefResponse.toDomain(): ArtistRef {
    return ArtistRef(
        id = id ?: 0L,
        name = name
    )
}

private fun ArtistRef.toResponse(): ArtistRefResponse {
    return ArtistRefResponse(
        id = id,
        name = name
    )
}

fun PlaylistListResponse.toDomain(): List<PlaylistCard> {
    return playlists.map {
        PlaylistCard(
            id = it.id,
            title = it.title,
            trackCount = it.trackCount,
            isPublic = it.isPublic,
            isOwn = it.isOwn,
            ownerName = it.ownerName,
            isSaved = it.isSaved,
            kind = it.kind
        )
    }
}

fun List<PlaylistCardResponse>.toDomain(): List<PlaylistCard> {
    return map { it.toDomain() }
}

fun PlaylistDetailResponse.toDomain(baseUrl: String): PlaylistDetail {
    val mappedTracks = tracks.map { it.toDomain(baseUrl) }
    val mappedPlaylist = playlist?.toDomain() ?: PlaylistCard(
        id = id ?: 0L,
        title = title ?: "Untitled playlist",
        trackCount = trackCount ?: mappedTracks.size,
        isPublic = isPublic ?: false,
        isOwn = isOwn ?: false,
        ownerName = ownerName,
        isSaved = isSaved ?: false,
        kind = kind
    )

    return PlaylistDetail(
        playlist = mappedPlaylist,
        description = description,
        tracks = mappedTracks
    )
}

fun PlaylistCardResponse.toDomain(): PlaylistCard {
    return PlaylistCard(
        id = id,
        title = title,
        trackCount = trackCount,
        isPublic = isPublic,
        isOwn = isOwn,
        ownerName = ownerName,
        isSaved = isSaved,
        kind = kind
    )
}

private fun String?.toAbsoluteMediaUrl(baseUrl: String): String? {
    val value = this?.trim()?.takeIf { it.isNotBlank() } ?: return null
    if (value.startsWith("http://", ignoreCase = true) ||
        value.startsWith("https://", ignoreCase = true)
    ) {
        return value
    }

    val normalizedBaseUrl = baseUrl.trimEnd('/')
    val normalizedPath = if (value.startsWith('/')) value else "/$value"
    return "$normalizedBaseUrl$normalizedPath"
}
