package protocol

import (
	"bytes"
	"encoding/binary"
	"fmt"

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

const (
	minAuthDataLength     = 37
	minAttestedAuthLength = 55
	maxCredentialIDLength = 1023
)

// AuthenticatorResponse represents the IDL with the same name.
//
// Authenticators respond to Relying Party requests by returning an object derived from the AuthenticatorResponse
// interface
//
// Specification: §5.2. Authenticator Responses (https://www.w3.org/TR/webauthn/#iface-authenticatorresponse)
type AuthenticatorResponse struct {
	// From the spec https://www.w3.org/TR/webauthn/#dom-authenticatorresponse-clientdatajson
	// This attribute contains a JSON serialization of the client data passed to the authenticator
	// by the client in its call to either create() or get().
	ClientDataJSON URLEncodedBase64 `json:"clientDataJSON"`
}

// AuthenticatorData represents the IDL with the same name.
//
// The authenticator data structure encodes contextual bindings made by the authenticator. These bindings are controlled
// by the authenticator itself, and derive their trust from the WebAuthn Relying Party's assessment of the security
// properties of the authenticator. In one extreme case, the authenticator may be embedded in the client, and its
// bindings may be no more trustworthy than the client data. At the other extreme, the authenticator may be a discrete
// entity with high-security hardware and software, connected to the client over a secure channel. In both cases, the
// Relying Party receives the authenticator data in the same format, and uses its knowledge of the authenticator to make
// trust decisions.
//
// The authenticator data has a compact but extensible encoding. This is desired since authenticators can be devices
// with limited capabilities and low power requirements, with much simpler software stacks than the client platform.
//
// Specification: §6.1. Authenticator Data (https://www.w3.org/TR/webauthn/#sctn-authenticator-data)
type AuthenticatorData struct {
	RPIDHash []byte                 `json:"rpid"`
	Flags    AuthenticatorFlags     `json:"flags"`
	Counter  uint32                 `json:"sign_count"`
	AttData  AttestedCredentialData `json:"att_data"`
	ExtData  []byte                 `json:"ext_data"`
}

type AttestedCredentialData struct {
	AAGUID       []byte `json:"aaguid"`
	CredentialID []byte `json:"credential_id"`

	// The raw credential public key bytes received from the attestation data.
	CredentialPublicKey []byte `json:"public_key"`
}

// AuthenticatorAttachment represents the IDL enum of the same name, and is used as part of the Authenticator Selection
// Criteria.
//
// This enumeration’s values describe authenticators' attachment modalities. Relying Parties use this to express a
// preferred authenticator attachment modality when calling navigator.credentials.create() to create a credential.
//
// If this member is present, eligible authenticators are filtered to only authenticators attached with the specified
// §5.4.5 Authenticator Attachment Enumeration (enum AuthenticatorAttachment). The value SHOULD be a member of
// AuthenticatorAttachment but client platforms MUST ignore unknown values, treating an unknown value as if the member
// does not exist.
//
// Specification: §5.4.4. Authenticator Selection Criteria (https://www.w3.org/TR/webauthn/#dom-authenticatorselectioncriteria-authenticatorattachment)
//
// Specification: §5.4.5. Authenticator Attachment Enumeration (https://www.w3.org/TR/webauthn/#enum-attachment)
type AuthenticatorAttachment string

const (
	// Platform represents a platform authenticator is attached using a client device-specific transport, called
	// platform attachment, and is usually not removable from the client device. A public key credential bound to a
	// platform authenticator is called a platform credential.
	Platform AuthenticatorAttachment = "platform"

	// CrossPlatform represents a roaming authenticator is attached using cross-platform transports, called
	// cross-platform attachment. Authenticators of this class are removable from, and can "roam" among, client devices.
	// A public key credential bound to a roaming authenticator is called a roaming credential.
	CrossPlatform AuthenticatorAttachment = "cross-platform"
)

// ResidentKeyRequirement represents the IDL of the same name.
//
// This enumeration’s values describe the Relying Party's requirements for client-side discoverable credentials
// (formerly known as resident credentials or resident keys).
//
// Specifies the extent to which the Relying Party desires to create a client-side discoverable credential. For
// historical reasons the naming retains the deprecated “resident” terminology. The value SHOULD be a member of
// ResidentKeyRequirement but client platforms MUST ignore unknown values, treating an unknown value as if the member
// does not exist. If no value is given then the effective value is required if requireResidentKey is true or
// discouraged if it is false or absent.
//
// Specification: §5.4.4. Authenticator Selection Criteria (https://www.w3.org/TR/webauthn/#dom-authenticatorselectioncriteria-residentkey)
//
// Specification: §5.4.6. Resident Key Requirement Enumeration (https://www.w3.org/TR/webauthn/#enumdef-residentkeyrequirement)
type ResidentKeyRequirement string

const (
	// ResidentKeyRequirementDiscouraged indicates the Relying Party prefers creating a server-side credential, but will
	// accept a client-side discoverable credential. This is the default.
	ResidentKeyRequirementDiscouraged ResidentKeyRequirement = "discouraged"

	// ResidentKeyRequirementPreferred indicates to the client we would prefer a discoverable credential.
	ResidentKeyRequirementPreferred ResidentKeyRequirement = "preferred"

	// ResidentKeyRequirementRequired indicates the Relying Party requires a client-side discoverable credential, and is
	// prepared to receive an error if a client-side discoverable credential cannot be created.
	ResidentKeyRequirementRequired ResidentKeyRequirement = "required"
)

// AuthenticatorTransport represents the IDL enum with the same name.
//
// Authenticators may implement various transports for communicating with clients. This enumeration defines hints as to
// how clients might communicate with a particular authenticator in order to obtain an assertion for a specific
// credential. Note that these hints represent the WebAuthn Relying Party's best belief as to how an authenticator may
// be reached. A Relying Party will typically learn of the supported transports for a public key credential via
// getTransports().
//
// Specification: §5.8.4. Authenticator Transport Enumeration (https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport)
type AuthenticatorTransport string

const (
	// USB indicates the respective authenticator can be contacted over removable USB.
	USB AuthenticatorTransport = "usb"

	// NFC indicates the respective authenticator can be contacted over Near Field Communication (NFC).
	NFC AuthenticatorTransport = "nfc"

	// BLE indicates the respective authenticator can be contacted over Bluetooth Smart (Bluetooth Low Energy / BLE).
	BLE AuthenticatorTransport = "ble"

	// Hybrid indicates the respective authenticator can be contacted using a combination of (often separate)
	// data-transport and proximity mechanisms. This supports, for example, authentication on a desktop computer using
	// a smartphone.
	//
	// WebAuthn Level 3.
	Hybrid AuthenticatorTransport = "hybrid"

	// Internal indicates the respective authenticator is contacted using a client device-specific transport, i.e., it
	// is a platform authenticator. These authenticators are not removable from the client device.
	Internal AuthenticatorTransport = "internal"
)

// UserVerificationRequirement is a representation of the UserVerificationRequirement IDL enum.
//
// A WebAuthn Relying Party may require user verification for some of its operations but not for others,
// and may use this type to express its needs.
//
// Specification: §5.8.6. User Verification Requirement Enumeration (https://www.w3.org/TR/webauthn/#enum-userVerificationRequirement)
type UserVerificationRequirement string

const (
	// VerificationRequired User verification is required to create/release a credential
	VerificationRequired UserVerificationRequirement = "required"

	// VerificationPreferred User verification is preferred to create/release a credential
	VerificationPreferred UserVerificationRequirement = "preferred" // This is the default

	// VerificationDiscouraged The authenticator should not verify the user for the credential
	VerificationDiscouraged UserVerificationRequirement = "discouraged"
)

// AuthenticatorFlags A byte of information returned during during ceremonies in the
// authenticatorData that contains bits that give us information about the
// whether the user was present and/or verified during authentication, and whether
// there is attestation or extension data present. Bit 0 is the least significant bit.
//
// Specification: §6.1. Authenticator Data - Flags (https://www.w3.org/TR/webauthn/#flags)
type AuthenticatorFlags byte

// The bits that do not have flags are reserved for future use.
const (
	// FlagUserPresent Bit 00000001 in the byte sequence. Tells us if user is present. Also referred to as the UP flag.
	FlagUserPresent AuthenticatorFlags = 1 << iota // Referred to as UP

	// FlagRFU1 is a reserved for future use flag.
	FlagRFU1

	// FlagUserVerified Bit 00000100 in the byte sequence. Tells us if user is verified
	// by the authenticator using a biometric or PIN. Also referred to as the UV flag.
	FlagUserVerified

	// FlagBackupEligible Bit 00001000 in the byte sequence. Tells us if a backup is eligible for device. Also referred
	// to as the BE flag.
	FlagBackupEligible // Referred to as BE

	// FlagBackupState Bit 00010000 in the byte sequence. Tells us if a backup state for device. Also referred to as the
	// BS flag.
	FlagBackupState

	// FlagRFU2 is a reserved for future use flag.
	FlagRFU2

	// FlagAttestedCredentialData Bit 01000000 in the byte sequence. Indicates whether
	// the authenticator added attested credential data. Also referred to as the AT flag.
	FlagAttestedCredentialData

	// FlagHasExtensions Bit 10000000 in the byte sequence. Indicates if the authenticator data has extensions. Also
	// referred to as the ED flag.
	FlagHasExtensions
)

// UserPresent returns if the UP flag was set.
func (flag AuthenticatorFlags) UserPresent() bool {
	return flag.HasUserPresent()
}

// UserVerified returns if the UV flag was set.
func (flag AuthenticatorFlags) UserVerified() bool {
	return flag.HasUserVerified()
}

// HasUserPresent returns if the UP flag was set.
func (flag AuthenticatorFlags) HasUserPresent() bool {
	return (flag & FlagUserPresent) == FlagUserPresent
}

// HasUserVerified returns if the UV flag was set.
func (flag AuthenticatorFlags) HasUserVerified() bool {
	return (flag & FlagUserVerified) == FlagUserVerified
}

// HasAttestedCredentialData returns if the AT flag was set.
func (flag AuthenticatorFlags) HasAttestedCredentialData() bool {
	return (flag & FlagAttestedCredentialData) == FlagAttestedCredentialData
}

// HasExtensions returns if the ED flag was set.
func (flag AuthenticatorFlags) HasExtensions() bool {
	return (flag & FlagHasExtensions) == FlagHasExtensions
}

// HasBackupEligible returns if the BE flag was set.
func (flag AuthenticatorFlags) HasBackupEligible() bool {
	return (flag & FlagBackupEligible) == FlagBackupEligible
}

// HasBackupState returns if the BS flag was set.
func (flag AuthenticatorFlags) HasBackupState() bool {
	return (flag & FlagBackupState) == FlagBackupState
}

// Unmarshal will take the raw Authenticator Data and marshals it into AuthenticatorData for further validation.
// The authenticator data has a compact but extensible encoding. This is desired since authenticators can be
// devices with limited capabilities and low power requirements, with much simpler software stacks than the client platform.
// The authenticator data structure is a byte array of 37 bytes or more, and is laid out in this table:
// https://www.w3.org/TR/webauthn/#table-authData
func (a *AuthenticatorData) Unmarshal(rawAuthData []byte) (err error) {
	if minAuthDataLength > len(rawAuthData) {
		return ErrBadRequest.
			WithDetails("Authenticator data length too short").
			WithInfo(fmt.Sprintf("Expected data greater than %d bytes. Got %d bytes", minAuthDataLength, len(rawAuthData)))
	}

	a.RPIDHash = rawAuthData[:32]
	a.Flags = AuthenticatorFlags(rawAuthData[32])
	a.Counter = binary.BigEndian.Uint32(rawAuthData[33:37])

	remaining := len(rawAuthData) - minAuthDataLength

	if a.Flags.HasAttestedCredentialData() {
		if len(rawAuthData) > minAttestedAuthLength {
			if err = a.unmarshalAttestedData(rawAuthData); err != nil {
				return err
			}

			attDataLen := len(a.AttData.AAGUID) + 2 + len(a.AttData.CredentialID) + len(a.AttData.CredentialPublicKey)
			remaining = remaining - attDataLen
		} else {
			return ErrBadRequest.WithDetails("Attested credential flag set but data is missing")
		}
	} else {
		if !a.Flags.HasExtensions() && len(rawAuthData) != 37 {
			return ErrBadRequest.WithDetails("Attested credential flag not set")
		}
	}

	if a.Flags.HasExtensions() {
		if remaining != 0 {
			a.ExtData = rawAuthData[len(rawAuthData)-remaining:]
			remaining -= len(a.ExtData)
		} else {
			return ErrBadRequest.WithDetails("Extensions flag set but extensions data is missing")
		}
	}

	if remaining != 0 {
		return ErrBadRequest.WithDetails("Leftover bytes decoding AuthenticatorData")
	}

	return nil
}

// If Attestation Data is present, unmarshall that into the appropriate public key structure.
func (a *AuthenticatorData) unmarshalAttestedData(rawAuthData []byte) (err error) {
	a.AttData.AAGUID = rawAuthData[37:53]

	idLength := binary.BigEndian.Uint16(rawAuthData[53:55])
	if len(rawAuthData) < int(55+idLength) {
		return ErrBadRequest.WithDetails("Authenticator attestation data length too short")
	}

	if idLength > maxCredentialIDLength {
		return ErrBadRequest.WithDetails("Authenticator attestation data credential id length too long")
	}

	a.AttData.CredentialID = rawAuthData[55 : 55+idLength]

	a.AttData.CredentialPublicKey, err = unmarshalCredentialPublicKey(rawAuthData[55+idLength:])
	if err != nil {
		return ErrBadRequest.WithDetails(fmt.Sprintf("Could not unmarshal Credential Public Key: %v", err))
	}

	return nil
}

// Unmarshall the credential's Public Key into CBOR encoding.
func unmarshalCredentialPublicKey(keyBytes []byte) (rawBytes []byte, err error) {
	var m interface{}

	if err = webauthncbor.Unmarshal(keyBytes, &m); err != nil {
		return nil, err
	}

	if rawBytes, err = webauthncbor.Marshal(m); err != nil {
		return nil, err
	}

	return rawBytes, nil
}

// ResidentKeyRequired - Require that the key be private key resident to the client device.
func ResidentKeyRequired() *bool {
	required := true

	return &required
}

// ResidentKeyNotRequired - Do not require that the private key be resident to the client device.
func ResidentKeyNotRequired() *bool {
	required := false
	return &required
}

// Deprecated: ResidentKeyUnrequired is an alias for ResidentKeyNotRequired and will be completely removed in the future.
func ResidentKeyUnrequired() *bool {
	return ResidentKeyNotRequired()
}

// Verify on AuthenticatorData handles Steps 9 through 12 for Registration
// and Steps 11 through 14 for Assertion.
func (a *AuthenticatorData) Verify(rpIdHash []byte, appIDHash []byte, userVerificationRequired bool) error {

	// Registration Step 9 & Assertion Step 11
	// Verify that the RP ID hash in authData is indeed the SHA-256
	// hash of the RP ID expected by the RP.
	if !bytes.Equal(a.RPIDHash[:], rpIdHash) && !bytes.Equal(a.RPIDHash[:], appIDHash) {
		return ErrVerification.WithInfo(fmt.Sprintf("RP Hash mismatch. Expected %x and Received %x", a.RPIDHash, rpIdHash))
	}

	// Registration Step 10 & Assertion Step 12
	// Verify that the User Present bit of the flags in authData is set.
	if !a.Flags.UserPresent() {
		return ErrVerification.WithInfo(fmt.Sprintln("User presence flag not set by authenticator"))
	}

	// Registration Step 11 & Assertion Step 13
	// If user verification is required for this assertion, verify that
	// the User Verified bit of the flags in authData is set.
	if userVerificationRequired && !a.Flags.UserVerified() {
		return ErrVerification.WithInfo(fmt.Sprintln("User verification required but flag not set by authenticator"))
	}

	// Registration Step 12 & Assertion Step 14
	// Verify that the values of the client extension outputs in clientExtensionResults
	// and the authenticator extension outputs in the extensions in authData are as
	// expected, considering the client extension input values that were given as the
	// extensions option in the create() call. In particular, any extension identifier
	// values in the clientExtensionResults and the extensions in authData MUST be also be
	// present as extension identifier values in the extensions member of options, i.e., no
	// extensions are present that were not requested. In the general case, the meaning
	// of "are as expected" is specific to the Relying Party and which extensions are in use.

	// This is not yet fully implemented by the spec or by browsers.

	return nil
}
