package bou.amine.apps.readerforselfossv2.android.fragments

import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.Bitmap
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.util.TypedValue
import android.view.GestureDetector
import android.view.InflateException
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.webkit.WebResourceResponse
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.ImageActivity
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
import bou.amine.apps.readerforselfossv2.android.model.toModel
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
import bou.amine.apps.readerforselfossv2.model.MercuryModel
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getImages
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.acra.ktx.sendSilentlyWithAcra
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
import java.net.MalformedURLException
import java.net.URL
import java.util.Locale
import java.util.concurrent.ExecutionException

private const val IMAGE_JPG = "image/jpg"

class ArticleFragment : Fragment(), DIAware {
    private var fontSize: Int = 16
    private lateinit var item: SelfossModel.Item
    private lateinit var url: String
    private lateinit var contentText: String
    private lateinit var contentSource: String
    private lateinit var contentImage: String
    private lateinit var contentTitle: String
    private lateinit var allImages: ArrayList<String>
    private lateinit var fab: FloatingActionButton
    private lateinit var textAlignment: String
    private lateinit var binding: FragmentArticleBinding

    override val di: DI by closestDI()
    private val repository: Repository by instance()
    private val appSettingsService: AppSettingsService by instance()

    private var typeface: Typeface? = null
    private var resId: Int = 0
    private var font = ""
    private var staticBar = false

    private val mercuryApi: MercuryApi by instance()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val pi: ParecelableItem = requireArguments().getParcelable(ARG_ITEMS)!!

