package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"time"

	"gordenko.dev/dima/httpreq"
)

type BuildRequestReq struct {
	URL       string
	FuncName  string
	Data      any
	AuthToken string
}
type Client struct {
	url            string
	buildRequest   func(BuildRequestReq) (*http.Request, error) // funcName, data, authToken
	requestTimeout time.Duration
}

type ClientOptions struct {
	URL            string
	BuildRequest   func(BuildRequestReq) (*http.Request, error) // funcName, data, authToken
	RequestTimeout time.Duration                                // optional
}

func NewClient(opt ClientOptions) (*Client, error) {
	if opt.URL == "" {
		return nil, errors.New("URL option is required")
	}
	if opt.BuildRequest == nil {
		return nil, errors.New("BuildRequest option is required")
	}
	s := &Client{
		url:            opt.URL,
		buildRequest:   opt.BuildRequest,
		requestTimeout: opt.RequestTimeout,
	}
	return s, nil
}

type CallReq struct {
	FuncName  string
	In        any           // optional
	AuthToken string        // optional
	Out       any           // optional
	Timeout   time.Duration // optional
}

func (s *Client) Call(req CallReq) (err error) {
	httpReq, err := s.buildRequest(BuildRequestReq{
		URL:       s.url,
		FuncName:  req.FuncName,
		Data:      req.In,
		AuthToken: req.AuthToken,
	})
	if err != nil {
		return fmt.Errorf("buildRequest: %s", err)
	}
	var timeout time.Duration
	if req.Timeout > 0 {
		timeout = req.Timeout
	} else if s.requestTimeout > 0 {
		timeout = s.requestTimeout
	}
	resp, err := httpreq.Send(httpReq, timeout)
	if err != nil {
		return
	}
	switch resp.StatusCode {
	case 200:
		if string(resp.Body) == "{}" {
			return
		}
		if err = checkErrorEnvelope(resp); err != nil {
			return
		}
		if req.Out != nil {
			err = json.Unmarshal(resp.Body, req.Out)
			if err != nil {
				return fmt.Errorf("json.Unmarshal into Out: %s", err)
			}
		}
		return
	case 500:
		return checkErrorEnvelope(resp)
	default:
		return fmt.Errorf("statusCode: %d", resp.StatusCode)
	}
}

func checkErrorEnvelope(resp httpreq.Response) error {
	var env ErrorEnvelope
	err := json.Unmarshal(resp.Body, &env)
	if err != nil {
		return fmt.Errorf("json.Unmarshal into ErrorEnvelope: %s", err)
	}
	if env.Error != nil {
		return *env.Error
	}
	return nil
}
