package oauth2

import (
	
	
	
	
	
	
	
	
	

	
)

// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
const (
	errAuthorizationPending = "authorization_pending"
	errSlowDown             = "slow_down"
	errAccessDenied         = "access_denied"
	errExpiredToken         = "expired_token"
)

// DeviceAuthResponse describes a successful RFC 8628 Device Authorization Response
// https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
type DeviceAuthResponse struct {
	// DeviceCode
	DeviceCode string `json:"device_code"`
	// UserCode is the code the user should enter at the verification uri
	UserCode string `json:"user_code"`
	// VerificationURI is where user should enter the user code
	VerificationURI string `json:"verification_uri"`
	// VerificationURIComplete (if populated) includes the user code in the verification URI. This is typically shown to the user in non-textual form, such as a QR code.
	VerificationURIComplete string `json:"verification_uri_complete,omitempty"`
	// Expiry is when the device code and user code expire
	Expiry time.Time `json:"expires_in,omitempty"`
	// Interval is the duration in seconds that Poll should wait between requests
	Interval int64 `json:"interval,omitempty"`
}

func ( DeviceAuthResponse) () ([]byte, error) {
	type  DeviceAuthResponse
	var  int64
	if !.Expiry.IsZero() {
		 = int64(time.Until(.Expiry).Seconds())
	}
	return json.Marshal(&struct {
		 int64 `json:"expires_in,omitempty"`
		*
	}{
		: ,
		:     (*)(&),
	})

}

func ( *DeviceAuthResponse) ( []byte) error {
	type  DeviceAuthResponse
	 := &struct {
		 int64 `json:"expires_in"`
		// workaround misspelling of verification_uri
		 string `json:"verification_url"`
		*
	}{
		: (*)(),
	}
	if  := json.Unmarshal(, &);  != nil {
		return 
	}
	if . != 0 {
		.Expiry = time.Now().UTC().Add(time.Second * time.Duration(.))
	}
	if .VerificationURI == "" {
		.VerificationURI = .
	}
	return nil
}

// DeviceAuth returns a device auth struct which contains a device code
// and authorization information provided for users to enter on another device.
func ( *Config) ( context.Context,  ...AuthCodeOption) (*DeviceAuthResponse, error) {
	// https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
	 := url.Values{
		"client_id": {.ClientID},
	}
	if len(.Scopes) > 0 {
		.Set("scope", strings.Join(.Scopes, " "))
	}
	for ,  := range  {
		.setValue()
	}
	return retrieveDeviceAuth(, , )
}

func retrieveDeviceAuth( context.Context,  *Config,  url.Values) (*DeviceAuthResponse, error) {
	if .Endpoint.DeviceAuthURL == "" {
		return nil, errors.New("endpoint missing DeviceAuthURL")
	}

	,  := http.NewRequest("POST", .Endpoint.DeviceAuthURL, strings.NewReader(.Encode()))
	if  != nil {
		return nil, 
	}
	.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	.Header.Set("Accept", "application/json")

	 := time.Now()
	,  := internal.ContextClient().Do()
	if  != nil {
		return nil, 
	}

	,  := io.ReadAll(io.LimitReader(.Body, 1<<20))
	if  != nil {
		return nil, fmt.Errorf("oauth2: cannot auth device: %v", )
	}
	if  := .StatusCode;  < 200 ||  > 299 {
		return nil, &RetrieveError{
			Response: ,
			Body:     ,
		}
	}

	 := &DeviceAuthResponse{}
	 = json.Unmarshal(, &)
	if  != nil {
		return nil, fmt.Errorf("unmarshal %s", )
	}

	if !.Expiry.IsZero() {
		// Make a small adjustment to account for time taken by the request
		.Expiry = .Expiry.Add(-time.Since())
	}

	return , nil
}

// DeviceAccessToken polls the server to exchange a device code for a token.
func ( *Config) ( context.Context,  *DeviceAuthResponse,  ...AuthCodeOption) (*Token, error) {
	if !.Expiry.IsZero() {
		var  context.CancelFunc
		,  = context.WithDeadline(, .Expiry)
		defer ()
	}

	// https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
	 := url.Values{
		"client_id":   {.ClientID},
		"grant_type":  {"urn:ietf:params:oauth:grant-type:device_code"},
		"device_code": {.DeviceCode},
	}
	if len(.Scopes) > 0 {
		.Set("scope", strings.Join(.Scopes, " "))
	}
	for ,  := range  {
		.setValue()
	}

	// "If no value is provided, clients MUST use 5 as the default."
	// https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
	 := .Interval
	if  == 0 {
		 = 5
	}

	 := time.NewTicker(time.Duration() * time.Second)
	defer .Stop()
	for {
		select {
		case <-.Done():
			return nil, .Err()
		case <-.C:
			,  := retrieveToken(, , )
			if  == nil {
				return , nil
			}

			,  := .(*RetrieveError)
			if ! {
				return nil, 
			}
			switch .ErrorCode {
			case errSlowDown:
				// https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
				// "the interval MUST be increased by 5 seconds for this and all subsequent requests"
				 += 5
				.Reset(time.Duration() * time.Second)
			case errAuthorizationPending:
				// Do nothing.
			case errAccessDenied, errExpiredToken:
				fallthrough
			default:
				return , 
			}
		}
	}
}