package passkey

import (
	"encoding/base64"
	"encoding/json"
	"errors"
)

// PublicKeyCredentialCreationOptions represents the options required for creating a new public key credential.
type PublicKeyCredentialCreationOptions struct {
	Challenge              string                          `json:"challenge"`                        // A unique, random challenge generated by the server.
	RP                     RelyingPartyEntity              `json:"rp"`                               // Information about the relying party (server).
	User                   UserEntity                      `json:"user"`                             // Information about the user creating the credential.
	PubKeyCredParams       []PublicKeyCredentialParameters `json:"pubKeyCredParams"`                 // List of supported public key credential parameters.
	Timeout                int                             `json:"timeout,omitempty"`                // Optional timeout in milliseconds for the credential creation.
	Attestation            string                          `json:"attestation,omitempty"`            // Attestation preference ("none", "indirect", "direct").
	AuthenticatorSelection AuthenticatorSelection          `json:"authenticatorSelection,omitempty"` // Preferences for selecting an authenticator.
}

// RelyingPartyEntity describes the relying party (typically the server).
type RelyingPartyEntity struct {
	Name string `json:"name"` // Human-readable name of the relying party.
	ID   string `json:"id"`   // Relying party identifier (usually the domain).
}

// UserEntity describes the user who is registering the credential.
type UserEntity struct {
	ID          string `json:"id"`          // User identifier (must be base64url-encoded binary ID).
	Name        string `json:"name"`        // User handle, typically the username.
	DisplayName string `json:"displayName"` // User's display name.
}

// PublicKeyCredentialParameters defines acceptable algorithms for public key credentials.
type PublicKeyCredentialParameters struct {
	Type string `json:"type"` // Credential type, typically "public-key".
	Alg  int    `json:"alg"`  // Cryptographic algorithm identifier (-7: ES256, -257: RS256).
}

// AuthenticatorSelection specifies authenticator requirements for registration.
type AuthenticatorSelection struct {
	UserVerification string `json:"userVerification"`      // Requirement level for user verification ("required", "preferred", "discouraged").
	ResidentKey      string `json:"residentKey,omitempty"` // Optional: whether a resident key is required.
}

// CreateOptionsParams allows flexible creation of credential creation options.
type CreateOptionsParams struct {
	RPID             string
	RPName           string
	UserID           []byte // raw user ID (will be base64url-encoded internally)
	UserName         string
	DisplayName      string
	Challenge        string
	Attestation      string // "none", "direct", etc.
	UserVerification string // "preferred", etc.
	ResidentKey      string // optional
	TimeoutMs        int    // optional
}

// CreateOptionsWithParams constructs and serializes the credential creation options into JSON.
// These options are then sent to the client browser for initiating passkey registration.
func CreateOptionsWithParams(p CreateOptionsParams) ([]byte, error) {
	userIDEncoded := base64.RawURLEncoding.EncodeToString(p.UserID)

	opts := PublicKeyCredentialCreationOptions{
		Challenge: p.Challenge,
		RP:        RelyingPartyEntity{Name: p.RPName, ID: p.RPID},
		User: UserEntity{
			ID:          userIDEncoded,
			Name:        p.UserName,
			DisplayName: p.DisplayName,
		},
		PubKeyCredParams: []PublicKeyCredentialParameters{
			{Type: "public-key", Alg: -7},   // ES256
			{Type: "public-key", Alg: -257}, // RS256
		},
		Attestation: p.Attestation,
		Timeout:     p.TimeoutMs,
		AuthenticatorSelection: AuthenticatorSelection{
			UserVerification: p.UserVerification,
			ResidentKey:      p.ResidentKey,
		},
	}

	b, err := json.Marshal(opts)
	if err != nil {
		return nil, errors.Join(ErrCreateOptionsMarshal, err)
	}
	return b, nil
}

// CreateOptions is a simplified wrapper for legacy usage.
// Use CreateOptionsWithParams for more control.
func CreateOptions(rpID, rpName, userID, userName, displayName, challenge string) ([]byte, error) {
	return CreateOptionsWithParams(CreateOptionsParams{
		RPID:             rpID,
		RPName:           rpName,
		UserID:           []byte(userID),
		UserName:         userName,
		DisplayName:      displayName,
		Challenge:        challenge,
		Attestation:      "none",
		UserVerification: "preferred",
	})
}
