package com.github.mondei1.offpass

import android.annotation.SuppressLint
import android.content.*
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.print.PrintAttributes
import android.print.PrintJob
import android.print.PrintManager
import android.text.method.HideReturnsTransformationMethod
import android.text.method.PasswordTransformationMethod
import android.util.Base64
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.View
import android.view.WindowManager
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.zxing.BarcodeFormat
import com.google.zxing.integration.android.IntentIntegrator
import com.google.zxing.integration.android.IntentResult
import com.journeyapps.barcodescanner.BarcodeEncoder
import dev.turingcomplete.kotlinonetimepassword.GoogleAuthenticator
import kotlinx.android.synthetic.main.activity_create.*
import kotlinx.android.synthetic.main.dialogpassphrase.view.*
import kotlinx.coroutines.*
import java.io.ByteArrayOutputStream
import java.lang.IllegalArgumentException
import java.util.*
import kotlin.collections.ArrayList

class CreateActivity : AppCompatActivity() {
    private var schema: QRSchema? = null
    private var qrCodeBitmap: Bitmap? = null

    private var fa_coroutine: Job? = null
    private var mWebView: WebView? = null
    private var printJob: PrintJob? = null

    private var fa_uri: Uri? = null

    private var known_passwords = ArrayList<String>()

    /**
     * Gets triggered when the user clicks on printing button (top left) and renders the paper to
     * print.
     */
    fun doPrint(passphrase: String, hint: String) {
        // Set up printing
        this.schema = QRSchema(this)
        this.schema!!.title = title_input.text.toString()
        this.schema!!.username = username_input.text.toString()
        this.schema!!.password = password_input.text.toString()
        this.schema!!.email = email_input.text.toString()
        this.schema!!.website_url = url_input.text.toString()

        if (fa_uri != null) {
            this.schema!!.custom.put("2fa", fa_uri.toString())
        }

        this.schema!!.build(arrayOf(), passphrase)

        val barcodeEncoder: BarcodeEncoder = BarcodeEncoder()
        this.qrCodeBitmap = barcodeEncoder.encodeBitmap(this.schema!!.raw, BarcodeFormat.QR_CODE, 400, 400)


        val webView = WebView(this)
        webView.webViewClient = object  : WebViewClient() {
            override fun shouldOverrideUrlLoading(view: WebView, requesst: WebResourceRequest) = false

            override fun onPageFinished(view: WebView?, url: String?) {
                Log.i("Create Activity", "Template page finished loading")
                createWebPrintJob(webView)
                mWebView = null
            }
        }

        var htmlDocument = String(this.resources.openRawResource(
            this.resources.getIdentifier("print", "raw", this.packageName)
        ).readBytes()).replace("\n", "")

        // Prepare html document
        var byteArrayOutputStream = ByteArrayOutputStream()
        this.qrCodeBitmap?.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
        htmlDocument = htmlDocument.replace("\$DATE", Date().time.toString())
            .replace("\$HINT", hint)
            .replace("\$QRCODE", Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.NO_WRAP))

        Log.i("Create Activity", htmlDocument)
        webView.loadDataWithBaseURL("file:///android_asset/", htmlDocument, "text/HTML", "UTF-8", null)

