package pkg

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
)

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

type PandocClient interface {
	ConvertLatexToHtml5(ctx context.Context, text string) (string, error)
	BatchConvertLatexToHtml5(ctx context.Context, texts []string) ([]string, error)
}

func NewPandocClient(client *http.Client, address string) *Client {
	return &Client{
		client:  client,
		address: address,
	}
}

type conversation struct {
	Text string `json:"text"`
	From string `json:"from"`
	To   string `json:"to"`
	Math string `json:"html-math-method"`
}

type message struct {
	Message   string `json:"message"`
	Verbosity string `json:"verbosity"`
}

type output struct {
	Error    string    `json:"error"`
	Output   string    `json:"output"`
	Base64   bool      `json:"base64"`
	Messages []message `json:"messages"`
}

func (client *Client) sendRaw(ctx context.Context, path string, body []byte) ([]byte, error) {
	path, err := url.JoinPath(client.address, path)
	if err != nil {
		return nil, err
	}

	buf := bytes.NewBuffer(body)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, path, buf)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := client.client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	body, err = io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

func (client *Client) convert(ctx context.Context, text, from, to, math string) (string, error) {
	body, err := json.Marshal(conversation{
		Text: text,
		From: from,
		To:   to,
		Math: math,
	})
	if err != nil {
		return "", err
	}

	resp, err := client.sendRaw(ctx, "/", body)
	if err != nil {
		return "", err
	}

	return string(resp), nil
}

func (client *Client) batchConvert(ctx context.Context, texts []string, from, to, math string) ([]string, error) {
	list := make([]conversation, len(texts))
	for i, text := range texts {
		list[i] = conversation{
			Text: text,
			From: from,
			To:   to,
			Math: math,
		}
	}

	body, err := json.Marshal(list)
	if err != nil {
		return nil, err
	}

	resp, err := client.sendRaw(ctx, "/batch", body)
	if err != nil {
		return nil, err
	}

	var result []output
	err = json.Unmarshal(resp, &result)
	if err != nil {
		return nil, err
	}

	if len(result) != len(texts) {
		return nil, fmt.Errorf("wrong number of fieilds returned: %d", len(result))
	}

	err = nil
	for _, o := range result {
		if o.Error != "" {
			err = errors.Join(err, errors.New(o.Error))
		}
	}

	if err != nil {
		return nil, Wrap(ErrBadInput, err, "BatchConvertLatexToHtml5", "invalid input")
	}

	res := make([]string, len(result))
	for i, o := range result {
		res[i] = o.Output
	}

	return res, nil
}

func (client *Client) ConvertLatexToHtml5(ctx context.Context, text string) (string, error) {
	return client.convert(ctx, text, "latex", "html5", "katex")
}

func (client *Client) BatchConvertLatexToHtml5(ctx context.Context, texts []string) ([]string, error) {
	return client.batchConvert(ctx, texts, "latex", "html5", "katex")
}