package cfig.lazybox.staging

import org.slf4j.LoggerFactory
import java.io.File
import java.io.IOException
import java.net.HttpURLConnection
import java.net.MalformedURLException
import java.net.URI
import java.net.URL
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.Duration
import java.time.format.DateTimeParseException
import kotlin.system.exitProcess

class DiffCI {
    private val FAKE_URL = "FAKE_URL"
    private val baseUrl = System.getenv("diffCIbaseUrl") ?: FAKE_URL
    private val finder = ChangelistFinder(baseUrl)
    private val dateFormat = DateTimeFormatter.ofPattern("yyyyMMdd")
    private val dateTimeFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmm")
    private val buildIdRegex = ".*/(\\d{12})/changelist".toRegex()
    private val log = LoggerFactory.getLogger(DiffCI::class.simpleName)

    fun run(args: Array<String>) {
        if (baseUrl == FAKE_URL) {
            log.info("Error: Environment variable 'diffCIbaseUrl' is not set.")
            log.info("Please set it to the base URL of the changelist directory.")
            exitProcess(1)
        }
        when (args.size) {
            1 -> handleSingleDate(args)
            2 -> handleRange(args)
            else -> printUsageAndExit()
        }
    }

    private fun handleSingleDate(args: Array<String>) {
        if (args[0].length != 8) {
            log.info("Error: For a single argument, please provide a date in yyyyMMdd format.")
            exitProcess(1)
        }
        val targetDate = parseDate(args[0]) ?: exitProcess(1)

        log.info("=============================================")
        log.info("Searching for changelists on: ${args[0]}")
        log.info("=============================================")

        val urls = finder.findChangelistUrlsForDate(targetDate)
        printResults(urls)
    }

    private fun handleRange(args: Array<String>) {
        val arg1 = args[0]
        val arg2 = args[1]

        if (arg1.length != arg2.length) {
            log.info("Error: Start and end arguments must be of the same format (both dates or both date-times).")
            exitProcess(1)
        }

        when (arg1.length) {
            8 -> handleDateRange(arg1, arg2)
            12 -> handleDateTimeRange(arg1, arg2)
            else -> {
                log.info("Error: Invalid argument format. Please use yyyyMMdd or yyyyMMddHHmm.")
                exitProcess(1)
            }
        }
    }

    private fun handleDateRange(startDateStr: String, endDateStr: String) {
        val startDate = parseDate(startDateStr) ?: exitProcess(1)
        val endDate = parseDate(endDateStr) ?: exitProcess(1)

        if (startDate.isAfter(endDate)) {
            log.info("Error: The start date ($startDateStr) must be before or the same as the end date ($endDateStr).")
            exitProcess(1)
        }

        log.info("=============================================")
        log.info("Searching for changelists from $startDateStr to $endDateStr")
        log.info("=============================================")

        val allUrls = mutableListOf<String>()
        var currentDate = startDate
        while (currentDate <= endDate) {
            log.info("----- Processing Date: $currentDate -----")
            val dailyUrls = finder.findChangelistUrlsForDate(currentDate)
            allUrls.addAll(dailyUrls)
            currentDate = currentDate.plusDays(1)
        }
        printResults(allUrls)
    }

    private fun handleDateTimeRange(startDateTimeStr: String, endDateTimeStr: String) {
        val startDateTime = parseDateTime(startDateTimeStr) ?: exitProcess(1)
        val endDateTime = parseDateTime(endDateTimeStr) ?: exitProcess(1)

        if (startDateTime.isAfter(endDateTime)) {
            log.info("Error: The start time ($startDateTimeStr) must be before or the same as the end time ($endDateTimeStr).")
            exitProcess(1)
        }

        log.info("=============================================")
        log.info("Searching for changelists between $startDateTimeStr and $endDateTimeStr")
        log.info("=============================================")

        val allUrls = mutableListOf<String>()
        var currentDate = startDateTime.toLocalDate()
        while (currentDate <= endDateTime.toLocalDate()) {
            log.info("----- Processing Date: $currentDate -----")
            val dailyUrls = finder.findChangelistUrlsForDate(currentDate)

            val filteredUrls = dailyUrls.filter { url ->
                val matchResult = buildIdRegex.find(url)
                if (matchResult != null) {
                    val buildId = matchResult.groupValues[1]
                    val buildDateTime = parseDateTime(buildId)
                    buildDateTime != null && !buildDateTime.isBefore(startDateTime) && !buildDateTime.isAfter(endDateTime)
                } else {
                    false
                }
            }
            allUrls.addAll(filteredUrls)
            currentDate = currentDate.plusDays(1)
        }
        printResults(allUrls, isTimeRange = true)
    }

    private fun printUsageAndExit() {
        log.info("Error: Invalid number of arguments.")
        log.info("Usage:")
        log.info("  Single Date:   --args='diffci <yyyymmdd>'")
        log.info("  Date Range:    --args='diffci <start_date> <end_date>'")
        log.info("  Time Range:    --args='diffci <start_datetime> <end_datetime>'")
        log.info("Examples:")
        log.info("  --args='diffci  20250628'")
        log.info("  --args='diffci  20250627 20250628'")
        log.info("  --args='diffci  202506281000 202506281430'")
        exitProcess(1)
    }

