package cy.agorise.bitsybitshareswallet.domain.usecase

import android.content.Context
import android.util.Log
import androidx.core.os.ConfigurationCompat
import androidx.documentfile.provider.DocumentFile
import com.pdfjet.*
import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedOutputStream
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.pow

class ExportTransactionsToPdfUseCase(private val applicationContext: Context) {

    suspend operator fun invoke(
        transactions: List<TransferDetail>,
        folderDocumentFile: DocumentFile
    ) = withContext(Dispatchers.IO) { // TODO Inject Dispatcher
        // Create the PDF file name
        val fileName = applicationContext.resources.let {
            "${it.getString(R.string.app_name)}-${it.getString(R.string.title_transactions)}.pdf"
        }

        // Obtains the path to store the PDF
        val pdfDocUri = folderDocumentFile.createFile("application/pdf", fileName)?.uri
        val contentResolver = applicationContext.contentResolver
        val pdfOutputStream = contentResolver.openOutputStream(pdfDocUri!!, "w")

        // Creates a new PDF object
        val pdf = PDF(BufferedOutputStream(pdfOutputStream), Compliance.PDF_A_1B)

        // Font used for the table headers
        val f1 = Font(pdf, CoreFont.HELVETICA_BOLD).apply { size = 7f }

        // Font used for the table contents
        val f2 = Font(pdf, CoreFont.HELVETICA).apply { size = 7f }

        // Creates a new PDF table
        val table = Table()

        // 2D array of cells used to populate the PDF table
        val tableData = mutableListOf<List<Cell>>()

        // Add column names/headers
        val columnNames = intArrayOf(
            R.string.title_from, R.string.title_to, R.string.title_memo, R.string.title_date,
            R.string.title_time, R.string.title_amount, R.string.title_equivalent_value
        )

        val header = columnNames.map { columnName ->
            Cell(f1, applicationContext.getString(columnName)).apply { setPadding(2f) }
        }

        // Add the table headers
        tableData.add(header)

        // Add the table contents
        val locale = ConfigurationCompat.getLocales(applicationContext.resources.configuration)[0]
        tableData.addAll(getData(transactions, f2, locale))

        // Configure the PDF table
        table.setData(tableData, Table.DATA_HAS_1_HEADER_ROWS)
        table.setCellBordersWidth(0.2f)
        // The A4 size has 595 points of width, with the below we are trying to assign the same
        // width to all cells and also keep them centered.
        for (i in 0..6) {
            table.setColumnWidth(i, 65f)
        }
        table.wrapAroundCellText()
        table.mergeOverlaidBorders()

        // Populate the PDF table
        while (table.hasMoreData()) {
            // Configures the PDF page
            val page = Page(pdf, Letter.PORTRAIT)
            table.setLocation(45f, 30f)
            table.drawOn(page)
        }

        pdf.close()

        Log.d("ExportTransactionsToPdf", "PDF generated and saved")
    }

    private suspend fun getData(
        transferDetails: List<TransferDetail>,
        font: Font,
        locale: Locale
    ): List<List<Cell>> = withContext(Dispatchers.IO) { // TODO Inject Dispatcher

        val tableData = mutableListOf<List<Cell>>()

        // Configure date and time formats to reuse in all the transfers
        val dateFormat = SimpleDateFormat("MM-dd-yyyy", locale)
        val timeFormat = SimpleDateFormat("HH:mm:ss", locale)
        var date: Date

        // Save all the transfers information
        for ((index, transferDetail) in transferDetails.withIndex()) {
            val cols = mutableListOf<String>()

            date = Date(transferDetail.date * 1000)

            cols.add(transferDetail.from ?: "")                 // From
            cols.add(transferDetail.to ?: "")                   // To
            cols.add(transferDetail.memo)                       // Memo
            cols.add(dateFormat.format(date))                   // Date
            cols.add(timeFormat.format(date))                   // Time

            // Asset Amount
            val assetPrecision = transferDetail.assetPrecision
            val assetAmount = transferDetail.assetAmount / 10.0.pow(assetPrecision)
            cols.add(
                String.format(
                    "%.${assetPrecision}f %s",
                    assetAmount,
                    transferDetail.assetSymbol
                )
            )

            // Fiat Equivalent
            if (transferDetail.fiatAmount != null && transferDetail.fiatSymbol != null) {
                val currency = Currency.getInstance(transferDetail.fiatSymbol)
                val fiatAmount =
                    transferDetail.fiatAmount / 10.0.pow(currency.defaultFractionDigits)
                cols.add(
                    String.format(
                        "%.${currency.defaultFractionDigits}f %s",
                        fiatAmount, currency.currencyCode
                    )
                )
            }

            val row = cols.map { col ->
                Cell(font, col).apply { setPadding(2f) }
            }

            tableData.add(row)

            appendMissingCells(tableData, font)

            // TODO update progress
        }

        tableData
    }

    private fun appendMissingCells(tableData: List<List<Cell>>, font: Font) {
        val firstRow = tableData[0]
        val numOfColumns = firstRow.size
        for (i in tableData.indices) {
            val dataRow = tableData[i] as ArrayList<Cell>
            val dataRowColumns = dataRow.size
            if (dataRowColumns < numOfColumns) {
                for (j in 0 until numOfColumns - dataRowColumns) {
                    dataRow.add(Cell(font))
                }
                dataRow[dataRowColumns - 1].colSpan = numOfColumns - dataRowColumns + 1
            }
        }
    }
}
