package initialise

import (
	"context"
	_ "embed"
	"fmt"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"github.com/zitadel/logging"

	"github.com/zitadel/zitadel/internal/database"
	"github.com/zitadel/zitadel/internal/database/dialect"
)

func newZitadel() *cobra.Command {
	return &cobra.Command{
		Use:   "zitadel",
		Short: "initialize ZITADEL internals",
		Long: `initialize ZITADEL internals.

Prerequisites:
- cockroachDB or postgreSQL with user and database
`,
		Run: func(cmd *cobra.Command, args []string) {
			config := MustNewConfig(viper.GetViper())
			err := verifyZitadel(cmd.Context(), config.Database)
			logging.OnError(err).Fatal("unable to init zitadel")
		},
	}
}

func VerifyZitadel(ctx context.Context, db *database.DB, config database.Config) error {
	err := ReadStmts(config.Type())
	if err != nil {
		return err
	}

	logging.WithFields().Info("verify system")
	if err := exec(db, fmt.Sprintf(createSystemStmt, config.Username()), nil); err != nil {
		return err
	}

	logging.WithFields().Info("verify encryption keys")
	if err := createEncryptionKeys(ctx, db); err != nil {
		return err
	}

	logging.WithFields().Info("verify projections")
	if err := exec(db, fmt.Sprintf(createProjectionsStmt, config.Username()), nil); err != nil {
		return err
	}

	logging.WithFields().Info("verify eventstore")
	if err := exec(db, fmt.Sprintf(createEventstoreStmt, config.Username()), nil); err != nil {
		return err
	}

	logging.WithFields().Info("verify events tables")
	if err := createEvents(ctx, db); err != nil {
		return err
	}

	logging.WithFields().Info("verify system sequence")
	if err := exec(db, createSystemSequenceStmt, nil); err != nil {
		return err
	}

	logging.WithFields().Info("verify unique constraints")
	if err := exec(db, createUniqueConstraints, nil); err != nil {
		return err
	}

	return nil
}

func verifyZitadel(ctx context.Context, config database.Config) error {
	logging.WithFields("database", config.DatabaseName()).Info("verify zitadel")

	db, err := database.Connect(config, false, dialect.DBPurposeQuery)
	if err != nil {
		return err
	}

	if err := VerifyZitadel(ctx, db, config); err != nil {
		return err
	}

	return db.Close()
}

func createEncryptionKeys(ctx context.Context, db *database.DB) error {
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	if _, err = tx.Exec(createEncryptionKeysStmt); err != nil {
		tx.Rollback()
		return err
	}

	return tx.Commit()
}

func createEvents(ctx context.Context, db *database.DB) (err error) {
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			rollbackErr := tx.Rollback()
			logging.OnError(rollbackErr).Debug("rollback failed")
			return
		}
		err = tx.Commit()
	}()

	// if events already exists events2 is created during a setup job
	var count int
	row := tx.QueryRow("SELECT count(*) FROM information_schema.tables WHERE table_schema = 'eventstore' AND table_name like 'events%'")
	if err = row.Scan(&count); err != nil {
		return err
	}
	if row.Err() != nil || count >= 1 {
		return row.Err()
	}
	_, err = tx.Exec(createEventsStmt)
	return err
}