/*
 * Copyright (c) 2026 Meshtastic LLC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package org.meshtastic.feature.discovery.export

import android.graphics.Paint
import android.graphics.pdf.PdfDocument
import kotlinx.coroutines.withContext
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.ioDispatcher
import java.io.ByteArrayOutputStream

private const val PAGE_WIDTH = 612
private const val PAGE_HEIGHT = 792
private const val MARGIN_LEFT = 40f
private const val MARGIN_TOP = 50f
private const val LINE_HEIGHT = 18f
private const val SECTION_GAP = 12f
private const val TITLE_SIZE = 18f
private const val HEADING_SIZE = 14f
private const val BODY_SIZE = 10f
private const val LABEL_SIZE = 9f
private const val FOOTER_SIZE = 8f
private const val PAGE_BOTTOM_MARGIN = 60f
private const val LABEL_COLUMN_WIDTH = 160f

@Single
class PdfDiscoveryExporter : DiscoveryExporter {

    override suspend fun export(data: DiscoveryExportData): ExportResult = withContext(ioDispatcher) {
        @Suppress("TooGenericExceptionCaught")
        try {
            val bytes = renderPdf(data)
            val fileName = DiscoveryReportFormatter.generateFileName(data.session, "pdf")
            ExportResult.Success(content = bytes, mimeType = "application/pdf", fileName = fileName)
        } catch (e: Exception) {
            ExportResult.Error("PDF generation failed: ${e.message}")
        }
    }

    private fun renderPdf(data: DiscoveryExportData): ByteArray {
        val document = PdfDocument()
        val renderer = PageRenderer(document)

        renderer.drawTitle("Meshtastic Discovery Report")
        renderer.advanceLine()

        // Session overview
        renderer.drawHeading("Session Overview")
        for ((label, value) in DiscoveryReportFormatter.formatSessionOverviewLines(data.session)) {
            renderer.drawLabelValue(label, value)
        }
        renderer.advanceSection()

        // Per-preset sections
        for (result in data.presetResults) {
            renderer.drawHeading("Preset: ${result.presetName}")
            for ((label, value) in DiscoveryReportFormatter.formatPresetLines(result)) {
                renderer.drawLabelValue(label, value)
            }

            val nodes = data.nodesByPreset[result.id].orEmpty()
            if (nodes.isNotEmpty()) {
                renderer.advanceLine()
                renderer.drawSubheading("Discovered Nodes (${nodes.size})")
                for (node in nodes) {
                    renderer.drawBody(DiscoveryReportFormatter.formatNodeLine(node))
                }
            }
            renderer.advanceSection()
        }

        // AI summary
        val summary = data.session.aiSummary
        if (!summary.isNullOrBlank()) {
            renderer.drawHeading("AI Analysis")
            renderer.drawWrappedBody(summary)
            renderer.advanceSection()
        }

        renderer.drawFooter("Generated by Meshtastic Android")
        renderer.finishCurrentPage()

        val outputStream = ByteArrayOutputStream()
        document.writeTo(outputStream)
        document.close()
        return outputStream.toByteArray()
    }

    @Suppress("TooManyFunctions")
    private class PageRenderer(private val document: PdfDocument) {
        private var pageNumber = 0
        private var currentPage: PdfDocument.Page? = null
        private var yPosition = MARGIN_TOP

        private val titlePaint =
            Paint().apply {
                textSize = TITLE_SIZE
                isFakeBoldText = true
                isAntiAlias = true
            }
        private val headingPaint =
            Paint().apply {
                textSize = HEADING_SIZE
                isFakeBoldText = true
                isAntiAlias = true
            }
        private val bodyPaint =
            Paint().apply {
                textSize = BODY_SIZE
                isAntiAlias = true
            }
        private val labelPaint =
            Paint().apply {
                textSize = LABEL_SIZE
                isAntiAlias = true
                color = android.graphics.Color.DKGRAY
            }
        private val footerPaint =
            Paint().apply {
                textSize = FOOTER_SIZE
                isAntiAlias = true
                color = android.graphics.Color.GRAY
            }

        private fun ensurePage() {
            if (currentPage == null) {
                pageNumber++
                val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, PAGE_HEIGHT, pageNumber).create()
                currentPage = document.startPage(pageInfo)
                yPosition = MARGIN_TOP
            }
        }

        private fun checkPageBreak(linesNeeded: Int = 1) {
            if (yPosition + linesNeeded * LINE_HEIGHT > PAGE_HEIGHT - PAGE_BOTTOM_MARGIN) {
                finishCurrentPage()
                ensurePage()
            }
        }

        fun finishCurrentPage() {
            currentPage?.let { document.finishPage(it) }
            currentPage = null
        }

        fun drawTitle(text: String) {
            ensurePage()
            currentPage?.canvas?.drawText(text, MARGIN_LEFT, yPosition, titlePaint)
            yPosition += LINE_HEIGHT + SECTION_GAP
        }

        fun drawHeading(text: String) {
            checkPageBreak(linesNeeded = 2)
            ensurePage()
            currentPage?.canvas?.drawText(text, MARGIN_LEFT, yPosition, headingPaint)
            yPosition += LINE_HEIGHT
        }

        fun drawSubheading(text: String) {
            checkPageBreak()
            ensurePage()
            currentPage?.canvas?.drawText(text, MARGIN_LEFT, yPosition, bodyPaint.apply { isFakeBoldText = true })
            bodyPaint.isFakeBoldText = false
            yPosition += LINE_HEIGHT
        }

        fun drawBody(text: String) {
            checkPageBreak()
            ensurePage()
            currentPage?.canvas?.drawText(text, MARGIN_LEFT, yPosition, bodyPaint)
            yPosition += LINE_HEIGHT
        }

        fun drawLabelValue(label: String, value: String) {
            checkPageBreak()
            ensurePage()
            currentPage?.canvas?.let { canvas ->
                canvas.drawText("$label:", MARGIN_LEFT, yPosition, labelPaint)
                canvas.drawText(value, MARGIN_LEFT + LABEL_COLUMN_WIDTH, yPosition, bodyPaint)
            }
            yPosition += LINE_HEIGHT
        }

        fun drawWrappedBody(text: String) {
            val maxWidth = PAGE_WIDTH - MARGIN_LEFT * 2
            val words = text.split(" ")
            var currentLine = StringBuilder()

            for (word in words) {
                val testLine = if (currentLine.isEmpty()) word else "$currentLine $word"
                if (bodyPaint.measureText(testLine) > maxWidth && currentLine.isNotEmpty()) {
                    drawBody(currentLine.toString())
                    currentLine = StringBuilder(word)
                } else {
                    currentLine = StringBuilder(testLine)
                }
            }
            if (currentLine.isNotEmpty()) {
                drawBody(currentLine.toString())
            }
        }

        fun drawFooter(text: String) {
            ensurePage()
            currentPage?.canvas?.drawText(text, MARGIN_LEFT, PAGE_HEIGHT - MARGIN_TOP / 2, footerPaint)
        }

        fun advanceLine() {
            yPosition += LINE_HEIGHT
        }

        fun advanceSection() {
            yPosition += SECTION_GAP
        }
    }
}
