package spitfire

import (
	"crypto/sha1"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"time"
)

// Package the APPINDEX update process
func PackageAPPINDEX(
	name, // e.g. "spitfire-browser"
	release, // e.g. "nightly"
	version, // e.g. "2024.12.21"
	arch, // e.g. "amd64"
	size, // e.g. "838205457" (compressed size)
	installedSize, // e.g. "3595495684" (uncompressed size)
	description, // e.g. "Spitfire build"
	url, // e.g. "https://spitfirebrowser.xyz/"
	license, // e.g. "AGPL-3.0"
	origin, // e.g. "browser"
	maintainer, // e.g. "Internet Addict"
	dependencies, // usually blank ..."
	platform, // e.g. "linux"
	remoteDir string, // e.g. "nightly/linux/amd64"
) error {

	// Construct a filename that matches what you compress & upload
	// Adjust this naming convention to match your compress step exactly!
	// Example: "spitfire-browser-nightly-amd64-linux.tar.gz"
	fileName := fmt.Sprintf("%s-%s-%s-%s.tar.gz", origin, arch, release, platform)

	// If you have a real file on disk, you might call `calcChecksum(fileName)`.
	// For demo, we call `calcChecksum` with a mock package name.
	checksum := calcChecksum(fileName)

	// Remove existing entry based on P, R, A, o, and p:
	removeExistingEntry(name, release, arch, origin, platform)

	// Use current Unix timestamp
	timestamp := time.Now().Unix()

	// Open or create the APPINDEX file for appending
	file, err := os.OpenFile("./APPINDEX", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatalf("Failed to open APPINDEX file: %v", err)
	}
	defer file.Close()

	// Build the SourceForge-based download URL:
	// https://downloads.sourceforge.net/project/spitfire-browser/<remoteDir>/<fileName>
	downloadURL := fmt.Sprintf("https://downloads.sourceforge.net/project/spitfire-browser/%s/%s", remoteDir, fileName)

	// Format the entry.
	entry := fmt.Sprintf(`
	C:%s
	P:%s
	R:%s
	V:%s
	A:%s
	S:%s
	I:%s
	T:%s
	U:%s
	L:%s
	o:%s
	m:%s
	t:%d
	D:%s
	p:%s
	q:
	d:%s
	I:https://weforge.xyz/Spitfire/Branding/raw/branch/main/active/browser/icon.svg
	S:https://spitfirebrowser.xyz/static/images/screenshots/1.png
	T:browser,experimental,testing
	r: Automated Nightly build of Spitfire
	c:%s
	`, checksum, name, release, version, arch, size, installedSize,
		description, url, license, origin, maintainer, timestamp, dependencies,
		platform, downloadURL, checksum)

	// Trim leading newline to keep it clean
	entry = strings.TrimPrefix(entry, "\n")

	if _, err := file.WriteString(entry + "\n"); err != nil {
		log.Fatalf("Failed to write to APPINDEX file: %v", err)
	}

	fmt.Println("APPINDEX has been updated successfully.")
	return nil
}

// calcChecksum calculates a checksum (SHA-1) for a given input string
func calcChecksum(input string) string {
	h := sha1.New()
	_, _ = io.WriteString(h, input)
	return fmt.Sprintf("%x", h.Sum(nil))
}

