package main

import (
	"bufio"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"
)

var key []byte

func main() {
	fmt.Println("OSEContainers ENCRYPTED DATABASE TOOL 0.1.0")
	fmt.Println("")
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Voer sleutelcode in: ")
	inputKey, _ := reader.ReadString('\n')
	key = []byte(strings.TrimSpace(inputKey))

	dbFile := "osecdatabase.db"
	if _, err := os.Stat(dbFile); os.IsNotExist(err) {
		createInitialDatabase(dbFile)
	}

	for {
		fmt.Println("")
		fmt.Print("Voer commando in: (HELP voor hulp EXIT voor aflsuiten): ")
		command, _ := reader.ReadString('\n')
		command = strings.TrimSpace(command)
		parts := strings.Split(command, " ")

		switch parts[0] {
		case "add":
			if len(parts) < 4 {
				fmt.Println("Ongeldig commando. Gebruik: add [container] [locatie] [waarde1,waarde2,...]")
				continue
			}
			addRecord(dbFile, parts[1], parts[2], parts[3])
		case "remove":
			if len(parts) < 3 {
				fmt.Println("Ongeldig commando. Gebruik: remove [container] [locatie]")
				continue
			}
			removeRecord(dbFile, parts[1], parts[2])
		case "read":
			if len(parts) < 2 {
				fmt.Println("Ongeldig commando. Gebruik: read all or read [container]")
				continue
			}
			if parts[1] == "all" {
				readAllRecords(dbFile)
			} else {
				readBucketRecords(dbFile, parts[1])
			}
		case "help":
			printHelp()
		case "exit":
			os.Exit(0)
		default:
			fmt.Println("Ongeldige commando, type HELP voor hulp")
		}
	}
}

func createInitialDatabase(filename string) {
	data := make(map[string]map[string]string)
	data["system"] = map[string]string{"ose_db_ver": "0.1.0"}
	saveData(filename, data)
}

func saveData(filename string, data map[string]map[string]string) {
	// Create a backup file before saving changes
	backupFilename := filename + ".bak"
	err := copyFile(filename, backupFilename)
	if err != nil {
		fmt.Println("ERROR: Could not create backup file:", err)
		return
	}

	encryptedData, err := encryptData(data)
	if err != nil {
		fmt.Println("ERROR: Could not encrypt data:", err)
		return
	}
	err = os.WriteFile(filename, encryptedData, 0644)
	if err != nil {
		fmt.Println("ERROR: Could not save data:", err)
		// Restore the backup file if saving fails
		_ = os.Rename(backupFilename, filename)
		return
	}
	fmt.Println("Data saved successfully.")
}

func copyFile(src, dst string) error {
	sourceFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	destinationFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destinationFile.Close()

	_, err = io.Copy(destinationFile, sourceFile)
	if err != nil {
		return err
	}
	return nil
}

func loadData(filename string) (map[string]map[string]string, error) {
	encryptedData, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	data, err := decryptData(encryptedData)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func encryptData(data map[string]map[string]string) ([]byte, error) {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return nil, err
	}
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	ciphertext := make([]byte, aes.BlockSize+len(jsonData))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, err
	}
	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], jsonData)
	return ciphertext, nil
}

func decryptData(ciphertext []byte) (map[string]map[string]string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	if len(ciphertext) < aes.BlockSize {
		return nil, fmt.Errorf("ciphertext te kort")
	}
	iv := ciphertext[:aes.BlockSize]
	ciphertext = ciphertext[aes.BlockSize:]
	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(ciphertext, ciphertext)
	var data map[string]map[string]string
	err = json.Unmarshal(ciphertext, &data)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func addRecord(filename, bucket, record, values string) {
	data, err := loadData(filename)
	if err != nil {
		fmt.Println("ERROR: Kon data niet laden:", err, "| Heeft u de correcte sleutelcode?")
		return
	}
	if _, ok := data[bucket]; !ok {
		data[bucket] = make(map[string]string)
	}
	data[bucket][record] = values
	saveData(filename, data)
	fmt.Println("Gegevens aan container toegevoegd")
}

func removeRecord(filename, bucket, record string) {
	data, err := loadData(filename)
	if err != nil {
		fmt.Println("ERROR: Kon gegevens niet laden:", err, "| Heeft u de correcte sleutelcode?")
		return
	}
	if _, ok := data[bucket]; ok {
		delete(data[bucket], record)
		saveData(filename, data)
		fmt.Println("Gegevens van container verwijderd")
	} else {
		fmt.Println("Container niet gevonden")
	}
}

func readAllRecords(filename string) {
	data, err := loadData(filename)
	if err != nil {
		fmt.Println("ERROR: Kon gegevens niet laden:", err, "| Heeft u de correcte sleutelcode?")
		return
	}
	for bucket, records := range data {
		fmt.Println("Container:", bucket)
		for record, values := range records {
			fmt.Printf("  Locatie: %s, Waardes: %s\n", record, values)
		}
	}
}

func readBucketRecords(filename, bucket string) {
	data, err := loadData(filename)
	if err != nil {
		fmt.Println("ERROR: Kon gegevens niet laden:", err, "| Heeft u de correcte sleutelcode?")
		return
	}
	if records, ok := data[bucket]; ok {
		fmt.Println("Container:", bucket)
		for record, values := range records {
			fmt.Printf("  Locatie: %s, Waardes: %s\n", record, values)
		}
	} else {
		fmt.Println("Container niet gevonden")
	}
}
func printHelp() {
	fmt.Println("")
	fmt.Println("=== OSEcontainers DATABASE TOOL | HELP PAGINA ===")
	fmt.Println("")
	fmt.Println("Commando's voor OSE_DB_TOOL & databasebeheer:")
	fmt.Println("")
	fmt.Println("add [container] [locatie] [waarde1,waarde2,...]")
	fmt.Println("\t| Gebruik ADD voor het toevoegen van containers, locaties en hun waardes")
	fmt.Println("")
	fmt.Println("remove [container] [locatie]")
	fmt.Println("\t| Gebruik REMOVE voor het verwijderen van locaties en hun waardes")
	fmt.Println("")
	fmt.Println("read all")
	fmt.Println("\t| Gebruik read all voor het zien van alle containers met hun locaties en waardes")
	fmt.Println("")
	fmt.Println("read [container]")
	fmt.Println("\t| Gebruik read [container] om alle locaties en waardes van een specifieke container te zien")
	fmt.Println("")
	fmt.Println("help : Print deze pagina | exit : Sluit de tool applicatie af")
	fmt.Println("")
	fmt.Println("				===============================										")
	fmt.Println("Notities:")
	fmt.Println("Meerdere waarden op een locatie moeten met een comma (,) gescheiden worden zonder spaties. Deze tool geeft je de vrijheid alles aan te passen. Maar OSE is specifiek in de syntax houd daar rekening mee.")
	fmt.Println("")
}