// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package oauth1

import (
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
)

// token stores the authorization credentials used to
// access protected resources.
type token struct {
	Token       string
	TokenSecret string
}

// Config stores the application configuration.
type Config struct {
	// HTTP client used to communicate with the authorization
	// server. If nil, DefaultClient is used.
	Client *http.Client

	// A Signer signs messages to create signed OAuth1 Requests.
	// If nil, the HMAC signing algorithm is used.
	Signer Signer

	// A value used by the Consumer to identify itself
	// to the Service Provider.
	ConsumerKey string

	// A secret used by the Consumer to establish
	// ownership of the Consumer Key.
	ConsumerSecret string

	// An absolute URL to which the Service Provider will redirect
	// the User back when the Obtaining User Authorization step
	// is completed.
	//
	// If the Consumer is unable to receive callbacks or a callback
	// URL has been established via other means, the parameter
	// value MUST be set to oob (case sensitive), to indicate
	// an out-of-band configuration.
	CallbackURL string

	// The URL used to obtain an unauthorized
	// Request Token.
	RequestTokenURL string

	// The URL used to obtain User authorization
	// for Consumer access.
	AccessTokenURL string

	// The URL used to exchange the User-authorized
	// Request Token for an Access Token.
	AuthorizationURL string
}

// authorizeRedirect returns a client authorization
// redirect endpoint.
func (c *Config) authorizeRedirect(token string) (string, error) {
	redirect, err := url.Parse(c.AuthorizationURL)
	if err != nil {
		return "", err
	}

	params := make(url.Values)
	params.Add("oauth_token", token)
	redirect.RawQuery = params.Encode()
	return redirect.String(), nil
}

// requestToken gets a request token from the server.
func (c *Config) requestToken() (*token, error) {
	endpoint, err := url.Parse(c.RequestTokenURL)
	if err != nil {
		return nil, err
	}
	req := &http.Request{
		URL:        endpoint,
		Method:     "POST",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     http.Header{},
	}
	err = newAuther(c).setRequestTokenAuthHeader(req)
	if err != nil {
		return nil, err
	}
	res, err := c.client().Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode > 300 {
		// TODO(bradrydzewski) unmarshal the oauth1 error.
		return nil, errors.New("Invalid Response")
	}
	return parseToken(res.Body)
}

// authorizeToken returns a client authorization
// redirect endpoint.
func (c *Config) authorizeToken(token, verifier string) (*token, error) {
	endpoint, err := url.Parse(c.AccessTokenURL)
	if err != nil {
		return nil, err
	}
	req := &http.Request{
		URL:        endpoint,
		Method:     "POST",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     http.Header{},
	}
	err = newAuther(c).setAccessTokenAuthHeader(req, token, "", verifier)
	if err != nil {
		return nil, err
	}
	res, err := c.client().Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()
	if res.StatusCode > 300 {
		x, _ := httputil.DumpResponse(res, true)
		println(string(x))
		// TODO(bradrydzewski) unmarshal the oauth1 error.
		return nil, errors.New("Invalid Response")
	}
	return parseToken(res.Body)
}

func (c *Config) client() *http.Client {
	client := c.Client
	if client == nil {
		client = http.DefaultClient
	}
	return client
}

func parseToken(r io.Reader) (*token, error) {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return nil, err
	}
	v, err := url.ParseQuery(string(b))
	if err != nil {
		return nil, err
	}
	return &token{
		Token:       v.Get("oauth_token"),
		TokenSecret: v.Get("oauth_token_secret"),
	}, nil
}
