package com.aletheia.app.ui.home

import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.aletheia.app.AletheiaApplication
import com.aletheia.app.R
import com.aletheia.app.data.Result
import com.aletheia.app.databinding.FragmentHomeBinding
import com.aletheia.app.model.Book
import com.aletheia.app.model.QBooksBook
import com.aletheia.app.ui.books.BooksAdapter
import com.aletheia.app.ui.books.QBooksShelfAdapter
import com.aletheia.app.ui.main.MainActivity
import com.aletheia.app.ui.qbooks.BookDetailActivity
import com.aletheia.app.ui.reader.ReaderActivity
import com.aletheia.app.util.setBookCover
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt

class HomeFragment : Fragment() {

    private var _binding: FragmentHomeBinding? = null
    private val binding get() = _binding!!
    private val app by lazy { requireActivity().application as AletheiaApplication }

    private lateinit var catalogAdapter: QBooksShelfAdapter
    private lateinit var downloadedAdapter: BooksAdapter
    private var bannerBooks: List<QBooksBook> = emptyList()
    private var catalogJob: Job? = null
    private val coverJobs = mutableListOf<Job>()
    private val localMetadataJobs = mutableListOf<Job>()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, state: Bundle?): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, state: Bundle?) {
        super.onViewCreated(view, state)

        catalogAdapter = QBooksShelfAdapter(::openBookDetails, ::openSearch)
        binding.catalogRecycler.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
        binding.catalogRecycler.adapter = catalogAdapter

        downloadedAdapter = BooksAdapter(
            onBookClicked = ::openBook,
            onBookLongPressed = { openMyBooks() },
            onAddBookClicked = ::openMyBooks,
            includeAddItem = false
        )
        binding.downloadedRecycler.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
        binding.downloadedRecycler.adapter = downloadedAdapter

        binding.homeSearch.setOnClickListener { openSearch() }
        binding.catalogAll.setOnClickListener { openSearch() }
        binding.downloadedAll.setOnClickListener { openMyBooks() }
        listOf(binding.chipGenres, binding.chipRecommendations, binding.chipBooks, binding.chipAuthors).forEach { chip ->
            chip.setOnClickListener { openSearch() }
        }
        binding.homeBannerFirst.setOnClickListener { openBanner(0) }
        binding.homeBannerSecond.setOnClickListener { openBanner(1) }
        binding.homeBannerThird.setOnClickListener { openBanner(2) }
        binding.homeBannerScroll.post {
            binding.homeBannerScroll.scrollTo(dp(BANNER_INITIAL_OFFSET_DP), 0)
        }
    }

    override fun onResume() {
        super.onResume()
        loadLocalBooks()
        loadCatalog()
    }

    override fun onDestroyView() {
        catalogJob?.cancel()
        coverJobs.forEach { it.cancel() }
        coverJobs.clear()
        localMetadataJobs.forEach { it.cancel() }
        localMetadataJobs.clear()
        _binding = null
        super.onDestroyView()
    }

    private fun loadLocalBooks() {
        viewLifecycleOwner.lifecycleScope.launch {
            val books = withContext(Dispatchers.IO) { app.bookRepository.getAllBooks() }
            if (_binding == null) return@launch
            downloadedAdapter.submitList(books.take(HOME_BOOK_LIMIT))
            binding.downloadedEmpty.visibility = if (books.isEmpty()) View.VISIBLE else View.GONE
            binding.downloadedRecycler.visibility = if (books.isEmpty()) View.GONE else View.VISIBLE
            enrichLocalBooks(books.take(HOME_BOOK_LIMIT))
        }
    }

    private fun enrichLocalBooks(books: List<Book>) {
        localMetadataJobs.forEach { it.cancel() }
        localMetadataJobs.clear()
        books.forEach { book ->
            localMetadataJobs += viewLifecycleOwner.lifecycleScope.launch {
                val enriched = app.qBooksService.enrichLocalBook(book)
                if (enriched == book || _binding == null) return@launch
                withContext(Dispatchers.IO) { app.bookRepository.updateBook(enriched) }
                downloadedAdapter.updateBook(enriched)
            }
        }
    }

    private fun loadCatalog() {
        catalogJob?.cancel()
        coverJobs.forEach { it.cancel() }
        coverJobs.clear()
        catalogJob = viewLifecycleOwner.lifecycleScope.launch {
            val cached = app.qBooksService.getCachedCatalogPage(pageSize = CATALOG_LIMIT)
            if (_binding == null) return@launch
            val cachedBooks = cached?.page?.books.orEmpty()
            if (cachedBooks.isNotEmpty()) {
                catalogAdapter.submitBooks(cachedBooks)
                updateBanners(cachedBooks)
                loadCovers(cachedBooks)
            } else {
                catalogAdapter.submitStatus(
                    getString(R.string.qbooks_home_loading_title),
                    getString(R.string.qbooks_home_loading_subtitle)
                )
            }
            if (cached?.isFresh(HOME_CACHE_MAX_AGE_MS) == true) return@launch

            val result = withContext(Dispatchers.IO) { app.qBooksService.getBooks(pageSize = CATALOG_LIMIT) }
            if (_binding == null) return@launch
            when (result) {
                is Result.Success -> if (result.value.isEmpty()) {
                    catalogAdapter.submitStatus(
                        getString(R.string.qbooks_home_empty_title),
                        getString(R.string.qbooks_home_empty_subtitle)
                    )
                } else {
                    catalogAdapter.submitBooks(result.value)
                    updateBanners(result.value)
                    loadCovers(result.value)
                }
                is Result.Failure -> if (cachedBooks.isEmpty()) {
                    catalogAdapter.submitStatus(
                        getString(R.string.qbooks_home_error_title),
                        getString(R.string.qbooks_home_error_subtitle)
                    )
                }
            }
        }
    }

    private fun loadCovers(books: List<QBooksBook>) {
        books.forEach { book ->
            coverJobs += viewLifecycleOwner.lifecycleScope.launch {
                var enriched = app.qBooksService.enrichBookMetadata(book)
                if (_binding != null && enriched != book) {
                    catalogAdapter.updateBook(enriched)
                    updateBannerBook(enriched)
                }
                val cover = if (enriched.coverImage == null && !enriched.coverUrl.isNullOrBlank()) {
                    app.qBooksService.fetchCoverImage(enriched)
                } else {
                    enriched.coverImage
                }
                if (cover != null && _binding != null) {
                    enriched = enriched.copy(coverImage = cover)
                    catalogAdapter.updateBook(enriched)
                    updateBannerBook(enriched)
                }
            }
        }
    }

    private fun updateBanners(books: List<QBooksBook>) {
        if (_binding == null) return
        val candidates = books.take(BANNER_CANDIDATE_COUNT).sortedBy { book -> book.title.length }.take(BANNER_COUNT)
        bannerBooks = when (candidates.size) {
            3 -> listOf(candidates[1], candidates[0], candidates[2])
            2 -> listOf(candidates[1], candidates[0])
            else -> candidates
        }
        val covers = listOf(binding.homeBannerCover1, binding.homeBannerCover2, binding.homeBannerCover3)
        val titles = listOf(binding.homeBannerTitle1, binding.homeBannerTitle2, binding.homeBannerTitle3)
        val subtitles = listOf(binding.homeBannerSubtitle1, binding.homeBannerSubtitle2, binding.homeBannerSubtitle3)
        bannerBooks.forEachIndexed { index, book ->
            covers[index].setBookCover(book.coverImage, R.drawable.default_cover)
            titles[index].text = book.title
            subtitles[index].text = book.author
        }
    }

    private fun updateBannerBook(book: QBooksBook) {
        if (bannerBooks.none { it.id == book.id }) return
        updateBanners(bannerBooks.map { current -> if (current.id == book.id) book else current })
    }

    private fun openBanner(index: Int) {
        bannerBooks.getOrNull(index)?.let(::openBookDetails) ?: openSearch()
    }

    private fun openBook(book: Book) {
        startActivity(Intent(requireContext(), ReaderActivity::class.java).putExtra(ReaderActivity.EXTRA_BOOK_ID, book.id))
    }

    private fun openBookDetails(book: QBooksBook) {
        startActivity(BookDetailActivity.createIntent(requireContext(), book))
    }

    private fun openSearch() {
        (activity as? MainActivity)?.selectTab(R.id.nav_search)
    }

    private fun openMyBooks() {
        (activity as? MainActivity)?.selectTab(R.id.nav_my_books)
    }

    private fun dp(value: Int): Int = (value * resources.displayMetrics.density).roundToInt()
    private fun dp(value: Float): Int = (value * resources.displayMetrics.density).roundToInt()

    companion object {
        private const val CATALOG_LIMIT = 12
        private const val HOME_BOOK_LIMIT = 8
        private const val BANNER_COUNT = 3
        private const val BANNER_CANDIDATE_COUNT = 8
        private const val BANNER_INITIAL_OFFSET_DP = 332.5f
        private const val HOME_CACHE_MAX_AGE_MS = 15 * 60 * 1000L
        fun newInstance() = HomeFragment()
    }
}
