package im.angry.openeuicc.ui

import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.graphics.BitmapFactory
import android.os.Bundle
import android.text.Editable
import android.util.Log
import android.view.*
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import im.angry.openeuicc.common.R
import im.angry.openeuicc.service.EuiccChannelManagerService
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class ProfileDownloadFragment : BaseMaterialDialogFragment(),
    Toolbar.OnMenuItemClickListener, EuiccChannelFragmentMarker {
    companion object {
        const val TAG = "ProfileDownloadFragment"

        const val LOW_NVRAM_THRESHOLD = 30 * 1024 // < 30 KiB, the alert may fail

        fun newInstance(slotId: Int, portId: Int, finishWhenDone: Boolean = false): ProfileDownloadFragment =
            newInstanceEuicc(ProfileDownloadFragment::class.java, slotId, portId) {
                putBoolean("finishWhenDone", finishWhenDone)
            }
    }

    private lateinit var toolbar: Toolbar
    private lateinit var profileDownloadServer: TextInputLayout
    private lateinit var profileDownloadCode: TextInputLayout
    private lateinit var profileDownloadConfirmationCode: TextInputLayout
    private lateinit var profileDownloadIMEI: TextInputLayout
    private lateinit var profileDownloadFreeSpace: TextView
    private lateinit var progress: ProgressBar

    private var freeNvram: Int = -1

    private var downloading = false

    private val finishWhenDone by lazy {
        requireArguments().getBoolean("finishWhenDone", false)
    }

    private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
        result.contents?.let { content ->
            onScanResult(content)
        }
    }

    private val gallerySelectorLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { result ->
        if (result == null) return@registerForActivityResult

        lifecycleScope.launch(Dispatchers.IO) {
            runCatching {
                requireContext().contentResolver.openInputStream(result)?.let { input ->
                    val bmp = BitmapFactory.decodeStream(input)
                    input.close()

                    decodeQrFromBitmap(bmp)?.let {
                        withContext(Dispatchers.Main) {
                            onScanResult(it)
                        }
                    }

                    bmp.recycle()
                }
            }
        }
    }

    private fun onScanResult(result: String) {
        val components = result.split("$")
        if (components.size < 3 || components[0] != "LPA:1") return
        profileDownloadServer.editText?.setText(components[1])
        profileDownloadCode.editText?.setText(components[2])
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val view = inflater.inflate(R.layout.fragment_profile_download, container, false)

        toolbar = view.requireViewById(R.id.toolbar)
        profileDownloadServer = view.requireViewById(R.id.profile_download_server)
        profileDownloadCode = view.requireViewById(R.id.profile_download_code)
        profileDownloadConfirmationCode = view.requireViewById(R.id.profile_download_confirmation_code)
        profileDownloadIMEI = view.requireViewById(R.id.profile_download_imei)
        profileDownloadFreeSpace = view.requireViewById(R.id.profile_download_free_space)
        progress = view.requireViewById(R.id.progress)

        toolbar.inflateMenu(R.menu.fragment_profile_download)

        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        toolbar.apply {
            setTitle(R.string.profile_download)
            setNavigationOnClickListener {
                if (!downloading) {
                    dismiss()
                }
            }
            setOnMenuItemClickListener(this@ProfileDownloadFragment)
        }
    }

    override fun onMenuItemClick(item: MenuItem): Boolean = downloading ||
        when (item.itemId) {
            R.id.scan -> {
                barcodeScannerLauncher.launch(ScanOptions().apply {
                    setDesiredBarcodeFormats(ScanOptions.QR_CODE)
                    setOrientationLocked(false)
                })
                true
            }
            R.id.scan_from_gallery -> {
                gallerySelectorLauncher.launch("image/*")
                true
            }
            R.id.ok -> {
                if (freeNvram > LOW_NVRAM_THRESHOLD) {
                    startDownloadProfile()
                } else {
                    AlertDialog.Builder(requireContext()).apply {
                        setTitle(R.string.profile_download_low_nvram_title)
                        setMessage(R.string.profile_download_low_nvram_message)
                        setIcon(android.R.drawable.ic_dialog_alert)
                        setCancelable(true)
                        setPositiveButton(android.R.string.ok) { _, _ ->
                            startDownloadProfile()
                        }
                        setNegativeButton(android.R.string.cancel, null)
                        show()
                    }
                }
                true
            }
            else -> false
        }

    override fun onResume() {
        super.onResume()
        setWidthPercent(95)
    }

    @SuppressLint("MissingPermission")
    override fun onStart() {
        super.onStart()

        lifecycleScope.launch(Dispatchers.IO) {
            ensureEuiccChannelManager()
            if (euiccChannelManagerService.isForegroundTaskRunning) {
                withContext(Dispatchers.Main) {
                    dismiss()
                }
                return@launch
            }

            withEuiccChannel { channel ->
                val imei = try {
                    telephonyManager.getImei(channel.logicalSlotId) ?: ""
                } catch (e: Exception) {
                    ""
                }

                // Fetch remaining NVRAM
                val str = channel.lpa.euiccInfo2?.freeNvram?.also {
                    freeNvram = it
                }?.let { formatFreeSpace(it) }

                withContext(Dispatchers.Main) {
                    profileDownloadFreeSpace.text = getString(
                        R.string.profile_download_free_space,
                        str ?: getText(R.string.unknown)
                    )
                    profileDownloadIMEI.editText!!.text =
                        Editable.Factory.getInstance().newEditable(imei)
                }
            }
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return super.onCreateDialog(savedInstanceState).also {
            it.setCanceledOnTouchOutside(false)
        }
    }

    private fun startDownloadProfile() {
        val server = profileDownloadServer.editText!!.let {
            it.text.toString().trim().apply {
                if (isEmpty()) {
                    it.requestFocus()
                    return@startDownloadProfile
                }
            }
        }

        val code = profileDownloadCode.editText!!.text.toString().trim()
            .ifBlank { null }
        val confirmationCode = profileDownloadConfirmationCode.editText!!.text.toString().trim()
            .ifBlank { null }
        val imei = profileDownloadIMEI.editText!!.text.toString().trim()
            .ifBlank { null }

        downloading = true

        profileDownloadServer.editText!!.isEnabled = false
        profileDownloadCode.editText!!.isEnabled = false
        profileDownloadConfirmationCode.editText!!.isEnabled = false
        profileDownloadIMEI.editText!!.isEnabled = false

        progress.isIndeterminate = true
        progress.visibility = View.VISIBLE

        lifecycleScope.launch {
            ensureEuiccChannelManager()
            euiccChannelManagerService.waitForForegroundTask()
            val err = doDownloadProfile(server, code, confirmationCode, imei)

            if (err != null) {
                Log.d(TAG, "Error downloading profile")
                Log.d(TAG, Log.getStackTraceString(err))

                Toast.makeText(requireContext(), R.string.profile_download_failed, Toast.LENGTH_LONG).show()
            }

            if (parentFragment is EuiccProfilesChangedListener) {
                (parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
            }

            try {
                dismiss()
            } catch (e: IllegalStateException) {
                // Ignored
            }
        }
    }

    private suspend fun doDownloadProfile(
        server: String,
        code: String?,
        confirmationCode: String?,
        imei: String?
    ) = withContext(Dispatchers.Main) {
        // The service is responsible for launching the actual blocking part on the IO context
        // On our side, we need the Main context because  of the UI updates
        euiccChannelManagerService.launchProfileDownloadTask(
            slotId,
            portId,
            server,
            code,
            confirmationCode,
            imei
        ).onEach {
            if (it is EuiccChannelManagerService.ForegroundTaskState.InProgress) {
                progress.progress = it.progress
                progress.isIndeterminate = it.progress == 0
            } else {
                progress.progress = 100
                progress.isIndeterminate = false
            }
        }.waitDone()
    }

    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        if (finishWhenDone) {
            activity?.finish()
        }
    }

    override fun onCancel(dialog: DialogInterface) {
        super.onCancel(dialog)
        if (finishWhenDone) {
            activity?.finish()
        }
    }
}