package storage

import (
	"crypto/ed25519"
	"crypto/rand"
	"database/sql"

	"git.mstar.dev/mstar/goutils/other"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
	"gorm.io/gorm"

	"git.mstar.dev/mstar/linstrom/config"
	"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
	"git.mstar.dev/mstar/linstrom/storage-new/models"
)

func InsertSelf() error {
	if err := insertRoles(); err != nil {
		return other.Error("storage", "failed to save/update default roles", err)
	}
	duck, err := insertDefaultDuck()
	if err != nil {
		return other.Error("storage", "failed to save/update default duck", err)
	}
	server, err := insertServer(duck)
	if err != nil {
		return other.Error("storage", "failed to save/update self server", err)
	}
	log.Debug().Any("server", server).Send()
	user, err := insertUser(server)
	if err != nil {
		return other.Error("storage", "failed to save/update self user", err)
	}
	if err = insertUserPronoun(user); err != nil {
		return other.Error("storage", "failed to save/update self user pronoun", err)
	}
	return nil
}

func insertRoles() error {
	return dbgen.Role.Save(models.AllDefaultRoles...)
}

func insertDefaultDuck() (*models.MediaMetadata, error) {
	dbDuck, err := dbgen.MediaMetadata.Where(dbgen.MediaMetadata.Location.Eq(models.DefaultDuckLocationName)).
		First()
	if err == nil {
		return dbDuck, nil
	} else if err != gorm.ErrRecordNotFound {
		return nil, err
	}
	duck := models.MediaMetadata{
		ID: uuid.NewString(),
		// NOTE: Default duck technically belongs to the server
		// but to avoid cyclic dependency, declare it as unowned
		OwnedById: sql.NullString{Valid: false},
		Remote:    false,
		Location:  models.DefaultDuckLocationName,
		Type:      "image/webp",
		Name:      "default-duck.webp",
		AltText:   "a greyscale image of a pidgeon on some smooth surface, likely a liquid. It is captioned with the text \"Duck\"",
		Blurred:   false,
	}
	if err := dbgen.MediaMetadata.Create(&duck); err != nil {
		return nil, err
	}
	return &duck, nil
}

func insertServer(duck *models.MediaMetadata) (*models.RemoteServer, error) {
	dbServer, err := dbgen.RemoteServer.Where(dbgen.RemoteServer.ID.Eq(1)).First()
	if err == nil {
		return dbServer, nil
	} else if err != gorm.ErrRecordNotFound {
		return nil, err
	}
	server := models.RemoteServer{
		Model: gorm.Model{
			ID: 1,
		},
		ServerType: models.ServerSoftwareLinstrom,
		Domain:     config.GlobalConfig.General.GetFullDomain(),
		Name:       config.GlobalConfig.Self.ServerDisplayName,
		Icon:       duck,
		IconId:     sql.NullString{String: duck.ID, Valid: true},
		IsSelf:     true,
	}
	err = dbgen.RemoteServer.Save(&server)
	if err != nil {
		return nil, err
	}
	return &server, nil
}

func insertUser(server *models.RemoteServer) (*models.User, error) {
	dbUser, err := dbgen.User.GetByUsername("linstrom")
	if err == nil {
		return dbUser, nil
	}
	if err != gorm.ErrRecordNotFound {
		return nil, err
	}
	publicKey, privateKey, err := ed25519.GenerateKey(nil)
	pkeyId := make([]byte, 64)
	_, err = rand.Read(pkeyId)
	if err != nil {
		return nil, other.Error(
			"storage",
			"failed to generate passkey ID for linstrom account",
			err,
		)
	}
	user := models.User{
		Username:             "linstrom",
		Server:               *server,
		ServerId:             server.ID,
		DisplayName:          config.GlobalConfig.Self.ServerActorDisplayName,
		Description:          "The default linstrom server user",
		IsBot:                true,
		Icon:                 nil,
		IconId:               sql.NullString{Valid: false},
		Background:           nil,
		BackgroundId:         sql.NullString{Valid: false},
		Banner:               nil,
		BannerId:             sql.NullString{Valid: false},
		Indexable:            false,
		PublicKey:            publicKey,
		PrivateKey:           privateKey,
		Verified:             true,
		FinishedRegistration: true,
		PasskeyId:            pkeyId,
	}
	err = dbgen.User.Save(&user)
	if err != nil {
		return nil, err
	}
	return &user, err
}

func insertUserPronoun(user *models.User) error {
	_, err := dbgen.UserToPronoun.Where(dbgen.UserToPronoun.UserId.Eq(user.ID)).
		Where(dbgen.UserToPronoun.Pronoun.Eq("Yes")).First()
	switch err {
	case nil:
		return nil
	case gorm.ErrRecordNotFound:
		return dbgen.UserToPronoun.Create(
			&models.UserToPronoun{UserId: user.ID, User: *user, Pronoun: "Yes"},
		)
	default:
		return err
	}
}
