package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"strconv"
	"strings"
)

type gokapiClient struct {
	httpClient *http.Client
	host       *url.URL
	apiKey     string
}

func newGokapiClient(host *url.URL, apiKey string) *gokapiClient {
	return &gokapiClient{
		httpClient: &http.Client{},
		host:       host,
		apiKey:     apiKey,
	}
}

func (gc *gokapiClient) uploadChunk(ctx context.Context, fi uploadInfo, offset int64, data io.Reader) error {
	var body bytes.Buffer

	boundary, err := gc.prepUploadChunkBody(&body, fi, offset, data)
	if err != nil {
		return err
	}

	actionURL := gc.host.ResolveReference(&url.URL{Path: "/api/chunk/add"})

	req, err := http.NewRequestWithContext(ctx, "POST", actionURL.String(), &body)
	if err != nil {
		return err
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Apikey", gc.apiKey)
	req.Header.Set("Content-Type", "multipart/form-data; boundary="+boundary)

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

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("upload chunk failed with status code %d", resp.StatusCode)
	}
	return nil
}

func (gc *gokapiClient) finalizeChunk(ctx context.Context, fi uploadInfo) (UploadResponse, error) {
	actionURL := gc.host.ResolveReference(&url.URL{Path: "/api/chunk/complete"})

	formData := url.Values{}
	formData.Set("uuid", fi.chunkID)
	formData.Set("filename", fi.filename)
	formData.Set("filesize", strconv.FormatInt(fi.totalSize, 10))
	formData.Set("contenttype", fi.contentType)
	formData.Set("allowedDownloads", strconv.Itoa(fi.allowedDownloads))
	formData.Set("expiryDays", strconv.Itoa(fi.expiryDays))
	formData.Set("password", fi.password)

	req, err := http.NewRequestWithContext(ctx, "POST", actionURL.String(), strings.NewReader(formData.Encode()))
	if err != nil {
		return UploadResponse{}, err
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Apikey", gc.apiKey)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

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

	if resp.StatusCode != http.StatusOK {
		return UploadResponse{}, fmt.Errorf("upload chunk finalization with status code %d", resp.StatusCode)
	}

	var r UploadResponse
	if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
		return UploadResponse{}, err
	}

	return r, nil
}

func (gc *gokapiClient) prepUploadChunkBody(w io.Writer, fi uploadInfo, offset int64, data io.Reader) (string, error) {
	mw := multipart.NewWriter(w)
	defer mw.Close()

	mw.WriteField("uuid", fi.chunkID)
	mw.WriteField("filesize", strconv.FormatInt(fi.totalSize, 10))
	mw.WriteField("offset", strconv.FormatInt(offset, 10))

	fileWriter, err := mw.CreateFormFile("file", fi.filename)
	if err != nil {
		return "", err
	}
	_, err = io.Copy(fileWriter, data)
	if err != nil {
		return "", err
	}

	return mw.Boundary(), nil
}