/*
Copyright (c) Edgeless Systems GmbH

SPDX-License-Identifier: AGPL-3.0-only
*/

package main

import (
	"context"
	"flag"
	"fmt"
	"log/slog"
	"net"
	"os"
	"sync"

	"github.com/spf13/afero"

	"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/deploy"
	"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/info"
	"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/logcollector"
	"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/metadata"
	"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/metadata/cloudprovider"
	"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/metadata/fallback"
	"github.com/edgelesssys/constellation/v2/debugd/internal/debugd/server"
	"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer"
	"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer/streamer"
	awscloud "github.com/edgelesssys/constellation/v2/internal/cloud/aws"
	azurecloud "github.com/edgelesssys/constellation/v2/internal/cloud/azure"
	platform "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
	gcpcloud "github.com/edgelesssys/constellation/v2/internal/cloud/gcp"
	openstackcloud "github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
	qemucloud "github.com/edgelesssys/constellation/v2/internal/cloud/qemu"
	"github.com/edgelesssys/constellation/v2/internal/logger"
)

const debugBanner = `
*****************************************
    THIS IS A CONSTELLATION DEBUG IMAGE.
    DO NOT USE IN PRODUCTION.
*****************************************
`

func main() {
	verbosity := flag.Int("v", 0, logger.CmdLineVerbosityDescription)
	flag.Parse()

	log := logger.NewJSONLogger(logger.VerbosityFromInt(*verbosity))
	fs := afero.NewOsFs()
	streamer := streamer.New(fs)
	filetransferer := filetransfer.New(log.WithGroup("filetransfer"), streamer, filetransfer.DontShowProgress)
	serviceManager := deploy.NewServiceManager(log.WithGroup("serviceManager"))

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	wg := &sync.WaitGroup{}

	csp := os.Getenv("CONSTEL_CSP")

	var fetcher *cloudprovider.Fetcher
	switch platform.FromString(csp) {
	case platform.AWS:
		meta, err := awscloud.New(ctx)
		if err != nil {
			log.With(slog.Any("error", err)).Error("Failed to initialize AWS metadata")
			os.Exit(1)
		}
		fetcher = cloudprovider.New(meta)

	case platform.Azure:
		meta, err := azurecloud.New(ctx)
		if err != nil {
			log.With(slog.Any("error", err)).Error("Failed to initialize Azure metadata")
			os.Exit(1)
		}
		fetcher = cloudprovider.New(meta)

	case platform.GCP:
		meta, err := gcpcloud.New(ctx)
		if err != nil {
			log.With(slog.Any("error", err)).Error("Failed to initialize GCP metadata")
			os.Exit(1)
		}
		defer meta.Close()
		fetcher = cloudprovider.New(meta)

	case platform.OpenStack:
		meta, err := openstackcloud.New(ctx)
		if err != nil {
			log.With(slog.Any("error", err)).Error("Failed to initialize OpenStack metadata")
			os.Exit(1)
		}
		fetcher = cloudprovider.New(meta)
	case platform.QEMU:
		fetcher = cloudprovider.New(qemucloud.New())

	default:
		log.Error(fmt.Sprintf("Unknown / unimplemented cloud provider CONSTEL_CSP=%v. Using fallback", csp))
		fetcher = fallback.NewFallbackFetcher()
	}

	infoMap := info.NewMap()
	infoMap.RegisterOnReceiveTrigger(
		logcollector.NewStartTrigger(ctx, wg, platform.FromString(csp), fetcher, log.WithGroup("logcollector")),
	)

	download := deploy.New(log.WithGroup("download"), &net.Dialer{}, serviceManager, filetransferer, infoMap)

	sched := metadata.NewScheduler(log.WithGroup("scheduler"), fetcher, download)
	serv := server.New(log.WithGroup("server"), serviceManager, filetransferer, infoMap)

	writeDebugBanner(log)

	sched.Start(ctx, wg)
	server.Start(log, wg, serv)
	wg.Wait()
}

func writeDebugBanner(log *slog.Logger) {
	tty, err := os.OpenFile("/dev/ttyS0", os.O_WRONLY, os.ModeAppend)
	if err != nil {
		log.With(slog.Any("error", err)).Error("Unable to open /dev/ttyS0 for printing banner")
		return
	}
	defer tty.Close()
	if _, err := fmt.Fprint(tty, debugBanner); err != nil {
		log.With(slog.Any("error", err)).Error("Unable to print to /dev/ttyS0")
	}
}
