// Copyright 2020-2025 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package oauth2

import (
	"net/url"
)

const (
	// DeviceRegistrationPath is the path for the device registration endpoint.
	DeviceRegistrationPath = "/oauth2/device/registration"
	// DeviceAuthorizationPath is the path for the device authorization endpoint.
	DeviceAuthorizationPath = "/oauth2/device/authorization"
	// DeviceTokenPath is the path for the device token endpoint.
	DeviceTokenPath = "/oauth2/device/token"
)

const (
	// DeviceAuthorizationGrantType is the grant type for the device authorization flow.
	DeviceAuthorizationGrantType = "urn:ietf:params:oauth:grant-type:device_code"
)

// The following error codes are defined by RFC 8628 Section 3.5 Device Authorization Response.
const (
	// ErrorCodeAuthorizationPending is a pending device authorization grant as the
	// end user hasn't yet completed the user interaction steps.
	ErrorCodeAuthorizationPending ErrorCode = "authorization_pending"
	// ErrorCodeSlowDown is returned for a pending device authorization grant and
	// polling should continue, but the interval MUST be increased by 5 seconds for
	// all subsequent requests.
	ErrorCodeSlowDown ErrorCode = "slow_down"
	// ErrorCodeAccessDenied is returned when the device authorization request was denied.
	ErrorCodeAccessDenied ErrorCode = "access_denied"
	// ErrorCodeExpiredToken is the device_code has expired, and the device authorization
	// session has concluded. The client MAY commence a new device authorization request but
	// SHOULD wait for user interaction before restarting to avoid unnecessary polling.
	ErrorCodeExpiredToken ErrorCode = "expired_token"
)

// DeviceRegistrationRequest describes an OpenID Connect Dynamic Client Registration 1.0 request
// for dynamic client registration. It is a subset of the full specification.
// It does not require a redirect URI or grant types for the device authorization flow.
// https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest
type DeviceRegistrationRequest struct {
	// Name of the client to be presented to the end user.
	ClientName string `json:"client_name"`
}

// DeviceRegistrationResponse describes a successful OpenID Connect Dynamic Client Registration 1.0 response
// for dynamic client registration.
type DeviceRegistrationResponse struct {
	// ClientID is the unique client identifier.
	ClientID string `json:"client_id"`
	// ClientSecret is the client secret. May be empty.
	ClientSecret string `json:"client_secret,omitempty"`
	// ClientIDIssuedAt is the time at which the ClientID was issued in seconds since the Unix epoch.
	ClientIDIssuedAt int `json:"client_id_issued_at"`
	// ClientSecretExpiresAt is the time at which the client_secret will expire in seconds since the Unix epoch.
	ClientSecretExpiresAt int `json:"client_secret_expires_at,omitempty"`
}

// DeviceAuthorizationRequest describes an RFC 8628 Device Authorization Request.
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
type DeviceAuthorizationRequest struct {
	// ClientID is the unique client identifier.
	ClientID string `json:"client_id"`
	// ClientSecret is the client secret. May be empty.
	ClientSecret string `json:"client_secret,omitempty"`
}

// ToValues converts the DeviceAuthorizationRequest to url.Values.
func (d *DeviceAuthorizationRequest) ToValues() url.Values {
	values := make(url.Values, 2)
	values.Set("client_id", d.ClientID)
	if d.ClientSecret != "" {
		values.Set("client_secret", d.ClientSecret)
	}
	return values
}

// FromValues converts the url.Values to a DeviceAuthorizationRequest.
func (d *DeviceAuthorizationRequest) FromValues(values url.Values) error {
	d.ClientID = values.Get("client_id")
	d.ClientSecret = values.Get("client_secret")
	return nil
}

// DeviceAuthorizationResponse describes a successful RFC 8628 Device Authorization Response
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
type DeviceAuthorizationResponse struct {
	// DeviceCode is the device verification code.
	DeviceCode string `json:"device_code"`
	// UserCode is the end-user verification code.
	UserCode string `json:"user_code"`
	// VerificationURI is the verification URI that the end user should visit to
	// enter the user_code.
	VerificationURI string `json:"verification_uri"`
	// VerificationURIComplete is the verification URI that includes the user_code.
	VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
	// ExpiresIn is the lifetime in seconds of the "device_code" and "user_code".
	ExpiresIn int `json:"expires_in"`
	// Interval is the minimum amount of time in seconds that the client SHOULD wait
	// between polling requests to the token endpoint.
	Interval int `json:"interval,omitempty"`
}

// DeviceAccessTokenRequest describes an RFC 8628 Device Token Request.
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
type DeviceAccessTokenRequest struct {
	// ClientID is the client identifier issued to the client during the registration process.
	ClientID string `json:"client_id"`
	// ClientSecret is the client secret. May be empty.
	ClientSecret string `json:"client_secret,omitempty"`
	// DeviceCode is the device verification code.
	DeviceCode string `json:"device_code"`
	// GrantType is the grant type for the device authorization flow. Must be
	// set to "urn:ietf:params:oauth:grant-type:device_code".
	GrantType string `json:"grant_type"`
}

// ToValues converts the DeviceTokenRequest to url.Values.
func (d *DeviceAccessTokenRequest) ToValues() url.Values {
	values := make(url.Values, 4)
	values.Set("client_id", d.ClientID)
	if d.ClientSecret != "" {
		values.Set("client_secret", d.ClientSecret)
	}
	values.Set("device_code", d.DeviceCode)
	values.Set("grant_type", d.GrantType)
	return values
}

// FromValues converts the url.Values to a DeviceTokenRequest.
func (d *DeviceAccessTokenRequest) FromValues(values url.Values) error {
	d.ClientID = values.Get("client_id")
	d.ClientSecret = values.Get("client_secret")
	d.DeviceCode = values.Get("device_code")
	d.GrantType = values.Get("grant_type")
	return nil
}

// DeviceAccessTokenResponse describes a successful RFC 8628 Device Token Response.
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
type DeviceAccessTokenResponse struct {
	// AccessToken is the access token that can be used to access the protected resources.
	AccessToken string `json:"access_token"`
	// TokenType is the type of the token issued as described in RFC 6749 Section 7.1.
	// https://datatracker.ietf.org/doc/html/rfc6749#section-7.1
	TokenType string `json:"token_type"`
	// ExpiresIn is the lifetime in seconds of the access token.
	ExpiresIn int `json:"expires_in,omitempty"`
	// RefreshToken may be used to obtain new access tokens using the same authorization
	// grant. May be empty.
	RefreshToken string `json:"refresh_token,omitempty"`
	// Scope is the scope of the access token as described in RFC 6749 Section 3.3.
	// https://datatracker.ietf.org/doc/html/rfc6749#section-3.3
	Scope string `json:"scope,omitempty"`
}