        mWebView = webView
    }

    /**
     * Invoked after doPrint() and takes the rendered web view and creates a new print job.
     */
    fun createWebPrintJob(webView: WebView) {
        (this.getSystemService(Context.PRINT_SERVICE) as? PrintManager)?.let { printManager ->
            val jobName = "Offpass Document"
            val printAdapter = webView.createPrintDocumentAdapter(jobName)

            printManager.print(
                jobName,
                printAdapter,
                PrintAttributes.Builder().build()
            ).also { printJob ->
                this.printJob = printJob
            }
        }
    }

    /**
     * Invoked when user gave empty passphrase.
     */
    fun showError(title: String, body: String) {
        val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.ErrorDialog))

        with(builder) {
            setTitle(title)
            setMessage(body)
            setPositiveButton("Got it", DialogInterface.OnClickListener { dialogInterface, i ->
                dialogInterface.dismiss()
            })
            show()
        }

    }

    fun passwordKnown(password: String): Boolean {
        return this.known_passwords.contains(password)
    }

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

        // Prevent other apps from making screenshots or to record Offpass.
        window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)

        this.schema = QRSchema(this)
        /*this.schema!!.decrypted_raw = "%JtuB4O9M42%Gitea|Nicolas|542superGoOD_pW&|klier.nicolas@protonmail.com|\$ul|(\$vb)\$O4|()What's your favorite series%Rick and morty|(2fa)otpauth://totp/OffPass%20Test?secret=d34gfkki5dkd5knifysrpgndd5xb2c7eddwki7ya4pvoisfa5c3ko5pv&issuer=Nicolas%20Klier"
        this.schema!!.parse(this)

        this.schema!!.build(arrayOf("website_url", "2fa", "What's your favorite series"), "123")
        this.schema!!.decrypt(this.schema!!.raw, "123")*/

        setSupportActionBar(findViewById(R.id.toolbar))

        // Split word list in the background (this process can take a few seconds to complete)
        GlobalScope.async {
            val known_passwords_ = String(resources.openRawResource(
                resources.getIdentifier("passwordlist", "raw", packageName)
            ).readBytes())

            known_passwords = known_passwords_.split("\n") as ArrayList<String>
        }

        setContentView(R.layout.activity_create)
        back.setOnClickListener {
            Log.i("CREATE", "Back got clicked!")
            finish()
        }
        print_button.setOnClickListener {
            // Ask user for passhprase and hint
            val builder = AlertDialog.Builder(ContextThemeWrapper(this, R.style.PassphraseDialog))

            with(builder) {
                val editTextLayout = layoutInflater.inflate(R.layout.dialogpassphrase, null)
                setView(editTextLayout)
                setTitle("Set Passphrase")
                setMessage("Final step is it to set your passphrase. Minimum are 8 characters.")
                setPositiveButton("Print", DialogInterface.OnClickListener { dialogInterface, i ->
                    val passphrase = editTextLayout.passphrase_input.text.toString();
                    if (passphrase == "") {
                        // Make a new alert, telling the user that passphrase must not be null.
                        dialogInterface.cancel()
                        showError("Empty passphrase", "Passphrase must not be null.")
                        return@OnClickListener
                    }
                    if (passphrase.length < 8) {
                        // Make a new alert, telling the user that his passphrase doesn't meet the minimum.
                        dialogInterface.cancel()
                        showError("Weak passphrase", "Passphrase has to be at least 8 characters long.")
                        return@OnClickListener
                    }
                    if (passphrase != editTextLayout.passphrase2_input.text.toString()) {
                        // Make a new alert, telling the user that his passphrase doesn't match the repeat field.
                        dialogInterface.cancel()
                        showError("Passphrase mismatch", "Both passphrases do not match.")
                        return@OnClickListener
                    }

                    // Check if password is found in our offline password list
                    if(passwordKnown(passphrase)) {
                        // Make a new alert, telling the user that his passphrase was found in our local wordlist.
                        dialogInterface.cancel()
                        showError("Passphrase is compromised!", "Passphrase got found in a wordlist of bad passwords. " +
                                "If you already used it somewhere, change it immediately!")
                        return@OnClickListener
                    }

                    doPrint(editTextLayout.passphrase_input.text.toString(),
                            editTextLayout.hint_input.text.toString())
                })
                setNegativeButton("Go back", DialogInterface.OnClickListener { dialogInterface, i ->
                    dialogInterface.dismiss()
                })
                show()
            }


        }
        password_hide.setOnClickListener {
            if (password_input.transformationMethod == HideReturnsTransformationMethod.getInstance()) {
                password_input.transformationMethod = PasswordTransformationMethod.getInstance()
                password_hide.setImageResource(R.drawable.ic_visibility_on)
            } else {
                password_input.transformationMethod = HideReturnsTransformationMethod.getInstance()
                password_hide.setImageResource(R.drawable.ic_visibility_off)

            }
        }

        fa_input.isFocusable = false

        fa_input.setOnClickListener {
            if (fa_coroutine != null) {
                if (fa_coroutine!!.isActive) {
                    // Copy code if already scanend
                    Toast.makeText(this, "Copied!", Toast.LENGTH_SHORT).show()
                    val clip: ClipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
                    clip.setPrimaryClip(ClipData.newPlainText("2FA code", fa_input.text))
                    return@setOnClickListener
                }
            }

            // Open QR-Code scanner
            IntentIntegrator(this)
                .setPrompt("Scan 2FA secret")
                .setBeepEnabled(false)
                .setBarcodeImageEnabled(false)
                .initiateScan()
        }
    }

    @SuppressLint("SimpleDateFormat", "SetTextI18n")
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        var result: IntentResult =
            IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
        if (result.contents == null) {
            Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show()
        } else {
            // We scanned a 2FA code
            if (result.contents.startsWith("otpauth://totp/")) {
                fa_progress.visibility = View.VISIBLE
                fa_uri = Uri.parse(result.contents)

                if (fa_uri?.getQueryParameter("secret").toString() == "" || fa_uri?.getQueryParameter("secret") == null) {
                    showError("Corrupt 2FA", "The scanned 2FA secret doesn't contain a secret!")
                    return
                }

                val fa_generator = GoogleAuthenticator(base32secret = fa_uri?.getQueryParameter("secret").toString())
                this.schema!!.custom.put("2fa", fa_uri?.getQueryParameter("secret").toString())

                /*val objAnim: ObjectAnimator = ObjectAnimator.ofInt(fa_progress, "progress")
                objAnim.duration = 300
                objAnim.interpolator = LinearInterpolator()
                objAnim.start()*/

                // TODO: There is a bug. When the user changes his current app, this coroutine stops.
                fa_coroutine = GlobalScope.launch(Dispatchers.Main) {
                    while (true) {
                        try {
                            fa_input.setText(fa_generator.generate())
                        } catch (error: IllegalArgumentException) {
                            showError("Corrupt 2FA", "The scanned 2FA secret is corrupt and therefore no codes can be generated.")
                            return@launch
                        }

                        var seconds_remaining = CryptoOperations().getRemainingTOTPTime()
                        Log.i("2FA Generator", "Remaining: $seconds_remaining")

                        if (seconds_remaining == 0) seconds_remaining = 30

                        // Change color
                        if (seconds_remaining >= 15) {
                            fa_progress.progressTintList = ColorStateList.valueOf(Color.GREEN)
                        } else if (seconds_remaining < 15 && seconds_remaining >= 8) {
                            fa_progress.progressTintList = ColorStateList.valueOf(Color.YELLOW)
                        } else {
                            fa_progress.progressTintList = ColorStateList.valueOf(Color.RED)
                        }

                        fa_progress.progress = seconds_remaining
                        delay(1000)
                    }
                }

                fa_label.text = "${fa_uri?.path?.replace("/", "")} (${fa_uri?.getQueryParameter("issuer")})"
            } else {
                showError("No 2FA", "The scanned QR-Code has the wrong format.")
            }
        }
    }

    override fun onStop() {
        super.onStop()
        fa_coroutine?.cancel(CancellationException("Activity is stopping"))
    }

}