package eu.kanade.tachiyomi.extension.all.webtoons

import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import java.util.ArrayList

open class WebtoonsTranslate(override val lang: String, private val translateLangCode: String, languageNameExtra: String = "") : Webtoons(lang) {
    // popularMangaRequest already returns manga sorted by latest update
    override val supportsLatest = false

    private val apiBaseUrl = HttpUrl.parse("https://global.apis.naver.com")!!
    private val mobileBaseUrl = HttpUrl.parse("https://m.webtoons.com")!!
    private val thumbnailBaseUrl = "https://mwebtoon-phinf.pstatic.net"

    private val pageListUrlPattern = "/lineWebtoon/ctrans/translatedEpisodeDetail_jsonp.json?titleNo=%s&episodeNo=%d&languageCode=%s&teamVersion=%d"

    private val pageSize = 24

    override val name = "Webtoons.com Translations$languageNameExtra"

    override fun headersBuilder(): Headers.Builder = super.headersBuilder()
        .removeAll("Referer")
        .add("Referer", mobileBaseUrl.toString())

    private fun mangaRequest(page: Int, requeztSize: Int): Request {
        val url = apiBaseUrl
            .resolve("/lineWebtoon/ctrans/translatedWebtoons_jsonp.json")!!
            .newBuilder()
            .addQueryParameter("orderType", "UPDATE")
            .addQueryParameter("offset", "${(page - 1) * requeztSize}")
            .addQueryParameter("size", "$requeztSize")
            .addQueryParameter("languageCode", translateLangCode)
            .build()
        return GET(url.toString(), headers)
    }

    // Webtoons translations doesn't really have a "popular" sort; just "UPDATE", "TITLE_ASC",
    // and "TITLE_DESC".  Pick UPDATE as the most useful sort.
    override fun popularMangaRequest(page: Int): Request = mangaRequest(page, pageSize)

    override fun popularMangaParse(response: Response): MangasPage {
        val offset = response.request().url().queryParameter("offset")!!.toInt()
        var totalCount: Int
        val mangas = mutableListOf<SManga>()

        JSONObject(response.body()!!.string()).let { json ->
            json.getString("code").let { code ->
                if (code != "000") throw Exception("Error getting popular manga: error code $code")
            }

            json.getJSONObject("result").let { results ->
                totalCount = results.getInt("totalCount")

                results.getJSONArray("titleList").let { array ->
                    for (i in 0 until array.length()) {
                        mangas.add(mangaFromJson(array[i] as JSONObject))
                    }
                }
            }
        }

        return MangasPage(mangas, totalCount > pageSize + offset)
    }

    private fun mangaFromJson(json: JSONObject): SManga {
        val relativeThumnailURL = json.getString("thumbnailIPadUrl")
            ?: json.getString("thumbnailMobileUrl")

        return SManga.create().apply {
            title = json.getString("representTitle")
            author = json.getString("writeAuthorName")
            artist = json.getString("pictureAuthorName") ?: author
            thumbnail_url = if (relativeThumnailURL != null) "$thumbnailBaseUrl$relativeThumnailURL" else null
            status = SManga.UNKNOWN
            url = mobileBaseUrl
                .resolve("/translate/episodeList")!!
                .newBuilder()
                .addQueryParameter("titleNo", json.getInt("titleNo").toString())
                .addQueryParameter("languageCode", translateLangCode)
                .addQueryParameter("teamVersion", json.optInt("teamVersion", 0).toString())
                .build()
                .toString()
        }
    }

