package sshkeys

import (
	"context"

	"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
	"github.com/FAU-CDI/wisski-distillery/internal/models"
	"github.com/gliderlabs/ssh"
	"github.com/tkw1536/pkglib/reflectx"
)

func (ssh2 *SSHKeys) TableInfo() component.TableInfo {
	return component.TableInfo{
		Model: reflectx.TypeFor[models.Keys](),
		Name:  models.KeysTable,
	}
}

// Keys returns a list of keys for the given user
func (ssh2 *SSHKeys) Keys(ctx context.Context, user string) ([]models.Keys, error) {
	// the empty user has no key
	if user == "" {
		return nil, nil
	}

	// get the table
	table, err := ssh2.dependencies.SQL.QueryTable(ctx, ssh2)
	if err != nil {
		return nil, err
	}

	var keys []models.Keys

	// make a query to find all keys (in the underlying model)
	query := table.Find(&keys, &models.Keys{User: user})
	if query.Error != nil {
		return nil, query.Error
	}

	return keys, nil
}

// Add adds a new key to the given user, unless it already exists
func (ssh2 *SSHKeys) Add(ctx context.Context, user string, comment string, key ssh.PublicKey) error {
	// check that the given user exists
	{
		_, err := ssh2.dependencies.Auth.User(ctx, user)
		if err != nil {
			return err
		}
	}

	// fetch all the keys
	keys, err := ssh2.Keys(ctx, user)
	if err != nil {
		return err
	}

	pks := make([]ssh.PublicKey, 0, len(keys))
	for _, key := range keys {
		if pk := key.PublicKey(); pk != nil {
			pks = append(pks, pk)
		}
	}

	// key already exists
	if KeyOneOf(pks, key) {
		return nil
	}

	// create a new key with the given comment
	mk := models.Keys{
		User:    user,
		Comment: comment,
	}
	mk.SetPublicKey(key)

	// get the table
	table, err := ssh2.dependencies.SQL.QueryTable(ctx, ssh2)
	if err != nil {
		return err
	}

	// create the key instance
	return table.Create(&mk).Error
}

// Remove removes a given publuc key from a user.
func (ssh2 *SSHKeys) Remove(ctx context.Context, user string, key ssh.PublicKey) error {
	// find all the keys for the given user
	keys, err := ssh2.Keys(ctx, user)
	if err != nil {
		return err
	}

	// iterate and find all the public keys
	var pks []uint
	for _, candidate := range keys {
		if ssh.KeysEqual(candidate.PublicKey(), key) {
			pks = append(pks, candidate.Pk)
		}
	}

	// nothing to delete
	if len(pks) == 0 {
		return nil
	}

	// query the table again
	table, err := ssh2.dependencies.SQL.QueryTable(ctx, ssh2)
	if err != nil {
		return nil
	}

	// and do the delete
	return table.Where("pk in ?", pks).Delete(&models.Keys{}).Error
}

func (ssh2 *SSHKeys) OnUserDelete(ctx context.Context, user *models.User) error {
	// get the table
	table, err := ssh2.dependencies.SQL.QueryTable(ctx, ssh2)
	if err != nil {
		return err
	}

	// delete all keys for the user
	return table.Delete(&models.Keys{}, &models.Keys{User: user.User}).Error
}
