package downloader

import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlHandler
import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser
import fuel.Fuel
import fuel.method
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.io.FileNotFoundException
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Paths
import java.time.LocalDate
import java.time.LocalTime
import java.time.Month
import java.time.ZoneId
import java.util.*
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

fun main() {
    var cookie = "<insert your session cookie here or store in gradle.properties>"

    if (Files.exists(Paths.get("gradle.properties"))) {
        val props = Properties()
        props.load(Files.newBufferedReader(Paths.get("gradle.properties")))
        cookie = props.getProperty("cookie", cookie)
    }

    val downloader = Downloader(2025, "aoc2025", cookie)
    downloader.updateToLatest()
}

class Downloader(val year: Int, val packageName: String, val sessionCookie: String) {

    companion object {
        val INPUT_FILENAME = "Day%02d.txt"
        val HTML_FILENAME = "Day%02d_description.html"
        val DESC_FILENAME = "Day%02d_description.txt"
        val TEMPLATE_FILENAME = "Day_template.txt"
        val GENCLASS_FILENAME = "Day%02d.kt"
    }

    fun updateToLatest() {
        val targetDir = Paths.get("src", packageName)
        if (!Files.exists(targetDir)) {
            Files.createDirectories(targetDir)
        }
        val now = LocalDate.now(ZoneId.of("UTC+1"))
        val maxPuzzles = if (year < 2025) 25 else 12
        val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, maxPuzzles))) {
            if (now.isAfter(LocalDate.of(year, Month.NOVEMBER, 30))) {
                now.dayOfMonth
            } else {
                throw IllegalStateException("You're too early.")
            }
        } else {
            maxPuzzles
        }

        for (day in 1..lastDay) {
            val inputFile = targetDir.resolve(INPUT_FILENAME.format(day))
            val htmlFile = targetDir.resolve(HTML_FILENAME.format(day))
            val descriptionFile = targetDir.resolve(DESC_FILENAME.format(day))
            val genClassFile = targetDir.resolve(GENCLASS_FILENAME.format(day))
            if (!Files.exists(inputFile)) {
                if (day == lastDay && LocalTime.now(ZoneId.of("UTC+1")).hour < 5) {
                    println("Puzzle will not be available within the next hour. Skipping.")
                    break
                }
                while (day == lastDay && LocalTime.now(ZoneId.of("UTC+1")).hour == 5) {
                    println("Waiting until puzzle is available...")
                    runBlocking {
                        if (LocalTime.now(ZoneId.of("UTC+1")).minute < 59) {
                            delay(1.minutes)
                        } else {
                            delay(1.seconds)
                        }
                    }
                }
                println("Attempting to download input for day $day")
                val (code, data) = downloadInput(day)
                if (code != 200) {
                    throw FileNotFoundException("Could not download input for $year/$day: Status $code, Message: $data")
                }
                Files.write(inputFile, data.toByteArray(Charset.defaultCharset()))
            } else {
                println("Skipping input for day $day")
            }

            if (!Files.exists(htmlFile)) {
                println("Attempting to download html for day $day")
                val (code, data) = downloadDescription(day)
                if (code != 200) {
                    throw FileNotFoundException("Could not download html for $year/$day: Status $code, Message: $data")
                }
                Files.write(htmlFile, data.toByteArray(Charset.defaultCharset()))
            } else {
                println("Skipping html for day $day")
            }

            var description = ""
            var testInput = ""
            if (Files.exists(htmlFile)) {
                val html = Files.readAllBytes(htmlFile).toString(Charsets.UTF_8)
                var enableDescription = false
                var preFound = false
                var codeFound = false

                val handler = KsoupHtmlHandler
                    .Builder()
                    .onOpenTag { name, attributes, isImplied ->
                        when (name) {
                            "article" -> enableDescription = (attributes["class"] == "day-desc")
                            "pre" -> {
                                if (enableDescription && testInput.isNotEmpty()) {
                                    testInput += "---<here be dragons>---\n"
                                }
                                preFound = enableDescription
                            }

                            "code" -> codeFound = preFound
                        }
                    }
                    .onCloseTag { name, isImplied ->
                        when (name) {
                            "article" -> enableDescription = false
                            "pre" -> preFound = false
                            "code" -> codeFound = false
                            "h2" -> if (enableDescription) description += "\n"
                        }
                    }
                    .onText { text ->
                        if (enableDescription) {
                            description += text
                        }
                        if (preFound) {
                            testInput += text
                        }
                    }
                    .build()
                val ksoupHtmlParser = KsoupHtmlParser(
                    handler = handler,
                )
                ksoupHtmlParser.write(html)
                ksoupHtmlParser.end()
                testInput = testInput.trim()

                Files.writeString(descriptionFile, description)
            }

            if (!Files.exists(genClassFile)) {
                var template = Files.readString(Paths.get("src", TEMPLATE_FILENAME))
                template = template.replace("\${package}", packageName)
                template = template.replace("\${day}", "%02d".format(day))
                template = template.replace("\${testinput}", testInput)
                template = template.replace("\${description}", description.substringBefore("\n"))
                template = template.replace("\${weblink}", "https://adventofcode.com/$year/day/$day")
                Files.writeString(genClassFile, template)
            }
        }
    }

    fun downloadInput(day: Int): Pair<Int, String> {
        return downloadStuff(day, "/input")
    }

    fun downloadDescription(day: Int): Pair<Int, String> {
        return downloadStuff(day, "")
    }

    fun downloadStuff(day: Int, suffix: String): Pair<Int, String> {
        return runBlocking {
            val req = Fuel.method(
                url = "https://adventofcode.com/$year/day/$day$suffix",
                method = "GET",
                headers = mapOf(
                    "User-Agent" to "git.platon42.de/chrisly42/advent-of-code (chrisly@platon42.de)",
                    "Cookie" to "session=$sessionCookie"
                )
            )
            req.statusCode to req.body
        }
    }
}