    override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
        return client.newCall(searchMangaRequest(page, query, filters))
            .asObservableSuccess()
            .map { response ->
                searchMangaParse(response, query)
            }
    }

    /**
     * Don't see a search function for Fan Translations, so let's do it client side.
     * There's 75 webtoons as of 2019/11/21, a hardcoded request of 200 should be a sufficient request
     * to get all titles, in 1 request, for quite a while
     */
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = mangaRequest(page, 200)

    private fun searchMangaParse(response: Response, query: String): MangasPage {
        val mangas = mutableListOf<SManga>()

        JSONObject(response.body()!!.string()).let { json ->
            json.getString("code").let { code ->
                if (code != "000") throw Exception("Error getting manga: error code $code")
            }

            json.getJSONObject("result").getJSONArray("titleList").let { array ->
                for (i in 0 until array.length()) {
                    (array[i] as JSONObject).let { jsonManga ->
                        if (jsonManga.getString("representTitle").contains(query, ignoreCase = true))
                            mangas.add(mangaFromJson(jsonManga))
                    }
                }
            }
        }

        return MangasPage(mangas, false)
    }

    override fun mangaDetailsRequest(manga: SManga): Request {
        return GET(manga.url, headers)
    }

    override fun mangaDetailsParse(document: Document): SManga {
        val getMetaProp = fun(property: String): String =
            document.head().select("meta[property=\"$property\"]").attr("content")
        var parsedAuthor = getMetaProp("com-linewebtoon:webtoon:author")
        var parsedArtist = parsedAuthor
        val authorSplit = parsedAuthor.split(" / ", limit = 2)
        if (authorSplit.count() > 1) {
            parsedAuthor = authorSplit[0]
            parsedArtist = authorSplit[1]
        }

        return SManga.create().apply {
            title = getMetaProp("og:title")
            artist = parsedArtist
            author = parsedAuthor
            description = getMetaProp("og:description")
            status = SManga.UNKNOWN
            thumbnail_url = getMetaProp("og:image")
        }
    }

    override fun chapterListSelector(): String = throw Exception("Not used")

    override fun chapterFromElement(element: Element): SChapter = throw Exception("Not used")

    override fun pageListParse(document: Document): List<Page> = throw Exception("Not used")

    override fun chapterListRequest(manga: SManga): Request {
        val titleNo = HttpUrl.parse(manga.url)!!
            .queryParameter("titleNo")
        val chapterUrl = apiBaseUrl
            .resolve("/lineWebtoon/ctrans/translatedEpisodes_jsonp.json")!!
            .newBuilder()
            .addQueryParameter("titleNo", titleNo)
            .addQueryParameter("languageCode", translateLangCode)
            .addQueryParameter("offset", "0")
            .addQueryParameter("limit", "10000")
            .toString()
        return GET(chapterUrl, mobileHeaders)
    }

    override fun chapterListParse(response: Response): List<SChapter> {
        val chapterData = response.body()!!.string()
        val chapterJson = JSONObject(chapterData)
        val responseCode = chapterJson.getString("code")
        if (responseCode != "000") {
            val message = chapterJson.optString("message", "error code $responseCode")
            throw Exception("Error getting chapter list: $message")
        }
        val results = chapterJson.getJSONObject("result").getJSONArray("episodes")
        val ret = ArrayList<SChapter>()
        for (i in 0 until results.length()) {
            val result = results.getJSONObject(i)
            if (result.getBoolean("translateCompleted")) {
                ret.add(parseChapterJson(result))
            }
        }
        ret.reverse()
        return ret
    }

    private fun parseChapterJson(obj: JSONObject) = SChapter.create().apply {
        name = obj.getString("title") + " #" + obj.getString("episodeSeq")
        chapter_number = obj.getInt("episodeSeq").toFloat()
        date_upload = obj.getLong("updateYmdt")
        scanlator = obj.getString("teamVersion")
        if (scanlator == "0") {
            scanlator = "(wiki)"
        }
        url = String.format(pageListUrlPattern, obj.getInt("titleNo"), obj.getInt("episodeNo"), obj.getString("languageCode"), obj.getInt("teamVersion"))
    }

    override fun pageListRequest(chapter: SChapter): Request {
        return GET(apiBaseUrl.resolve(chapter.url).toString(), headers)
    }

    override fun pageListParse(response: Response): List<Page> {
        val pageJson = JSONObject(response.body()!!.string())
        val results = pageJson.getJSONObject("result").getJSONArray("imageInfo")
        val ret = ArrayList<Page>()
        for (i in 0 until results.length()) {
            val result = results.getJSONObject(i)
            ret.add(Page(i, "", result.getString("imageUrl")))
        }
        return ret
    }

    override fun getFilterList(): FilterList = FilterList()
}
