package de.storchp.fdroidbuildstatus

import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updatePadding
import dagger.hilt.android.AndroidEntryPoint
import de.storchp.fdroidbuildstatus.adapter.ApiResponse
import de.storchp.fdroidbuildstatus.adapter.fdroid.FdroidClient
import de.storchp.fdroidbuildstatus.databinding.ActivityBuildlogBinding
import de.storchp.fdroidbuildstatus.model.BuildlogType
import de.storchp.fdroidbuildstatus.utils.LoglinesBuffer
import de.storchp.fdroidbuildstatus.utils.PreferencesService
import de.storchp.fdroidbuildstatus.utils.addInsetsListener
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.FileWriter
import java.io.IOException
import java.io.Reader
import javax.inject.Inject

private val TAG = BuildlogActivity::class.java.simpleName

private const val NO_VERSION_CODE = -1L

@AndroidEntryPoint
class BuildlogActivity : AppCompatActivity() {

    @Inject
    lateinit var preferencesService: PreferencesService

    @Inject
    lateinit var fdroidClient: FdroidClient

    private lateinit var binding: ActivityBuildlogBinding
    private lateinit var id: String
    private var versionCode: Long = NO_VERSION_CODE

    override fun onCreate(savedInstanceState: Bundle?) {
        enableEdgeToEdge()
        super.onCreate(savedInstanceState)
        binding = ActivityBuildlogBinding.inflate(layoutInflater)
        binding.topAppBar.addInsetsListener {
            updatePadding(left = it.left, right = it.right)
        }
        binding.buildLog.addInsetsListener {
            updatePadding(left = it.left, right = it.right, bottom = it.bottom)
        }
        setSupportActionBar(binding.topAppBar)
        setContentView(binding.root)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportFragmentManager.setFragmentResultListener(
            BUILDLOG_TYPE_REQUEST,
            this
        ) { _: String?, bundle: Bundle ->
            val buildlogType = BuildlogType.valueOf(
                bundle.getString(BUILDLOG_TYPE_RESULT)!!
            )
            if (buildlogType.extension != null) {
                onBuildlogDownload(buildlogType)
            }
        }
    }

    override fun onResume() {
        super.onResume()
        val intent = intent
        if (intent != null) {
            val newId = intent.getStringExtra(EXTRA_BUILD_ITEM_ID)
            val newVersionCode =
                intent.getLongExtra(EXTRA_BUILD_ITEM_VERSION_CODE, NO_VERSION_CODE)
            if (newId == null || newVersionCode == NO_VERSION_CODE) {
                Log.e(TAG, "id and versionCode must be provided")
                finish()
                return
            }
            id = newId
            versionCode = newVersionCode
            title = getString(R.string.buildlog_title, versionCode.toString())
            fetchBuildLog(id, versionCode, UILogConsumer())
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_buildlog, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val itemId = item.itemId
        if (itemId == R.id.action_download_buildlog) {
            onBuildlogDownload(preferencesService.getBuildlogType())
            return true
        } else if (itemId == android.R.id.home) {
            finish()
            return true
        }
        return super.onOptionsItemSelected(item)
    }

    private var downloadLogResultLauncher = registerForActivityResult(
        ActivityResultContracts.CreateDocument("text/plain")
    ) { result: Uri? ->
        fetchBuildLog(
            id,
            versionCode,
            object : LogConsumer {
                override fun consume(id: String, versionCode: Long, reader: BufferedReader) {
                    try {
                        contentResolver.openFileDescriptor(result!!, "w")
                            .use { parcelableFileDescriptor ->
                                BufferedWriter(
                                    FileWriter(parcelableFileDescriptor!!.fileDescriptor)
                                ).use { writer ->
                                    reader.lines().forEach { line ->
                                        try {
                                            writer.write(line)
                                            writer.newLine()
                                        } catch (exception: IOException) {
                                            throw RuntimeException(exception)
                                        }
                                    }
                                }
                            }
                    } catch (e: Exception) {
                        Log.e(TAG, "Error downloading logfile to Document URI", e)
                        Toast.makeText(
                            this@BuildlogActivity,
                            resources.getString(R.string.loading_build_log_failed, e.message),
                            Toast.LENGTH_LONG
                        ).show()
                    }
                }
            })
    }

    private fun onBuildlogDownload(buildlogType: BuildlogType?) {
        try {
            when (buildlogType) {
                BuildlogType.GZ -> startActivity(
                    Intent(Intent.ACTION_VIEW, fdroidClient.logUri(id, versionCode))
                )

                BuildlogType.LOG -> downloadLogResultLauncher.launch(
                    String.format(
                        "%s_%s.log",
                        id,
                        versionCode
                    )
                )

                else -> {
                    BuildlogTypeFragment().show(
                        supportFragmentManager,
                        BUILDLOG_TYPE_REQUEST
                    )
                }
            }
        } catch (_: ActivityNotFoundException) {
            Toast.makeText(
                this,
                resources.getString(R.string.error_no_app_found_to_open_buildlog),
                Toast.LENGTH_LONG
            ).show()
        }
    }

    private interface LogConsumer {
        @Throws(IOException::class)
        fun consume(id: String, versionCode: Long, reader: BufferedReader)
    }

    private inner class UILogConsumer : LogConsumer {
        override fun consume(id: String, versionCode: Long, reader: BufferedReader) {
            val buffer = LoglinesBuffer(preferencesService.getMaxLogLines())
            reader.lines().forEach(buffer::add)
            setBuildLog(buffer.toString(), id, versionCode, UILogConsumer())
        }
    }

    private fun fetchBuildLog(id: String, versionCode: Long, consumer: LogConsumer) {
        startProgress()
        fdroidClient.getBuildLog(id, versionCode) { response: ApiResponse<Reader?> ->
            stopProgress()
            if (response.isSuccess) {
                try {
                    consumer.consume(id, versionCode, BufferedReader(response.value()))
                } catch (e: Exception) {
                    Log.d(TAG, "reading logfile failed", e)
                    Toast.makeText(
                        this@BuildlogActivity,
                        resources.getString(R.string.loading_build_log_failed, e.message),
                        Toast.LENGTH_LONG
                    ).show()
                }
            } else if (response.status() == ApiResponse.Status.NOT_FOUND) {
                Toast.makeText(
                    this@BuildlogActivity,
                    resources.getString(R.string.build_log_not_found),
                    Toast.LENGTH_LONG
                ).show()
            } else {
                Toast.makeText(
                    this@BuildlogActivity,
                    resources.getString(
                        R.string.loading_build_log_failed,
                        response.errorMessage()
                    ),
                    Toast.LENGTH_LONG
                ).show()
            }
        }
    }

    private fun setBuildLog(
        log: String,
        id: String,
        versionCode: Long,
        logConsumer: LogConsumer
    ) {
        Log.d(TAG, "Log.size: " + log.length)
        val lines = log.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        binding.buildLog.adapter = ArrayAdapter(this, R.layout.item_logline, lines)
        if (lines.size == 1) {
            // we might have an incomplete build log with only 1 line, fetch again
            fetchBuildLog(id, versionCode, logConsumer)
        }
    }

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

    private fun stopProgress() {
        binding.progressBar.visibility = View.GONE
    }

}