package exh.md.service

import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import exh.md.dto.CheckTokenDto
import exh.md.dto.LoginRequestDto
import exh.md.dto.LoginResponseDto
import exh.md.dto.LogoutDto
import exh.md.dto.MangaListDto
import exh.md.dto.RatingDto
import exh.md.dto.RatingResponseDto
import exh.md.dto.ReadChapterDto
import exh.md.dto.ReadingStatusDto
import exh.md.dto.ReadingStatusMapDto
import exh.md.dto.RefreshTokenDto
import exh.md.dto.ResultDto
import exh.md.utils.MdApi
import exh.md.utils.MdConstants
import exh.md.utils.MdUtil
import okhttp3.Authenticator
import okhttp3.CacheControl
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request

class MangaDexAuthService(
    private val client: OkHttpClient,
    private val headers: Headers,
    private val preferences: TrackPreferences,
    private val mdList: MdList,
) {
    private val noAuthenticatorClient = client.newBuilder()
        .authenticator(Authenticator.NONE)
        .build()

    fun getHeaders() = MdUtil.getAuthHeaders(
        headers,
        preferences,
        mdList,
    )

    suspend fun login(request: LoginRequestDto): LoginResponseDto {
        return noAuthenticatorClient.newCall(
            POST(
                MdApi.login,
                body = MdUtil.encodeToBody(request),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun logout(): LogoutDto {
        return client.newCall(
            POST(
                MdApi.logout,
                getHeaders(),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun checkToken(): CheckTokenDto {
        return noAuthenticatorClient.newCall(
            GET(
                MdApi.checkToken,
                getHeaders(),
                CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun refreshToken(request: RefreshTokenDto): LoginResponseDto {
        return noAuthenticatorClient.newCall(
            POST(
                MdApi.refreshToken,
                getHeaders(),
                body = MdUtil.encodeToBody(request),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun userFollowList(offset: Int): MangaListDto {
        return client.newCall(
            GET(
                "${MdApi.userFollows}?limit=100&offset=$offset&includes[]=${MdConstants.Types.coverArt}",
                getHeaders(),
                CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun readingStatusForManga(mangaId: String): ReadingStatusDto {
        return client.newCall(
            GET(
                "${MdApi.manga}/$mangaId/status",
                getHeaders(),
                CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun readChaptersForManga(mangaId: String): ReadChapterDto {
        return client.newCall(
            GET(
                "${MdApi.manga}/$mangaId/read",
                getHeaders(),
                CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun updateReadingStatusForManga(
        mangaId: String,
        readingStatusDto: ReadingStatusDto,
    ): ResultDto {
        return client.newCall(
            POST(
                "${MdApi.manga}/$mangaId/status",
                getHeaders(),
                body = MdUtil.encodeToBody(readingStatusDto),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun readingStatusAllManga(): ReadingStatusMapDto {
        return client.newCall(
            GET(
                MdApi.readingStatusForAllManga,
                getHeaders(),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun readingStatusByType(status: String): ReadingStatusMapDto {
        return client.newCall(
            GET(
                "${MdApi.readingStatusForAllManga}?status=$status",
                getHeaders(),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun markChapterRead(chapterId: String): ResultDto {
        return client.newCall(
            POST(
                "${MdApi.chapter}/$chapterId/read",
                getHeaders(),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun markChapterUnRead(chapterId: String): ResultDto {
        return client.newCall(
            Request.Builder()
                .url("${MdApi.chapter}/$chapterId/read")
                .delete()
                .headers(getHeaders())
                .cacheControl(CacheControl.FORCE_NETWORK)
                .build(),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun followManga(mangaId: String): ResultDto {
        return client.newCall(
            POST(
                "${MdApi.manga}/$mangaId/follow",
                getHeaders(),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun unfollowManga(mangaId: String): ResultDto {
        return client.newCall(
            Request.Builder()
                .url("${MdApi.manga}/$mangaId/follow")
                .delete()
                .headers(getHeaders())
                .cacheControl(CacheControl.FORCE_NETWORK)
                .build(),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun updateMangaRating(mangaId: String, rating: Int): ResultDto {
        return client.newCall(
            POST(
                "${MdApi.rating}/$mangaId",
                getHeaders(),
                body = MdUtil.encodeToBody(RatingDto(rating)),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun deleteMangaRating(mangaId: String): ResultDto {
        return client.newCall(
            Request.Builder()
                .delete()
                .url("${MdApi.rating}/$mangaId")
                .headers(getHeaders())
                .cacheControl(CacheControl.FORCE_NETWORK)
                .build(),
        ).await().parseAs(MdUtil.jsonParser)
    }

    suspend fun mangasRating(vararg mangaIds: String): RatingResponseDto {
        return client.newCall(
            GET(
                MdApi.rating.toHttpUrl()
                    .newBuilder()
                    .apply {
                        mangaIds.forEach {
                            addQueryParameter("manga[]", it)
                        }
                    }
                    .build(),
                getHeaders(),
                cache = CacheControl.FORCE_NETWORK,
            ),
        ).await().parseAs(MdUtil.jsonParser)
    }
}
