package client

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strings"
	"time"

	"code.secondbit.org/uuid.hg"

	"code.secondbit.org/ducky/subscriptions.hg/api"
)

var (
	ErrNilClient     = errors.New("nil client wrapper")
	ErrNilHTTPClient = errors.New("nil client")
)

type Client struct {
	client  *http.Client
	address string
	ID      uuid.ID
}

func New(address string, id uuid.ID) *Client {
	address = strings.TrimRight(address, "/")
	return &Client{
		address: address,
		client:  &http.Client{},
		ID:      id,
	}
}

func (c *Client) do(method, url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
	if c == nil {
		return api.Response{}, ErrNilClient
	}
	if c.client == nil {
		return api.Response{}, ErrNilHTTPClient
	}
	var response api.Response
	if !strings.HasPrefix(url, "http") {
		url = strings.TrimLeft(url, "/")
		url = "/" + url
		url = c.address + url
	}
	var body io.Reader
	if request != nil {
		data, err := json.Marshal(request)
		if err != nil {
			return response, err
		}
		body = bytes.NewBuffer(data)
	}
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return response, err
	}
	req.Header.Set("Ducky-Scope", strings.Join(scopes, " "))
	req.Header.Set("Ducky-Issuer", c.ID.String())
	if subject != nil {
		req.Header.Set("Ducky-Subject", subject.String())
	}
	req.Header.Set("Ducky-Expires", time.Now().Add(time.Hour).String())
	req.Header.Set("Ducky-Issued-At", time.Now().String())
	req.Header.Set("Ducky-Not-Before", time.Now().Add(-5*time.Minute).String())
	resp, err := c.client.Do(req)
	if err != nil {
		return response, err
	}
	defer resp.Body.Close()
	switch resp.Header.Get("Content-Type") {
	case "application/json":
		dec := json.NewDecoder(resp.Body)
		err = dec.Decode(&response)
		if err != nil {
			return response, err
		}
	default:
		dec := json.NewDecoder(resp.Body)
		err = dec.Decode(&response)
		if err != nil {
			return response, err
		}
	}
	return response, nil
}

func (c *Client) Get(url string, scopes []string, subject uuid.ID) (api.Response, error) {
	return c.do("GET", url, nil, scopes, subject)
}

func (c *Client) Post(url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
	return c.do("POST", url, request, scopes, subject)
}

func (c *Client) Patch(url string, request interface{}, scopes []string, subject uuid.ID) (api.Response, error) {
	return c.do("PATCH", url, request, scopes, subject)
}
