package api

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type Client struct {
	baseURL    string
	apiKey     string
	token      string
	httpClient *http.Client
}

func NewClient(baseURL, apiKey string) *Client {
	return &Client{
		baseURL: strings.TrimRight(baseURL, "/"),
		apiKey:  apiKey,
		httpClient: &http.Client{
			Timeout: 120 * time.Second,
			Transport: &http.Transport{
				MaxIdleConns:        32,
				MaxIdleConnsPerHost: 16,
				IdleConnTimeout:     90 * time.Second,
			},
		},
	}
}

func (c *Client) WithToken(token string) *Client {
	clone := *c
	clone.token = strings.TrimSpace(token)
	return &clone
}

func (c *Client) authHeader() string {
	if c.token != "" {
		return "Bearer " + c.token
	}
	return "Bearer " + c.apiKey
}

func (c *Client) do(ctx context.Context, method, path string, query url.Values, dest any) error {
	return c.doBody(ctx, method, path, query, nil, dest)
}

func (c *Client) doJSON(ctx context.Context, method, path string, body any, dest any) error {
	var payload io.Reader
	if body != nil {
		b, err := json.Marshal(body)
		if err != nil {
			return err
		}
		payload = strings.NewReader(string(b))
	}
	return c.doBody(ctx, method, path, nil, payload, dest)
}

func parseAPIError(status int, raw []byte) *APIError {
	var pe ProductErrorBody
	if err := json.Unmarshal(raw, &pe); err == nil {
		if pe.Error.Code != "" || pe.Error.Message != "" {
			return &APIError{Status: status, Code: pe.Error.Code, Message: pe.Error.Message}
		}
	}
	if msg := parseFastAPIDetail(raw); msg != "" {
		return &APIError{Status: status, Message: msg}
	}
	msg := strings.TrimSpace(string(raw))
	if len(msg) > 500 {
		msg = msg[:500]
	}
	if msg == "" {
		msg = http.StatusText(status)
	}
	return &APIError{Status: status, Message: msg}
}

func parseFastAPIDetail(raw []byte) string {
	var body struct {
		Detail json.RawMessage `json:"detail"`
	}
	if err := json.Unmarshal(raw, &body); err != nil || len(body.Detail) == 0 {
		return ""
	}
	var s string
	if err := json.Unmarshal(body.Detail, &s); err == nil {
		return strings.TrimSpace(s)
	}
	var items []struct {
		Msg string `json:"msg"`
	}
	if err := json.Unmarshal(body.Detail, &items); err == nil && len(items) > 0 {
		msgs := make([]string, 0, len(items))
		for _, it := range items {
			if m := strings.TrimSpace(it.Msg); m != "" {
				msgs = append(msgs, m)
			}
		}
		return strings.Join(msgs, " ")
	}
	return ""
}

func (c *Client) doDownload(ctx context.Context, method, path string, query url.Values) ([]byte, string, error) {
	u, err := url.Parse(c.baseURL + path)
	if err != nil {
		return nil, "", err
	}
	if len(query) > 0 {
		u.RawQuery = query.Encode()
	}
	req, err := http.NewRequestWithContext(ctx, method, u.String(), nil)
	if err != nil {
		return nil, "", err
	}
	req.Header.Set("Authorization", c.authHeader())
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Connection", "close")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, "", err
	}
	defer resp.Body.Close()
	raw, err := io.ReadAll(io.LimitReader(resp.Body, 32<<20))
	if err != nil {
		return nil, "", err
	}
	if resp.StatusCode >= 400 {
		return nil, "", parseAPIError(resp.StatusCode, raw)
	}
	ct := resp.Header.Get("Content-Type")
	return raw, ct, nil
}

func (c *Client) doBody(ctx context.Context, method, path string, query url.Values, body io.Reader, dest any) error {
	u, err := url.Parse(c.baseURL + path)
	if err != nil {
		return err
	}
	if len(query) > 0 {
		u.RawQuery = query.Encode()
	}

	return doWithRetry(ctx, 6, func() error {
		req, err := http.NewRequestWithContext(ctx, method, u.String(), body)
		if err != nil {
			return err
		}
		req.Header.Set("Authorization", c.authHeader())
		req.Header.Set("Accept", "application/json")
		req.Header.Set("Connection", "close")
		if body != nil {
			req.Header.Set("Content-Type", "application/json")
		}

		resp, err := c.httpClient.Do(req)
		if err != nil {
			return err
		}

		raw, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20))
		resp.Body.Close()
		if err != nil {
			return err
		}

		if resp.StatusCode == http.StatusServiceUnavailable || resp.StatusCode == http.StatusTooManyRequests {
			return &APIError{Status: resp.StatusCode, Message: "serviço temporariamente indisponível"}
		}

		if resp.StatusCode >= 400 {
			apiErr := parseAPIError(resp.StatusCode, raw)
			return apiErr
		}

		if dest == nil {
			return nil
		}
		if err := json.Unmarshal(raw, dest); err != nil {
			return fmt.Errorf("decode response: %w", err)
		}
		return nil
	})
}
