package main

import (
	"crypto/x509"
	"encoding/pem"
	"image/color"
	"log"
	"os"
	certpair "pkcs-generator/modules/certpairs"
	"slices"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/canvas"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/layout"
	"fyne.io/fyne/v2/storage"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
)

var (
	windowSize fyne.Size = fyne.NewSize(700, 500)

	keyTextFilter []string = []string{".key", ".txt", ".pem"}
	crtTextFilter []string = []string{".crt", ".cer", ".txt", ".pem"}
)

func main() {
	a := app.NewWithID("nl.systemec.pkcs-generator")
	a.Settings().SetTheme(theme.DefaultTheme())

	w := a.NewWindow("Systemec PKCS-Generator")
	w.Resize(windowSize)

	var keyPath, certPath string

	// Labels to show selected filenames
	fileLabel1 := widget.NewLabel("No file selected")
	fileLabel2 := widget.NewLabel("No file selected")

	radioLabel1 := widget.NewLabel("Select which Sectigo Intermediate")

	// Certificate Keyfile
	keyBtn := widget.NewButton("Upload Private Key File", func() {
		keyDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) {
			if r != nil {
				fileLabel1.SetText(r.URI().Name())
				keyPath = r.URI().Path()
				r.Close()
			}
		}, w)
		keyFilter := storage.NewExtensionFileFilter(keyTextFilter)
		keyDiag.SetFilter(keyFilter)

		keyDiag.Resize(windowSize)
		keyDiag.Show()
	})

	// Certificate file
	certBtn := widget.NewButton("Upload Certificate File", func() {
		// Use NewFileOpen to get the dialog object
		certDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) {
			if r != nil {
				fileLabel2.SetText(r.URI().Name())
				certPath = r.URI().Path()
				r.Close()
			}
		}, w)
		certFilter := storage.NewExtensionFileFilter(crtTextFilter)
		certDiag.SetFilter(certFilter)

		// Resize the dialog
		certDiag.Resize(windowSize)
		certDiag.Show()
	})

	// Certificate Intermediate selector
	// Basic in form but can easily be expanded.
	var sectigo2025 bool
	caRadio := widget.NewRadioGroup([]string{"New Sectigo (2025-03-22+)", "Old Sectigo (2025-03-22-)"}, func(selected string) {
		switch selected {
		case "New Sectigo (2025-03-22+)":
			sectigo2025 = true
		case "Old Sectigo (2025-03-22-)":
			sectigo2025 = false
		default: //Fallback
			sectigo2025 = true
		}
	})
	caRadio.SetSelected("New Sectigo (2025+)") // default

	// Multiline entry
	textLabl := widget.NewLabel("Status will appear here...")
	textLabl.Selectable = true

	border := canvas.NewRectangle(color.Transparent)
	border.StrokeColor = color.RGBA{255, 255, 255, 255}
	border.StrokeWidth = 2
	border.CornerRadius = 5

	statusBox := container.NewStack(border, textLabl)

	actionBtn := widget.NewButton("Generate", func() {
		files := []string{keyPath, certPath}

		// The following structure is basic but can easily be expanded to fit more pairs
		// 0 = ROOT
		// 1 = CA
		// As per spec in the certpair module.
		var certPair []string
		if sectigo2025 {
			certPair = certpair.SectigoNewChain
		} else {
			certPair = certpair.SectigoOldChain
		}

		allPresent := true
		if slices.Contains(files, "") {
			allPresent = false
		}

		if !allPresent {
			textLabl.SetText("One or more files missing!")
			log.Println("One or more files missing!")
			return
		}

		respText, pfxData := integrityCheckAndGo(keyPath, certPath, certPair[1], certPair[0])

		if pfxData == nil {
			log.Println(respText)
			textLabl.SetText(respText)
			return
		}

		// Show Save File dialog immediately
		svDialog := dialog.NewFileSave(
			func(writer fyne.URIWriteCloser, err error) {
				if err != nil {
					dialog.ShowError(err, w)
					return
				}
				if writer == nil {
					// User cancelled
					return
				}

				_, writeErr := writer.Write(pfxData)
				if writeErr != nil {
					dialog.ShowError(writeErr, w)
					return
				}

				writer.Close()

				var dnText string = "PKCS file saved to: " + writer.URI().Path() + "\n" + respText

				textLabl.SetText(dnText)
			}, w)
		svDialog.Resize(windowSize)

		svDialog.SetFileName("certificate_store.pfx")
		svDialog.SetFilter(storage.NewExtensionFileFilter([]string{".pfx"}))

		svDialog.Show()
	})

	cancelBtn := widget.NewButton("Exit", func() {
		log.Println("Quitting...")
		os.Exit(0)
	})

	// CREATE LAYOUTS

	bottom := container.NewHBox(
		actionBtn,          // left
		layout.NewSpacer(), // flexible space
		cancelBtn,          // right
	)

	centerContent := container.NewVBox(
		widget.NewLabel("Select relevant files."),
		container.New(layout.NewGridLayout(2), keyBtn, fileLabel1),
		container.New(layout.NewGridLayout(2), certBtn, fileLabel2),
		widget.NewLabel("\n"),
		container.New(layout.NewGridLayout(2), radioLabel1, caRadio),
		layout.NewSpacer(),  // optional flexible space
		statusBox,           // Add the referenced text container
		widget.NewLabel(""), // Add empty line for space
	)

	content := container.NewBorder(
		nil,      // top
		bottom,   // bottom
		nil, nil, // left, right
		centerContent,
	)

	w.SetContent(content)
	w.ShowAndRun()
}

func readFile(path string) []byte {
	rawData, err := os.ReadFile(path)
	if err != nil {
		log.Printf("Error reading contents of %s, %e", path, err)
		return nil
	} else {
		return rawData
	}
}

func parsePrivateKey(keyData []byte) any {
	block, _ := pem.Decode(keyData)
	if block == nil {
		log.Fatal("failed to decode key PEM")
	}

	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		log.Fatal(err)
	}

	return privateKey
}

func parseX509(certData []byte) *x509.Certificate {
	block, _ := pem.Decode(certData)
	if block == nil {
		log.Fatal("failed to parse PEM block")
	}

	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		log.Fatal(err)
	}
	return cert
}

func integrityCheckAndGo(keyPath, certPath, caRootString, caCertString string) (string, []byte) {
	if _, err := os.Stat(keyPath); err != nil {
		log.Printf("Error checking Private Keyfile, %e", err)
		return "Error...", nil
	}

	if _, err := os.Stat(certPath); err != nil {
		log.Printf("Error checking Certificate file, %e", err)
		return "Error...", nil
	}

	keyData := readFile(keyPath)   // Read the private key file (PEM/DER)
	certData := readFile(certPath) // Read the certificate file (PEM/DER)

	key := parsePrivateKey(keyData) // Convert bytes to Go private key object
	cert := parseX509(certData)     // Convert bytes to Go x509.Certificate
	caCert := parseX509([]byte(caCertString))
	rootCert := parseX509([]byte(caRootString))

	checkPublicKey(cert) // Print the information about the key

	if checkCertKeyPair(cert.PublicKey, key) {
		log.Println("Private key matches certificate")
	} else {
		return "Private key does NOT match certificate", nil
	}

	caCertList := []*x509.Certificate{caCert, rootCert}

	msg, pfxData, err := generatePKCS12(key, cert, caCertList)
	if err != nil {
		return msg, nil
	}
	return msg, pfxData
}