    fun fetchChangeList(urlsToFetch: List<String>) {
        val OUTPUT_FILENAME = "merged_content.txt"
        if (urlsToFetch.isEmpty()) {
            return
        }
        val outputFile = File(OUTPUT_FILENAME)
        log.info("Starting to fetch content from ${urlsToFetch.size} URLs ...")

        outputFile.bufferedWriter().use { writer ->
            // Iterate over each URL string in the list.
            urlsToFetch.forEachIndexed { index, urlString ->
                log.info("(${index + 1}/${urlsToFetch.size}) Fetching: $urlString")
                try {
                    // Create a URL object from the string.
                    val url = URL(urlString)
                    val connection = (url.openConnection() as HttpURLConnection).also {
                        it.requestMethod = "GET"
                        it.connectTimeout = 5000 // 5 seconds
                        it.readTimeout = 5000    // 5 seconds
                    }
                    val responseCode = connection.responseCode
                    // Write a header for this URL's content in the output file.
                    writer.write("--- START: Content from $urlString ---")
                    writer.newLine()
                    // Check for a successful HTTP response (2xx status codes).
                    if (responseCode in 200..299) {
                        connection.inputStream.bufferedReader().use { reader ->
                            // Read the content line by line and write to the file.
                            reader.forEachLine { line ->
                                writer.write(line)
                                writer.newLine()
                            }
                        }
                    } else {
                        val errorMessage = "Received non-OK response code: $responseCode"
                        log.info("Warning: $errorMessage for $urlString")
                        writer.write("! FAILED: $errorMessage")
                        writer.newLine()
                    }

                } catch (e: MalformedURLException) {
                    val errorMessage = "Invalid URL format"
                    log.error("Error for '$urlString': $errorMessage. Skipping.")
                    writer.write("! FAILED: $errorMessage")
                    writer.newLine()
                } catch (e: IOException) {
                    val errorMessage = "Error reading data (I/O problem): ${e.message}"
                    log.info("Error for '$urlString': $errorMessage. Skipping.")
                    writer.write("! FAILED: $errorMessage")
                    writer.newLine()
                } catch (e: Exception) {
                    val errorMessage = "An unexpected error occurred: ${e.message}"
                    log.error("Error for '$urlString': $errorMessage. Skipping.")
                    writer.write("! FAILED: $errorMessage")
                    writer.newLine()
                } finally {
                    // Write a footer and add extra newlines for separation.
                    writer.write("--- END: Content from $urlString ---")
                    writer.newLine()
                    writer.newLine()
                }
            }
        }
        log.info("Process complete. '${outputFile.absolutePath}'.")
    }

    private fun printResults(urls: List<String>, isTimeRange: Boolean = false) {
        log.info("--- Results ---")
        if (urls.isNotEmpty()) {
            log.info("Successfully found ${urls.size} changelist files:")
            urls.forEach { log.info(it) }
        } else {
            val rangeType = if (isTimeRange) "time range" else "date(s)"
            log.info("No changelist files were found for the specified $rangeType.")
        }
        log.info("---------------")
        fetchChangeList(urls)
    }

    private fun parseDate(dateStr: String): LocalDate? {
        return try {
            LocalDate.parse(dateStr, dateFormat)
        } catch (e: DateTimeParseException) {
            log.info("Error: Invalid date format for '$dateStr'.")
            log.info("Please use the yyyyMMdd format (e.g., 20250628).")
            null
        }
    }

    private fun parseDateTime(dateTimeStr: String): LocalDateTime? {
        return try {
            LocalDateTime.parse(dateTimeStr, dateTimeFormat)
        } catch (e: DateTimeParseException) {
            log.info("Error: Invalid date-time format for '$dateTimeStr'.")
            log.info("Please use the yyyyMMddHHmm format (e.g., 202506281027).")
            null
        }
    }

    /**
     * Inner class to handle the web scraping logic.
     */
    private inner class ChangelistFinder(private val baseUrl: String) {
        private val client = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_1_1)
            .connectTimeout(Duration.ofSeconds(10))
            .build()

        fun findChangelistUrlsForDate(date: LocalDate): List<String> {
            val yearMonthPattern = DateTimeFormatter.ofPattern("yyyyMM")
            val yearMonthDayPattern = DateTimeFormatter.ofPattern("yyyyMMdd")
            val yearMonthStr = date.format(yearMonthPattern)
            val yearMonthDayStr = date.format(yearMonthDayPattern)
            val directoryUrl = "$baseUrl/$yearMonthStr/$yearMonthDayStr/"

            return try {
                val htmlContent = fetchUrlContent(directoryUrl)
                if (htmlContent != null) {
                    parseDirectoryHtmlWithRegex(htmlContent, directoryUrl, yearMonthDayStr)
                } else {
                    emptyList()
                }
            } catch (e: IOException) {
                log.info("An error occurred during the network request: ${e.message}")
                emptyList()
            } catch (e: InterruptedException) {
                Thread.currentThread().interrupt()
                log.info("The network request was interrupted: ${e.message}")
                emptyList()
            }
        }

        private fun fetchUrlContent(url: String): String? {
            try {
                val request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build()
                val response = client.send(request, HttpResponse.BodyHandlers.ofString())
                if (response.statusCode() != 200) {
                    if (response.statusCode() != 404) {
                        log.info("Request for $url failed with code: ${response.statusCode()}")
                    }
                    return null
                }
                return response.body()
            } catch (e: Exception) {
                log.info("Exception while fetching URL '$url': ${e.message}")
                return null
            }
        }

        private fun parseDirectoryHtmlWithRegex(html: String, directoryUrl: String, datePrefix: String): List<String> {
            val regex = "href=\"($datePrefix\\d+)/\"".toRegex()
            return regex.findAll(html)
                .map { matchResult ->
                    val dirName = matchResult.groupValues[1]
                    "$directoryUrl$dirName/changelist"
                }
                .toList()
        }
    }
}
