//go:generate swagger generate spec

package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"

	"github.com/porter-dev/porter/api/server"
	"github.com/porter-dev/porter/api/server/router"

	"github.com/porter-dev/porter/api/authmanagement"

	"golang.org/x/sync/errgroup"

	"github.com/porter-dev/porter/internal/telemetry"

	"github.com/porter-dev/porter/api/server/shared/config"
	"github.com/porter-dev/porter/api/server/shared/config/loader"
	"github.com/porter-dev/porter/internal/models"
	"gorm.io/gorm"
)

// Version will be linked by an ldflag during build
var Version string = "dev-ce"

func main() {
	g, ctx := errgroup.WithContext(context.Background())
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	var versionFlag, authServiceFlag bool
	flag.BoolVar(&versionFlag, "version", false, "print version and exit")
	flag.BoolVar(&authServiceFlag, "auth", false, "run auth service instead of porter api")
	flag.Parse()

	// Exit safely when version is used
	if versionFlag {
		fmt.Println(Version)
		os.Exit(0)
	}

	cl := loader.NewEnvLoader(Version)

	config, err := cl.LoadConfig()
	if err != nil {
		log.Fatal("Config loading failed: ", err)
	}

	config.Logger.Info().Msg("Initializing data")
	err = initData(config)
	if err != nil {
		log.Fatal("Data initialization failed: ", err)
	}
	config.Logger.Info().Msg("Initialed data")

	tracer, err := telemetry.InitTracer(ctx, config.TelemetryConfig)
	if err != nil {
		config.Logger.Fatal().Err(err).Msg("Error initializing telemetry")
	}
	defer tracer.Shutdown()

	if authServiceFlag {
		g.Go(func() error {
			a, err := authmanagement.NewService()
			if err != nil {
				return fmt.Errorf("failed to initialize AuthManagement server: %s", err.Error())
			}

			config.Logger.Info().Msgf("Starting AuthManagement server on port %d", a.Config.Port)
			if err := a.ListenAndServe(ctx); err != nil && err != http.ErrServerClosed {
				return fmt.Errorf("AuthManagement server failed: %s", err.Error())
			}
			config.Logger.Info().Msg("Shutting down AuthManagement server")
			return nil
		})
	} else {
		config.Logger.Info().Msg("Creating API router")
		appRouter := router.NewAPIRouter(config)
		config.Logger.Info().Msg("Created API router")

		p := server.PorterAPIServer{
			Port:       config.ServerConf.Port,
			Router:     appRouter,
			ServerConf: config.ServerConf,
		}

		g.Go(func() error {
			config.Logger.Info().Msgf("Starting PorterAPI server on port %d", config.ServerConf.Port)
			if err := p.ListenAndServe(ctx); err != nil && err != http.ErrServerClosed {
				return fmt.Errorf("PorterAPI server failed: %s", err.Error())
			}
			config.Logger.Info().Msg("Shutting down PorterAPI server")
			return nil
		})
	}

	termFunc := func() error {
		termChan := make(chan os.Signal, 1)
		signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)

		select {
		case <-termChan:
			config.Logger.Info().Msg("Process shutdown signal received")
			cancel()
			return nil
		case <-ctx.Done():
			return nil
		}
	}

	g.Go(termFunc)

	err = g.Wait()
	if err != nil {
		config.Logger.Fatal().Err(err).Msg("Received server error")
	}
}

const (
	defaultProjectName = "default"
	defaultClusterName = "cluster-1"
)

func initData(conf *config.Config) error {
	// if the config specifies in-cluster connections are permitted, create a new project with a
	// cluster that uses the in-cluster config. this will be the default project for this instance.
	if conf.ServerConf.InitInCluster {
		l := conf.Logger
		l.Debug().Msg("in-cluster config variable set: checking for default project and cluster")

		// look for a project with id 1 with name of defaultProjectName
		_, err := conf.Repo.Project().ReadProject(1)

		if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
			l.Debug().Msg("default project not found: attempting creation")

			_, err = conf.Repo.Project().CreateProject(&models.Project{
				Name: defaultProjectName,
			})

			if err != nil {
				return err
			}

			l.Debug().Msg("successfully created default project")
		} else if err != nil {
			return err
		}

		// determine if there are any clusters in the project already
		clusters, err := conf.Repo.Cluster().ListClustersByProjectID(1)
		if err != nil {
			return err
		}

		// if there are already clusters in the project, determine if any of the clusters are using an
		// in-cluster auth mechanism
		if len(clusters) > 0 {
			for _, cluster := range clusters {
				if cluster.AuthMechanism == models.InCluster {
					return nil
				}
			}
		}

		_, err = conf.Repo.Cluster().ReadCluster(1, 1)

		if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
			l.Debug().Msg("default cluster not found: attempting creation")

			_, err = conf.Repo.Cluster().CreateCluster(&models.Cluster{
				Name:                defaultClusterName,
				AuthMechanism:       models.InCluster,
				ProjectID:           1,
				MonitorHelmReleases: true,
			})

			if err != nil {
				return err
			}

			l.Debug().Msg("successfully created default cluster")
		} else if err != nil {
			return err
		}
	}

	return nil
}