        item = pi.toModel()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        try {
            binding = FragmentArticleBinding.inflate(inflater, container, false)

            try {
                url = item.getLinkDecoded()
            } catch (e: Exception) {
                e.sendSilentlyWithAcra()
            }

            contentText = item.content
            contentTitle = item.title.getHtmlDecoded()
            contentImage = item.getThumbnail(repository.baseUrl)
            contentSource = try {
                item.sourceAuthorAndDate()
            } catch (e: Exception) {
                e.sendSilentlyWithAcraWithName("Article Fragment parse date")
                item.sourceAuthorOnly()
            }
            allImages = item.getImages()

            fontSize = appSettingsService.getFontSize()
            staticBar = appSettingsService.isStaticBarEnabled()
            font = appSettingsService.getFont()

            refreshAlignment()

            fab = binding.fab

            fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))

            fab.rippleColor = resources.getColor(R.color.colorAccentDark)

            val floatingToolbar: FloatingToolbar = handleFloatingToolbar()

            if (staticBar) {
                fab.hide()
                floatingToolbar.show()
            }

            binding.source.text = contentSource
            if (typeface != null) {
                binding.source.typeface = typeface
            }

            handleContent()

            binding.nestedScrollView.setOnScrollChangeListener(
                NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
                    if (scrollY > oldScrollY) {
                        floatingToolbar.hide()
                        fab.hide()
                    } else {
                        if (staticBar) {
                            floatingToolbar.show()
                        } else {
                            if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
                        }
                    }
                },
            )
        } catch (e: InflateException) {
            e.sendSilentlyWithAcraWithName("webview not available")
            try {
                AlertDialog.Builder(requireContext())
                    .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
                    .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
                    .setPositiveButton(
                        android.R.string.ok,
                    ) { _, _ ->
                        appSettingsService.disableArticleViewer()
                        requireActivity().finish()
                    }
                    .create()
                    .show()
            } catch (e: IllegalStateException) {
                e.sendSilentlyWithAcraWithName("Context required is null")
            }
        }

        return binding.root
    }

    private fun handleContent() {
        if (contentText.isEmptyOrNullOrNullString()) {
            if (repository.isNetworkAvailable()) {
                getContentFromMercury()
            }
        } else {
            binding.titleView.text = contentTitle
            if (typeface != null) {
                binding.titleView.typeface = typeface
            }

            htmlToWebview()

            if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
                binding.imageView.visibility = View.VISIBLE
                Glide
                    .with(requireContext())
                    .asBitmap()
                    .load(contentImage)
                    .apply(RequestOptions.fitCenterTransform())
                    .into(binding.imageView)
            } else {
                binding.imageView.visibility = View.GONE
            }
        }
    }

    private fun handleFloatingToolbar(): FloatingToolbar {
        val floatingToolbar: FloatingToolbar = binding.floatingToolbar
        if (appSettingsService.getPublicAccess()) {
            floatingToolbar.setMenu(R.menu.reader_toolbar_no_read)
        }
        floatingToolbar.attachFab(fab)

        floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))

        floatingToolbar.setClickListener(
            object : FloatingToolbar.ItemClickListener {
                override fun onItemClick(item: MenuItem) {
                    when (item.itemId) {
                        R.id.share_action -> requireActivity().shareLink(url, contentTitle)
                        R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
                        R.id.unread_action ->
                            try {
                                if (this@ArticleFragment.item.unread) {
                                    CoroutineScope(Dispatchers.IO).launch {
                                        repository.markAsRead(this@ArticleFragment.item)
                                    }
                                    this@ArticleFragment.item.unread = false
                                    Toast.makeText(
                                        requireContext(),
                                        R.string.marked_as_read,
                                        Toast.LENGTH_LONG,
                                    ).show()
                                } else {
                                    CoroutineScope(Dispatchers.IO).launch {
                                        repository.unmarkAsRead(this@ArticleFragment.item)
                                    }
                                    this@ArticleFragment.item.unread = true
                                    Toast.makeText(
                                        context,
                                        R.string.marked_as_unread,
                                        Toast.LENGTH_LONG,
                                    ).show()
                                }
                            } catch (e: IllegalStateException) {
                                e.sendSilentlyWithAcraWithName("Context required is null")
                            }

                        else -> Unit
                    }
                }

                override fun onItemLongClick(item: MenuItem?) {
                    // We do nothing
                }
            },
        )
        return floatingToolbar
    }

    private fun refreshAlignment() {
        textAlignment =
            when (appSettingsService.getActiveAllignment()) {
                1 -> "justify"
                2 -> "left"
                else -> "justify"
            }
    }

    private fun getContentFromMercury() {
        binding.progressBar.visibility = View.VISIBLE

        CoroutineScope(Dispatchers.Main).launch {
            try {
                val response = mercuryApi.query(url)
                if (response.success && response.data != null) {
                    handleMercuryData(response.data!!)
                } else {
                    openInBrowserAfterFailing()
                }
            } catch (e: Exception) {
                openInBrowserAfterFailing()
            }
        }
    }

    private fun handleMercuryData(data: MercuryModel.ParsedContent) {
        if (data.error == true || data.failed == true) {
            openInBrowserAfterFailing()
        } else {
            binding.titleView.text = data.title.orEmpty()
            if (typeface != null) {
                binding.titleView.typeface = typeface
            }
            URL(data.url)
            url = data.url!!

            contentText = data.content.orEmpty()
            htmlToWebview()

            handleLeadImage(data.lead_image_url)

            binding.nestedScrollView.scrollTo(0, 0)
            binding.progressBar.visibility = View.GONE
        }
    }

    private fun handleLeadImage(lead_image_url: String?) {
        if (!lead_image_url.isNullOrEmpty() && context != null) {
            binding.imageView.visibility = View.VISIBLE
            Glide
                .with(requireContext())
                .asBitmap()
                .load(
                    lead_image_url,
                )
                .apply(RequestOptions.fitCenterTransform())
                .into(binding.imageView)
        } else {
            binding.imageView.visibility = View.GONE
        }
    }

    private fun handleImageLoading() {
        binding.webcontent.webViewClient =
            object : WebViewClient() {
                @Deprecated("Deprecated in Java")
                override fun shouldOverrideUrlLoading(
                    view: WebView?,
                    url: String,
                ): Boolean {
                    return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
                        requireContext().openUrlInBrowser(url)
                        true
                    } else {
                        false
                    }
                }

                @Deprecated("Deprecated in Java")
                override fun shouldInterceptRequest(
                    view: WebView,
                    url: String,
                ): WebResourceResponse? {
                    val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
                    if (url.lowercase(Locale.US).contains(".jpg") ||
                        url.lowercase(Locale.US)
                            .contains(".jpeg")
                    ) {
                        try {
                            val image =
                                Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
                                    .get()
                            return WebResourceResponse(
                                IMAGE_JPG,
                                "UTF-8",
                                getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
                            )
                        } catch (e: ExecutionException) {
                            // Do nothing
                        }
                    } else if (url.lowercase(Locale.US).contains(".png")) {
                        try {
                            val image =
                                Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
                                    .get()
                            return WebResourceResponse(
                                IMAGE_JPG,
                                "UTF-8",
                                getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
                            )
                        } catch (e: ExecutionException) {
                            // Do nothing
                        }
                    } else if (url.lowercase(Locale.US).contains(".webp")) {
                        try {
                            val image =
                                Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
                                    .get()
                            return WebResourceResponse(
                                IMAGE_JPG,
                                "UTF-8",
                                getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
                            )
                        } catch (e: ExecutionException) {
                            // Do nothing
                        }
                    }

                    return super.shouldInterceptRequest(view, url)
                }
            }
    }

    private fun htmlToWebview() {
        try {
            val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
            val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)

            binding.webcontent.settings.standardFontFamily = a.getString(0)
            binding.webcontent.visibility = View.VISIBLE

            val colorOnSurface = TypedValue()
            requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)

            val colorSurface = TypedValue()
            requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)

            binding.webcontent.settings.useWideViewPort = true
            binding.webcontent.settings.loadWithOverviewMode = true
            binding.webcontent.settings.javaScriptEnabled = false

            handleImageLoading()

            val gestureDetector =
                GestureDetector(
                    activity,
                    object : GestureDetector.SimpleOnGestureListener() {
                        override fun onSingleTapUp(e: MotionEvent): Boolean {
                            return performClick()
                        }
                    },
                )

            binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }

            binding.webcontent.settings.layoutAlgorithm =
                WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING

            var baseUrl: String? = null

            try {
                val itemUrl = URL(url)
                baseUrl = itemUrl.protocol + "://" + itemUrl.host
            } catch (e: MalformedURLException) {
                e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
            }

            val fontName =
                when (font) {
                    getString(R.string.open_sans_font_id) -> "Open Sans"
                    getString(R.string.roboto_font_id) -> "Roboto"
                    getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
                    else -> ""
                }

            val fontLinkAndStyle =
                if (font.isNotEmpty()) {
                    """<link href="https://fonts.googleapis.com/css?family=${
                        fontName.replace(
                            " ",
                            "+",
                        )
                    }" rel="stylesheet">
                |<style>
                |   * {
                |       font-family: '$fontName';
                |   }
                |</style>
                    """.trimMargin()
                } else {
                    ""
                }

            binding.webcontent.loadDataWithBaseURL(
                baseUrl,
                """<html>
                |<head>
                |   <meta name="viewport" content="width=device-width, initial-scale=1">
                |   <style>
                |      img {
                |        display: inline-block;
                |        height: auto;
                |        width: 100%;
                |        max-width: 100%;
                |      }
                |      a {
                |        color: ${
                    String.format(
                        "#%06X",
                        0xFFFFFF and resources.getColor(R.color.colorAccent),
                    )
                } !important;
                |      }
                |      *:not(a) {
                |        color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
                |      }
                |      * {
                |        font-size: ${fontSize}px;
                |        text-align: $textAlignment;
                |        word-break: break-word;
                |        overflow:hidden;
                |        line-height: 1.5em;
                |        background-color: ${
                    String.format(
                        "#%06X",
                        0xFFFFFF and colorSurface.data,
                    )
                };
                |      }
                |      body, html {
                |        background-color: ${
                    String.format(
                        "#%06X",
                        0xFFFFFF and colorSurface.data,
                    )
                } !important;
                |        border-color: ${
                    String.format(
                        "#%06X",
                        0xFFFFFF and colorSurface.data,
                    )
                }  !important;
                |        padding: 0 !important;
                |        margin: 0 !important;
                |      }
                |      a, pre, code {
                |        text-align: $textAlignment;
                |      }
                |      pre, code {
                |        white-space: pre-wrap;
                |        width:100%;
                |        background-color: ${
                    String.format(
                        "#%06X",
                        0xFFFFFF and colorSurface.data,
                    )
                };
                |      }
                |   </style>
                |   $fontLinkAndStyle
                |</head>
                |<body>
                |   $contentText
                |</body>
                """.trimMargin(),
                "text/html",
                "utf-8",
                null,
            )
        } catch (e: IllegalStateException) {
            e.sendSilentlyWithAcraWithName("Context required is null")
        }
    }

    fun scrollDown() {
        val height = binding.nestedScrollView.measuredHeight
        binding.nestedScrollView.smoothScrollBy(0, height / 2)
    }

    fun scrollUp() {
        val height = binding.nestedScrollView.measuredHeight
        binding.nestedScrollView.smoothScrollBy(0, -height / 2)
    }

    private fun openInBrowserAfterFailing() {
        binding.progressBar.visibility = View.GONE
        try {
            requireContext().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
        } catch (e: IllegalStateException) {
            e.sendSilentlyWithAcraWithName("Context required is null")
        }
    }

    companion object {
        private const val ARG_ITEMS = "items"

        fun newInstance(item: SelfossModel.Item): ArticleFragment {
            val fragment = ArticleFragment()
            val args = Bundle()
            args.putParcelable(ARG_ITEMS, item.toParcelable())
            fragment.arguments = args
            return fragment
        }
    }

    fun performClick(): Boolean {
        if (allImages != null && (
                    binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
                            binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
                    )
        ) {
            val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)

            val intent = Intent(activity, ImageActivity::class.java)
            intent.putExtra("allImages", allImages)
            intent.putExtra("position", position)
            startActivity(intent)
            return false
        }
        return false
    }
}