// removeExistingEntry removes an existing entry from APPINDEX if it matches P, R, A, o, and p fields.
func removeExistingEntry(name, release, arch, origin, platform string) {
	// Read file contents
	content, err := os.ReadFile("./APPINDEX")
	if err != nil {
		if os.IsNotExist(err) {
			return // If file does not exist, no need to remove anything
		}
		log.Fatalf("Failed to read APPINDEX: %v", err)
	}

	lines := strings.Split(string(content), "\n")

	var newLines []string
	var currentEntry []string

	inEntry := false // true when we're reading lines for a single entry

	// We'll store the P, R, A, o, p values within the current entry
	var pVal, rVal, aVal, oVal, plVal string

	for _, line := range lines {
		trimmed := strings.TrimSpace(line)

		// Detect the start of an entry (a line that starts with "C:")
		if strings.HasPrefix(trimmed, "C:") {
			// If we were in an entry previously, check if it should be removed or kept
			if inEntry && len(currentEntry) > 0 {
				// Decide whether to keep the previous entry
				if !(pVal == name && rVal == release && aVal == arch && oVal == origin && plVal == platform) {
					newLines = append(newLines, currentEntry...)
					newLines = append(newLines, "") // Blank line to separate entries
				}
			}

			// Start a new entry
			currentEntry = []string{trimmed}
			inEntry = true

			// Reset these values for the new entry
			pVal, rVal, aVal, oVal, plVal = "", "", "", "", ""
			continue
		}

		if inEntry {
			// Collect lines for this entry
			currentEntry = append(currentEntry, trimmed)

			// Extract fields for matching later
			switch {
			case strings.HasPrefix(trimmed, "P:"):
				pVal = strings.TrimPrefix(trimmed, "P:")
			case strings.HasPrefix(trimmed, "R:"):
				rVal = strings.TrimPrefix(trimmed, "R:")
			case strings.HasPrefix(trimmed, "A:"):
				aVal = strings.TrimPrefix(trimmed, "A:")
			case strings.HasPrefix(trimmed, "o:"):
				oVal = strings.TrimPrefix(trimmed, "o:")
			case strings.HasPrefix(trimmed, "p:"):
				plVal = strings.TrimPrefix(trimmed, "p:")
			}
		} else {
			// Lines outside of entries just get appended directly
			// (e.g. if there's extraneous text before the first "C:" or after the last)
			if trimmed != "" {
				newLines = append(newLines, trimmed)
			}
		}
	}

	// Handle the last entry if we ended inEntry
	if inEntry && len(currentEntry) > 0 {
		// Decide whether to keep the final entry
		if !(pVal == name && rVal == release && aVal == arch && oVal == origin && plVal == platform) {
			newLines = append(newLines, currentEntry...)
		}
	}

	// Join everything back and ensure at least one trailing newline
	finalContent := strings.Join(newLines, "\n")
	if !strings.HasSuffix(finalContent, "\n") {
		finalContent += "\n"
	}

	err = os.WriteFile("./APPINDEX", []byte(finalContent), 0644)
	if err != nil {
		log.Fatalf("Failed to update APPINDEX: %v", err)
	}
}

// CleanAppIndex cleans up any orphaned "C:" entries and collapses excessive newlines
func CleanAppIndex() error {
	// Read file contents
	content, err := os.ReadFile("./APPINDEX")
	if err != nil {
		return fmt.Errorf("failed to read APPINDEX: %v", err)
	}

	// Split the file content into lines
	lines := strings.Split(string(content), "\n")
	var newLines []string
	var currentEntry []string
	inEntry := false

	for _, line := range lines {
		line = strings.TrimSpace(line)

		// Start of an entry when we encounter a checksum
		if strings.HasPrefix(line, "C:") {
			// If we already have a valid entry, add it to newLines
			if inEntry && len(currentEntry) > 1 {
				newLines = append(newLines, currentEntry...)
			}
			currentEntry = []string{line}
			inEntry = true
		} else if inEntry && line == "" {
			// End of an entry
			if len(currentEntry) > 1 {
				newLines = append(newLines, currentEntry...)
				newLines = append(newLines, "") // Add a blank line to separate entries
			}
			currentEntry = nil
			inEntry = false
		} else if inEntry {
			// Continue adding lines to the current entry
			currentEntry = append(currentEntry, line)
		} else if line != "" {
			// Add non-entry lines (for extra safety)
			newLines = append(newLines, line)
		}
	}

	// In case the last entry was valid
	if inEntry && len(currentEntry) > 1 {
		newLines = append(newLines, currentEntry...)
	}

	// Collapse consecutive blank lines
	cleanedContent := strings.Join(newLines, "\n")
	cleanedContent = strings.ReplaceAll(cleanedContent, "\n\n\n", "\n\n")

	// Write the cleaned content back to the file
	err = os.WriteFile("./APPINDEX", []byte(cleanedContent), 0644)
	if err != nil {
		return fmt.Errorf("failed to write cleaned APPINDEX: %v", err)
	}

	fmt.Println("APPINDEX cleaned successfully.")
	return nil
}