package com.aletheia.app.ui.books

import android.content.Context
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
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.FragmentBookshelfBinding
import com.aletheia.app.model.Book
import com.aletheia.app.model.QBooksBook
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.BookSharing
import com.aletheia.app.util.setBookCover
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt

class BookshelfFragment : Fragment() {

    private var _binding: FragmentBookshelfBinding? = null
    private val binding get() = _binding!!

    private val app by lazy { requireActivity().application as AletheiaApplication }
    private lateinit var booksAdapter: BooksAdapter
    private lateinit var qBooksShelfAdapter: QBooksShelfAdapter
    private var currentBooks: List<Book> = emptyList()
    private var visibleBooks: List<Book> = emptyList()
    private var continueReadingBook: Book? = null
    private val metadataJobs = mutableListOf<Job>()
    private var loadBooksJob: Job? = null
    private var libraryFilter = LibraryFilter.ALL

    private val importBookLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
        if (uri == null) {
            return@registerForActivityResult
        }

        lifecycleScope.launch(Dispatchers.IO) {
            runCatching {
                requireContext().contentResolver.takePersistableUriPermission(
                    uri,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION
                )
            }
            val result = runCatching { app.bookRepository.importBook(uri) }
            withContext(Dispatchers.Main) {
                result.onFailure { exception ->
                    showMessage(getString(R.string.dialog_add_book_failed, exception.readableMessage()))
                }
                loadBooks()
            }
        }
    }

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

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

        booksAdapter = BooksAdapter(
            onBookClicked = ::openBook,
            onAddBookClicked = ::launchImport,
            onBookLongPressed = ::confirmDeleteBook,
            onBookActionsClicked = ::showBookActions
        )
        binding.booksRecycler.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
        binding.booksRecycler.addItemDecoration(BookshelfRailDecoration(requireContext(), dp(12), dp(8), dp(8)))
        binding.booksRecycler.adapter = booksAdapter

        qBooksShelfAdapter = QBooksShelfAdapter(
            onBookClicked = ::openQBooksBookPreview,
            onStatusClicked = ::openQBooks
        )
        binding.qbooksHomeRecycler.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
        binding.qbooksHomeRecycler.addItemDecoration(BookshelfRailDecoration(requireContext(), dp(12), dp(8), dp(8)))
        binding.qbooksHomeRecycler.adapter = qBooksShelfAdapter
        binding.qbooksHomeSection.visibility = View.GONE
        qBooksShelfAdapter.submitStatus(
            getString(R.string.qbooks_home_loading_title),
            getString(R.string.qbooks_home_loading_subtitle)
        )

        binding.booksSearchInput.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                renderFilteredBooks()
            }
            override fun afterTextChanged(s: Editable?) = Unit
        })

        binding.headerSearchButton.setOnClickListener {
            binding.booksSearchInput.requestFocus()
        }
        binding.localShelfAllButton.setOnClickListener {
            binding.booksRecycler.smoothScrollToPosition(0)
        }
        binding.qbooksAllButton.setOnClickListener {
            openQBooks()
        }
        binding.continueOpenButton.setOnClickListener {
            continueReadingBook?.let(::openBook)
        }
        binding.clearLibrarySearchButton.setOnClickListener {
            binding.booksSearchInput.setText("")
        }
        binding.libraryFilterGroup.setOnCheckedStateChangeListener { _, checkedIds ->
            libraryFilter = when (checkedIds.firstOrNull()) {
                R.id.library_filter_reading -> LibraryFilter.READING
                R.id.library_filter_downloaded -> LibraryFilter.DOWNLOADED
                R.id.library_filter_finished -> LibraryFilter.FINISHED
                else -> LibraryFilter.ALL
            }
            renderFilteredBooks()
        }
        binding.emptyLibraryCard.setOnClickListener { launchImport() }
        parentFragmentManager.setFragmentResultListener(
            REQUEST_REFRESH_LIBRARY,
            viewLifecycleOwner
        ) { _, _ ->
            loadBooks()
        }
    }

    override fun onResume() {
        super.onResume()
        loadBooks()
    }

    override fun onHiddenChanged(hidden: Boolean) {
        super.onHiddenChanged(hidden)
        if (!hidden && _binding != null) {
            loadBooks()
        }
    }

    override fun onDestroyView() {
        loadBooksJob?.cancel()
        loadBooksJob = null
        metadataJobs.forEach { it.cancel() }
        metadataJobs.clear()
        super.onDestroyView()
        _binding = null
    }

    private fun loadBooks() {
        loadBooksJob?.cancel()
        loadBooksJob = viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
            val result = runCatching { app.bookRepository.getAllBooks() }
            withContext(Dispatchers.Main) {
                if (_binding == null) {
                    return@withContext
                }

                result.onSuccess { books ->
                    currentBooks = books
                    renderFilteredBooks()
                    enrichMissingMetadata(books.take(METADATA_PREFETCH_LIMIT))
                }.onFailure { exception ->
                    showMessage(getString(R.string.dialog_load_books_failed, exception.readableMessage()))
                }
            }
        }
    }

    private fun enrichMissingMetadata(books: List<Book>) {
        metadataJobs.forEach { it.cancel() }
        metadataJobs.clear()
        books.forEach { book ->
            metadataJobs += viewLifecycleOwner.lifecycleScope.launch {
                val enriched = app.qBooksService.enrichLocalBook(book)
                if (enriched == book || _binding == null) return@launch
                withContext(Dispatchers.IO) { app.bookRepository.updateBook(enriched) }
                currentBooks = currentBooks.map { current ->
                    if (current.id == enriched.id) enriched else current
                }
                renderFilteredBooks()
            }
        }
    }

    private fun renderFilteredBooks() {
        val query = binding.booksSearchInput.text?.toString().orEmpty().trim()
        visibleBooks = filterBooks(currentBooks, query)
        booksAdapter.submitList(visibleBooks)
        renderLibraryState(
            allBooks = currentBooks,
            displayedBooks = visibleBooks,
            query = query
        )
    }

    private fun renderLibraryState(allBooks: List<Book>, displayedBooks: List<Book>, query: String) {
        val isFiltered = query.isNotBlank() || libraryFilter != LibraryFilter.ALL
        continueReadingBook = if (isFiltered) {
            null
        } else {
            allBooks
                .filter { it.isInProgress }
                .maxByOrNull { it.lastRead }
                ?: allBooks.maxByOrNull { it.lastRead }
        }

        val isLibraryEmpty = allBooks.isEmpty()
        val isSearchEmpty = !isLibraryEmpty && displayedBooks.isEmpty()
        val hasVisibleBooks = displayedBooks.isNotEmpty()
        binding.emptyLibraryCard.visibility = if (isLibraryEmpty) View.VISIBLE else View.GONE
        binding.emptySearchCard.visibility = if (isSearchEmpty) View.VISIBLE else View.GONE
        binding.bookshelfPanel.visibility = if (hasVisibleBooks) View.VISIBLE else View.GONE
        binding.booksRecycler.visibility = if (hasVisibleBooks) View.VISIBLE else View.GONE
        binding.continueReadingCard.visibility =
            if (continueReadingBook != null) View.VISIBLE else View.GONE

        if (isSearchEmpty) {
            binding.emptySearchDetailText.text = if (query.isNotBlank()) {
                getString(R.string.status_empty_library_search_subtitle, query)
            } else {
                getString(R.string.library_filter_empty)
            }
        }

        continueReadingBook?.let { book ->
            binding.continueCover.setBookCover(book.coverImage, R.drawable.default_cover)
            binding.continueTitle.text = book.title
            binding.continueAuthor.text = book.author
            binding.continueProgress.text = book.progressText
        }
    }

    private fun loadQBooksPreview() {
        if (!::qBooksShelfAdapter.isInitialized) {
            return
        }

        qBooksShelfAdapter.submitStatus(
            getString(R.string.qbooks_home_loading_title),
            getString(R.string.qbooks_home_loading_subtitle)
        )
        lifecycleScope.launch {
            val result = withContext(Dispatchers.IO) {
                app.qBooksService.getBooks(pageSize = QBOOKS_PREVIEW_SIZE)
            }

            if (_binding == null) {
                return@launch
            }

            when (result) {
                is Result.Success -> {
                    if (result.value.isEmpty()) {
                        qBooksShelfAdapter.submitStatus(
                            getString(R.string.qbooks_home_empty_title),
                            getString(R.string.qbooks_home_empty_subtitle)
                        )
                    } else {
                        qBooksShelfAdapter.submitBooks(result.value)
                    }
                }
                is Result.Failure -> {
                    val notConfigured = result.message.contains("Адрес QBooks не задан")
                    qBooksShelfAdapter.submitStatus(
                        getString(
                            if (notConfigured) {
                                R.string.qbooks_home_not_ready_title
                            } else {
                                R.string.qbooks_home_error_title
                            }
                        ),
                        getString(
                            if (notConfigured) {
                                R.string.qbooks_home_not_ready_subtitle
                            } else {
                                R.string.qbooks_home_error_subtitle
                            }
                        )
                    )
                }
            }
        }
    }

    private fun filterBooks(books: List<Book>, query: String): List<Book> {
        val search = query.lowercase().trim()
        return books.filter { book ->
            val matchesSearch = search.isBlank() ||
                book.title.lowercase().contains(search) ||
                book.author.lowercase().contains(search)
            val matchesFilter = when (libraryFilter) {
                LibraryFilter.ALL,
                LibraryFilter.DOWNLOADED -> true
                LibraryFilter.READING -> book.isInProgress
                LibraryFilter.FINISHED -> book.isCompleted
            }
            matchesSearch && matchesFilter
        }
    }

    private enum class LibraryFilter {
        ALL,
        READING,
        DOWNLOADED,
        FINISHED
    }

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

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

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

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

    private fun launchImport() {
        importBookLauncher.launch(arrayOf("application/epub+zip", "application/x-fictionbook+xml", "application/xml", "text/xml"))
    }

    private fun showBookActions(anchor: View, book: Book) {
        PopupMenu(requireContext(), anchor).apply {
            inflate(R.menu.book_actions_menu)
            setOnMenuItemClickListener { item ->
                when (item.itemId) {
                    R.id.action_share_book -> {
                        shareBook(book)
                        true
                    }
                    R.id.action_delete_book -> {
                        confirmDeleteBook(book)
                        true
                    }
                    else -> false
                }
            }
            show()
        }
    }

    private fun shareBook(book: Book) {
        runCatching { BookSharing.shareDownloadedBook(requireContext(), book) }
            .onFailure { exception ->
                showMessage(getString(R.string.dialog_share_book_failed, exception.readableMessage()))
            }
    }

    private fun confirmDeleteBook(book: Book) {
        MaterialAlertDialogBuilder(requireContext())
            .setTitle(R.string.dialog_delete_book_title)
            .setMessage(getString(R.string.dialog_delete_book_message, book.title))
            .setPositiveButton(R.string.action_delete) { _, _ -> deleteBook(book) }
            .setNegativeButton(R.string.action_cancel, null)
            .show()
    }

    private fun deleteBook(book: Book) {
        lifecycleScope.launch(Dispatchers.IO) {
            val result = runCatching { app.bookRepository.deleteBook(book) }
            withContext(Dispatchers.Main) {
                if (_binding == null) {
                    return@withContext
                }

                result.onSuccess {
                    loadBooks()
                }.onFailure { exception ->
                    showMessage(getString(R.string.dialog_delete_book_failed, exception.readableMessage()))
                }
            }
        }
    }

    private fun showMessage(message: String) {
        MaterialAlertDialogBuilder(requireContext())
            .setTitle(R.string.dialog_error_title)
            .setMessage(message)
            .setPositiveButton(R.string.action_ok, null)
            .show()
    }

    private fun Throwable.readableMessage(): String = message ?: javaClass.simpleName

    companion object {
        const val REQUEST_REFRESH_LIBRARY = "refresh_my_books_library"
        private const val QBOOKS_PREVIEW_SIZE = 8
        private const val METADATA_PREFETCH_LIMIT = 8

        fun newInstance() = BookshelfFragment()
    }

    private class BookshelfRailDecoration(
        context: Context,
        private val spacing: Int,
        private val shelfGap: Int,
        private val shelfHeight: Int
    ) : RecyclerView.ItemDecoration() {
        private val shelfPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = ContextCompat.getColor(context, R.color.night_wood)
        }
        private val shelfShadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
            color = ContextCompat.getColor(context, R.color.night_wood_dark)
            alpha = 140
        }

        override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
            val position = parent.getChildAdapterPosition(view)
            if (position == RecyclerView.NO_POSITION) {
                return
            }

            outRect.right = if (position < state.itemCount - 1) spacing else 0
            outRect.bottom = spacing + shelfGap + shelfHeight
        }

        override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
            val left = parent.paddingLeft.toFloat()
            val maxRight = (parent.width - parent.paddingRight).toFloat()
            var lowestChildBottom = 0
            var rightMostChildEdge = 0

            for (index in 0 until parent.childCount) {
                val child = parent.getChildAt(index)
                val params = child.layoutParams as RecyclerView.LayoutParams
                lowestChildBottom = maxOf(lowestChildBottom, child.bottom + params.bottomMargin)
                rightMostChildEdge = maxOf(rightMostChildEdge, child.right + params.rightMargin)
            }

            if (lowestChildBottom == 0) {
                return
            }

            val right = (rightMostChildEdge + shelfHeight / 2f)
                .coerceAtMost(maxRight)
                .coerceAtLeast(left + shelfHeight)
            val shelfTop = lowestChildBottom + (shelfGap / 2)
            val shelfBottom = shelfTop + shelfHeight
            val shelfRadius = shelfHeight / 2f

            canvas.drawRoundRect(
                left,
                shelfTop.toFloat() + shelfHeight / 2f,
                right,
                shelfBottom.toFloat() + shelfHeight / 2f,
                shelfRadius,
                shelfRadius,
                shelfShadowPaint
            )
            canvas.drawRoundRect(
                left,
                shelfTop.toFloat(),
                right,
                shelfBottom.toFloat(),
                shelfRadius,
                shelfRadius,
                shelfPaint
            )
        }
    }
}
