package db

import (
	"encoding/hex"
	"time"

	"github.com/go-webauthn/webauthn/webauthn"
)

type WebAuthnCredential struct {
	ID                 uint `gorm:"primaryKey"`
	Name               string
	UserID             uint
	User               User
	CredentialID       binaryData `gorm:"type:binary_data"`
	PublicKey          binaryData `gorm:"type:binary_data"`
	AttestationType    string
	AAGUID             binaryData `gorm:"type:binary_data"`
	SignCount          uint32
	CloneWarning       bool
	FlagUserPresent    bool
	FlagUserVerified   bool
	FlagBackupEligible bool
	FlagBackupState    bool
	CreatedAt          int64
	LastUsedAt         int64
}

func (*WebAuthnCredential) TableName() string {
	return "webauthn"
}

func GetAllWACredentialsForUser(userID uint) ([]webauthn.Credential, error) {
	var creds []WebAuthnCredential
	err := db.Where("user_id = ?", userID).Find(&creds).Error
	if err != nil {
		return nil, err
	}
	webCreds := make([]webauthn.Credential, len(creds))
	for i, cred := range creds {
		webCreds[i] = webauthn.Credential{
			ID:              cred.CredentialID,
			PublicKey:       cred.PublicKey,
			AttestationType: cred.AttestationType,
			Authenticator: webauthn.Authenticator{
				AAGUID:       cred.AAGUID,
				SignCount:    cred.SignCount,
				CloneWarning: cred.CloneWarning,
			},
			Flags: webauthn.CredentialFlags{
				UserPresent:    cred.FlagUserPresent,
				UserVerified:   cred.FlagUserVerified,
				BackupEligible: cred.FlagBackupEligible,
				BackupState:    cred.FlagBackupState,
			},
		}
	}
	return webCreds, nil
}

func GetAllCredentialsForUser(userID uint) ([]WebAuthnCredential, error) {
	var creds []WebAuthnCredential
	err := db.Where("user_id = ?", userID).Find(&creds).Error
	return creds, err
}

func GetUserByCredentialID(credID binaryData) (*User, error) {
	var credential WebAuthnCredential
	var err error

	switch db.Name() {
	case "postgres":
		hexCredID := hex.EncodeToString(credID)
		if err = db.Preload("User").Where("credential_id = decode(?, 'hex')", hexCredID).First(&credential).Error; err != nil {
			return nil, err
		}
	case "mysql", "sqlite":
		hexCredID := hex.EncodeToString(credID)
		if err = db.Preload("User").Where("credential_id = unhex(?)", hexCredID).First(&credential).Error; err != nil {
			return nil, err
		}
	}

	return &credential.User, err
}

func GetCredentialByIDDB(id uint) (*WebAuthnCredential, error) {
	var cred WebAuthnCredential
	err := db.Where("id = ?", id).First(&cred).Error
	return &cred, err
}

func GetCredentialByID(id binaryData) (*WebAuthnCredential, error) {
	var cred WebAuthnCredential
	var err error

	switch db.Name() {
	case "postgres":
		hexCredID := hex.EncodeToString(id)
		if err = db.Where("credential_id = decode(?, 'hex')", hexCredID).First(&cred).Error; err != nil {
			return nil, err
		}
	case "mysql", "sqlite":
		hexCredID := hex.EncodeToString(id)
		if err = db.Where("credential_id = unhex(?)", hexCredID).First(&cred).Error; err != nil {
			return nil, err
		}
	}

	return &cred, err
}

func CreateFromCrendential(userID uint, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) {
	credDb := &WebAuthnCredential{
		UserID:             userID,
		Name:               name,
		CredentialID:       cred.ID,
		PublicKey:          cred.PublicKey,
		AttestationType:    cred.AttestationType,
		AAGUID:             cred.Authenticator.AAGUID,
		SignCount:          cred.Authenticator.SignCount,
		CloneWarning:       cred.Authenticator.CloneWarning,
		FlagUserPresent:    cred.Flags.UserPresent,
		FlagUserVerified:   cred.Flags.UserVerified,
		FlagBackupEligible: cred.Flags.BackupEligible,
		FlagBackupState:    cred.Flags.BackupState,
	}
	err := db.Create(credDb).Error
	return credDb, err
}

func (w *WebAuthnCredential) UpdateSignCount() error {
	return db.Model(w).Update("sign_count", w.SignCount).Error
}

func (w *WebAuthnCredential) UpdateLastUsedAt() error {
	return db.Model(w).Update("last_used_at", time.Now().Unix()).Error
}

func (w *WebAuthnCredential) Delete() error {
	return db.Delete(w).Error
}

// -- DTO -- //

type CrendentialDTO struct {
	PasskeyName string `json:"passkeyname" validate:"max=50"`
}
