package api

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"runtime"
	"strings"
)

type API interface {
	AuthUrl() string
	UploadAsciicast(string) (string, string, error)
}

type AsciinemaAPI struct {
	url     string
	user    string
	token   string
	version string
	http    HTTP
}

func New(url, user, token, version string) *AsciinemaAPI {
	return &AsciinemaAPI{
		url:     url,
		user:    user,
		token:   token,
		version: version,
		http:    &HTTPClient{},
	}
}

func (a *AsciinemaAPI) AuthUrl() string {
	return fmt.Sprintf("%v/connect/%v", a.url, a.token)
}

func (a *AsciinemaAPI) UploadAsciicast(path string) (string, string, error) {
	files, err := filesForUpload(path)
	if err != nil {
		return "", "", err
	}

	response, err := a.makeUploadRequest(files)
	if err != nil {
		return "", "", fmt.Errorf("Connection failed (%v)", err.Error())
	}
	defer response.Body.Close()

	body, err := extractBody(response)
	if err != nil {
		return "", "", err
	}

	warn := extractWarningMessage(response)

	if response.StatusCode != 200 && response.StatusCode != 201 {
		return "", warn, handleError(response, body)
	}

	return body, warn, nil
}

func (a *AsciinemaAPI) makeUploadRequest(files map[string]io.ReadCloser) (*http.Response, error) {
	return a.http.PostForm(a.urlForUpload(), a.user, a.token, a.headersForUpload(), files)
}

func (a *AsciinemaAPI) urlForUpload() string {
	return a.url + "/api/asciicasts"
}

func (a *AsciinemaAPI) headersForUpload() map[string]string {
	return map[string]string{
		"User-Agent": fmt.Sprintf("asciinema/%s %s/%s %s-%s", a.version, runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH),
	}
}

func filesForUpload(path string) (map[string]io.ReadCloser, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}

	return map[string]io.ReadCloser{"asciicast:asciicast.json": file}, nil
}

func extractWarningMessage(response *http.Response) string {
	parts := strings.SplitN(response.Header.Get("Warning"), " ", 2)

	if len(parts) == 2 {
		return parts[1]
	}

	return ""
}

func extractBody(response *http.Response) (string, error) {
	body := &bytes.Buffer{}

	_, err := body.ReadFrom(response.Body)
	if err != nil {
		return "", err
	}

	return body.String(), nil
}

func handleError(response *http.Response, body string) error {
	switch response.StatusCode {
	case 400:
		return fmt.Errorf("Invalid request: %v", body)
	case 401:
		return fmt.Errorf("Invalid or revoked recorder token")
	case 404:
		return errors.New("Your client version is no longer supported. Please upgrade to the latest version.")
	case 413:
		return errors.New("Sorry, your asciicast is too big.")
	case 422:
		return fmt.Errorf("Invalid asciicast: %v", body)
	case 504:
		return errors.New("The server is down for maintenance. Try again in a minute.")
	default:
		return errors.New("HTTP status: " + response.Status)
	}
}
