package grafana

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

	"git.ar21.de/yolokube/grafana-backuper/pkg/grafana/schema"
	"golang.org/x/net/http/httpguts"
)

const UserAgent = "grafana-backuper"

type Client struct {
	endpoint   string
	token      string
	tokenValid bool
	userAgent  string
	httpClient *http.Client

	Dashboard        DashboardClient
	DashboardVersion DashboardVersionClient
	File             SearchClient
}

type ClientOption func(*Client)

func WithToken(token string) ClientOption {
	return func(client *Client) {
		client.token = token
		client.tokenValid = httpguts.ValidHeaderFieldName(token)
	}
}

func WithHTTPClient(httpClient *http.Client) ClientOption {
	return func(client *Client) {
		client.httpClient = httpClient
	}
}

func WithUserAgent(userAgent string) ClientOption {
	return func(client *Client) {
		client.userAgent = userAgent
	}
}

func NewClient(endpoint string, options ...ClientOption) *Client {
	client := &Client{
		endpoint:   endpoint,
		tokenValid: true,
		httpClient: &http.Client{},
		userAgent:  UserAgent,
	}

	for _, option := range options {
		option(client)
	}

	client.Dashboard = DashboardClient{client: client}
	client.DashboardVersion = DashboardVersionClient{client: client}
	client.File = SearchClient{client: client}

	return client
}

func (c *Client) newRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) {
	url := fmt.Sprintf("%s%s", c.endpoint, path)
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", c.userAgent)

	if !c.tokenValid {
		return nil, errors.New("authorization token contains invalid characters")
	}

	if c.token != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	return req.WithContext(ctx), nil
}

type Response struct {
	*http.Response
	ErrorResponse *ErrorResponse

	body []byte
}

type ErrorResponse struct {
	Message string
	TraceID uint
}

func (c *Client) do(req *http.Request, v any) (*Response, error) {
	httpResp, err := c.httpClient.Do(req)
	resp := &Response{Response: httpResp}
	if err != nil {
		return resp, err
	}
	defer httpResp.Body.Close()

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

	resp.Body = io.NopCloser(bytes.NewReader(body))

	if resp.StatusCode != http.StatusOK {
		var errorResponse schema.ErrorResponse
		if err = json.Unmarshal(resp.body, &errorResponse); err == nil {
			return resp, fmt.Errorf(
				"grafana: got error with status code %d: %s",
				resp.StatusCode,
				ErrorResponseFromSchema(errorResponse).Message,
			)
		}
		return resp, fmt.Errorf("grafana: server responded with an unexpected status code %d", resp.StatusCode)
	}

	if v != nil {
		if writer, ok := v.(io.Writer); ok {
			_, err = io.Copy(writer, bytes.NewReader(resp.body))
		} else {
			err = json.Unmarshal(resp.body, v)
		}
	}

	return resp, err
}