package main

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"log"
	"net"
	"os"
	"strings"
	"time"

	"github.com/spf13/viper"
)

func CheckUpgrades() {
	v := viper.GetString("version")
	if standaloneVersion == "" {
		gitVersion := controlCommand("git-version", true)
		if gitVersion != "" {
			viper.Set("version", strings.TrimSpace(gitVersion))
			_ = viper.WriteConfig()
		}
	} else if v != standaloneVersion {
		viper.Set("version", standaloneVersion)
		_ = viper.WriteConfig()
	}

	changed := CheckUpgrade_01_CeremonyHSM()

	if changed {
		time.Sleep(2 * time.Second)
		log.Println("Applying updated configuration...")
		controlCommand("apply", false)
		time.Sleep(2 * time.Second)
		log.Println("Updating CRL links if needed...")
		controlCommand("check-crl", false)
		time.Sleep(2 * time.Second)
		log.Println("Restarting boulder containers...")
		controlCommand("boulder-restart", false)
	}
}

func readFileAsString(filename string) string {
	read, err := os.ReadFile(filename)
	if err != nil {
		log.Printf("**** Could not read '%s': %s\n", filename, err.Error())
		log.Println("**** ABORT MIGRATION ****")
		time.Sleep(1 * time.Minute)
		os.Exit(1)
	}

	return string(read)
}

func controlCommand(command string, ignoreError bool) string {
	conn, err := net.Dial("tcp", "control:3030")
	if err != nil {
		if ignoreError {
			return ""
		}
		log.Println("**** Failed to connect to control container!")
		time.Sleep(1 * time.Minute)
		os.Exit(1)
	}
	defer func() { _ = conn.Close() }()

	_, _ = fmt.Fprint(conn, command+"\n")

	reader := bufio.NewReader(conn)
	message, err := io.ReadAll(reader)
	if err != nil {
		log.Printf("**** Failed to read response from control container: %s\n", err.Error())
		time.Sleep(1 * time.Minute)
		os.Exit(1)
	}

	if len(message) >= 4 {
		tail := message[len(message)-4:]
		if strings.Compare(string(tail), "\nok\n") == 0 {
			msg := message[0 : len(message)-4]
			log.Printf("**** Message from control server: '%s'", msg)
		}
	}

	return string(message)
}

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

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

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

	err = destinationFile.Sync()
	if err != nil {
		return err
	}

	return nil
}

// Check if we should upgrade to using the Ceremony tool and store keys on SoftHSM (January 2025).
func CheckUpgrade_01_CeremonyHSM() bool {
	baseDir := "/opt/labca/data/"
	prevRootCert := baseDir + "root-ca.pem"
	if _, err := os.Stat(prevRootCert); errors.Is(err, fs.ErrNotExist) {
		baseDir = "/go/src/labca/data/"
		prevRootCert = baseDir + "root-ca.pem"
		if _, err := os.Stat(prevRootCert); errors.Is(err, fs.ErrNotExist) {
			return false
		}
	}

	log.Println("**** BEGIN MIGRATION: upgrade01 ****")

	rootCertFile := fmt.Sprintf("%sroot-01-cert.pem", CERT_FILES_PATH)
	if _, err := os.Stat(rootCertFile); !errors.Is(err, fs.ErrNotExist) {
		log.Printf("**** File %s already exists!\n", rootCertFile)
		log.Println("**** ABORT MIGRATION ****")
		time.Sleep(1 * time.Minute)
		os.Exit(1)
	}

	prevRootKey := baseDir + "root-ca.key"
	if _, err := os.Stat(prevRootKey); errors.Is(err, fs.ErrNotExist) {
		log.Println("**** Root key file not present on the system: cannot upgrade automatically!")
		log.Println("**** Please do a fresh install of LabCA and import / upload the root certificate and key.")
		log.Println("**** ABORT MIGRATION ****")
		time.Sleep(1 * time.Minute)
		os.Exit(1)
	}

	// Migrate root certificate and key
	ci := &CertificateInfo{IsRoot: true}
	ci.Initialize()
	ci.IsRoot = true
	ci.CreateType = "upload"
	ci.Certificate = readFileAsString(prevRootCert)
	ci.Key = readFileAsString(prevRootKey)
	prevRootCRL := baseDir + "root-ca.crl"
	if _, err := os.Stat(prevRootCRL); !errors.Is(err, fs.ErrNotExist) {
		ci.CRL = readFileAsString(prevRootCRL)
		_ = copyFile(prevRootCRL, strings.ReplaceAll(rootCertFile, "-cert.", "-crl."))
	}

	if err := ci.Create("root-01", false); err != nil {
		log.Printf("**** Could not convert previous root certificate and key: %s\n", err.Error())
		log.Println("**** ABORT MIGRATION ****")
		time.Sleep(1 * time.Minute)
		os.Exit(1)
	}

	// Migrate issuer certificate and key
	ci = &CertificateInfo{IsRoot: false}
	ci.Initialize()
	ci.IsRoot = false
	ci.CreateType = "upload"
	prevIssuerCert := baseDir + "issuer/ca-int.pem"
	ci.Certificate = readFileAsString(prevIssuerCert)
	prevIssuerKey := baseDir + "issuer/ca-int.key"
	ci.Key = readFileAsString(prevIssuerKey)
	ci.CRL = ""

	if err := ci.Create("issuer-01", false); err != nil {
		log.Printf("**** Could not convert previous issuer certificate and key: %s\n", err.Error())
		log.Println("**** ABORT MIGRATION ****")
		time.Sleep(1 * time.Minute)
		os.Exit(1)
	}

	_ = os.Rename(prevRootCert, prevRootCert+"_backup")
	_ = os.Rename(prevRootKey, prevRootKey+"_backup")
	_ = os.Rename(prevRootCRL, prevRootCRL+"_backup")
	_ = os.Rename(prevIssuerCert, prevIssuerCert+"_backup")
	_ = os.Rename(prevIssuerKey, prevIssuerKey+"_backup")

	log.Println("**** END MIGRATION ****")
	return true
}
