package dnsimple

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
)

// GrantType is a string that identifies a particular grant type in the exchange request.
type GrantType string

const (
	// AuthorizationCodeGrant is the type of access token request
	// for an Authorization Code Grant flow.
	// https://tools.ietf.org/html/rfc6749#section-4.1
	AuthorizationCodeGrant = GrantType("authorization_code")
)

// OauthService handles communication with the authorization related
// methods of the DNSimple API.
//
// See https://developer.dnsimple.com/v2/oauth/
type OauthService struct {
	client *Client
}

// AccessToken represents a DNSimple Oauth access token.
type AccessToken struct {
	Token     string `json:"access_token"`
	Type      string `json:"token_type"`
	AccountID int64  `json:"account_id"`
}

// ExchangeAuthorizationRequest represents a request to exchange
// an authorization code for an access token.
// RedirectURI is optional, all the other fields are mandatory.
type ExchangeAuthorizationRequest struct {
	Code         string    `json:"code"`
	ClientID     string    `json:"client_id"`
	ClientSecret string    `json:"client_secret"`
	RedirectURI  string    `json:"redirect_uri,omitempty"`
	State        string    `json:"state,omitempty"`
	GrantType    GrantType `json:"grant_type,omitempty"`
}

// ExchangeAuthorizationError represents a failed request to exchange
// an authorization code for an access token.
type ExchangeAuthorizationError struct {
	// HTTP response
	HTTPResponse *http.Response

	ErrorCode        string `json:"error"`
	ErrorDescription string `json:"error_description"`
}

// Error implements the error interface.
func (r *ExchangeAuthorizationError) Error() string {
	return fmt.Sprintf("%v %v: %v %v",
		r.HTTPResponse.Request.Method, r.HTTPResponse.Request.URL,
		r.ErrorCode, r.ErrorDescription)
}

// ExchangeAuthorizationForToken exchanges the short-lived authorization code for an access token
// you can use to authenticate your API calls.
func (s *OauthService) ExchangeAuthorizationForToken(authorization *ExchangeAuthorizationRequest) (*AccessToken, error) {
	path := versioned("/oauth/access_token")

	req, err := s.client.newRequest("POST", path, authorization)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		errorResponse := &ExchangeAuthorizationError{}
		err = json.NewDecoder(resp.Body).Decode(errorResponse)
		if err != nil {
			return nil, err
		}
		errorResponse.HTTPResponse = resp
		return nil, errorResponse
	}

	accessToken := &AccessToken{}
	err = json.NewDecoder(resp.Body).Decode(accessToken)

	return accessToken, err
}

// AuthorizationOptions represents the option you can use to generate an authorization URL.
type AuthorizationOptions struct {
	RedirectURI string `url:"redirect_uri,omitempty"`
	// A randomly generated string to verify the validity of the request.
	// Currently "state" is required by the DNSimple OAuth implementation, so you must specify it.
	State string `url:"state,omitempty"`
}

// AuthorizeURL generates the URL to authorize an user for an application via the OAuth2 flow.
func (s *OauthService) AuthorizeURL(clientID string, options *AuthorizationOptions) string {
	uri, _ := url.Parse(strings.Replace(s.client.BaseURL, "api.", "", 1))
	uri.Path = "/oauth/authorize"
	query := uri.Query()
	query.Add("client_id", clientID)
	query.Add("response_type", "code")
	uri.RawQuery = query.Encode()

	path, _ := addURLQueryOptions(uri.String(), options)
	return path
}
