package qcclient

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strings"
	"time"
)

type Client interface {
	Health(ctx context.Context) error
	ListAITemplates(ctx context.Context) ([]Template, error)
	GetTemplate(ctx context.Context, templateID int64) (*Template, error)
	GetTemplateSchema(ctx context.Context, templateID int64) (json.RawMessage, error)
	GenerateContent(ctx context.Context, req GenerateContentRequest) (GenerateContentData, json.RawMessage, error)
	CreateSite(ctx context.Context, req CreateSiteRequest) (*CreateSiteResponseData, json.RawMessage, error)
	GetJob(ctx context.Context, jobID int64) (*JobStatusData, json.RawMessage, error)
	GetEditorURL(ctx context.Context, siteID int64) (*SiteEditorLoginData, json.RawMessage, error)
}

type HTTPClient struct {
	baseURL string
	token   string
	client  *http.Client
	logger  *slog.Logger
}

func New(baseURL, token string, timeout time.Duration, logger *slog.Logger) *HTTPClient {
	if timeout <= 0 {
		timeout = 10 * time.Second
	}
	return &HTTPClient{
		baseURL: strings.TrimRight(baseURL, "/"),
		token:   token,
		client:  &http.Client{Timeout: timeout},
		logger:  logger,
	}
}

func (c *HTTPClient) Health(ctx context.Context) error {
	_, _, err := c.doJSON(ctx, http.MethodGet, "/health", nil)
	return err
}

func (c *HTTPClient) ListAITemplates(ctx context.Context) ([]Template, error) {
	body, _, err := c.doJSON(ctx, http.MethodGet, "/templates?type=ai", nil)
	if err != nil {
		return nil, err
	}
	var rsp APIResponse[[]Template]
	if err := json.Unmarshal(body, &rsp); err != nil {
		return nil, fmt.Errorf("decode templates response: %w", err)
	}
	return rsp.Data, nil
}

func (c *HTTPClient) GetTemplate(ctx context.Context, templateID int64) (*Template, error) {
	body, _, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("/templates/%d", templateID), nil)
	if err != nil {
		return nil, err
	}
	var rsp APIResponse[Template]
	if err := json.Unmarshal(body, &rsp); err != nil {
		return nil, fmt.Errorf("decode template response: %w", err)
	}
	return &rsp.Data, nil
}

func (c *HTTPClient) GetTemplateSchema(ctx context.Context, templateID int64) (json.RawMessage, error) {
	body, _, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("/templates/%d/schema", templateID), nil)
	if err != nil {
		return nil, err
	}
	return json.RawMessage(body), nil
}

func (c *HTTPClient) GenerateContent(ctx context.Context, req GenerateContentRequest) (GenerateContentData, json.RawMessage, error) {
	body, raw, err := c.doJSON(ctx, http.MethodPost, "/generate-content", req)
	if err != nil {
		return nil, nil, err
	}
	var rsp APIResponse[GenerateContentData]
	if err := json.Unmarshal(body, &rsp); err != nil {
		return nil, raw, fmt.Errorf("decode generate-content response: %w", err)
	}
	return rsp.Data, raw, nil
}

func (c *HTTPClient) CreateSite(ctx context.Context, req CreateSiteRequest) (*CreateSiteResponseData, json.RawMessage, error) {
	body, raw, err := c.doJSON(ctx, http.MethodPost, "/sites", req)
	if err != nil {
		return nil, nil, err
	}
	var rsp APIResponse[CreateSiteResponseData]
	if err := json.Unmarshal(body, &rsp); err != nil {
		return nil, raw, fmt.Errorf("decode create site response: %w", err)
	}
	return &rsp.Data, raw, nil
}

func (c *HTTPClient) GetJob(ctx context.Context, jobID int64) (*JobStatusData, json.RawMessage, error) {
	body, raw, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("/jobs/%d", jobID), nil)
	if err != nil {
		return nil, nil, err
	}
	var rsp APIResponse[JobStatusData]
	if err := json.Unmarshal(body, &rsp); err != nil {
		return nil, raw, fmt.Errorf("decode job response: %w", err)
	}
	return &rsp.Data, raw, nil
}

func (c *HTTPClient) GetEditorURL(ctx context.Context, siteID int64) (*SiteEditorLoginData, json.RawMessage, error) {
	body, raw, err := c.doJSON(ctx, http.MethodGet, fmt.Sprintf("/sites/%d/editor-url", siteID), nil)
	if err != nil {
		return nil, nil, err
	}
	var rsp APIResponse[SiteEditorLoginData]
	if err := json.Unmarshal(body, &rsp); err != nil {
		return nil, raw, fmt.Errorf("decode editor-url response: %w", err)
	}
	return &rsp.Data, raw, nil
}

func (c *HTTPClient) doJSON(ctx context.Context, method, path string, payload any) ([]byte, json.RawMessage, error) {
	var body io.Reader
	if payload != nil {
		b, err := json.Marshal(payload)
		if err != nil {
			return nil, nil, fmt.Errorf("marshal request: %w", err)
		}
		body = bytes.NewReader(b)
	}

	req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, body)
	if err != nil {
		return nil, nil, fmt.Errorf("build request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+c.token)
	req.Header.Set("Accept", "application/json")
	if payload != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	start := time.Now()
	res, err := c.client.Do(req)
	if err != nil {
		return nil, nil, fmt.Errorf("do request: %w", err)
	}
	defer res.Body.Close()

	respBody, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, nil, fmt.Errorf("read response: %w", err)
	}

	c.logger.Info("qc request",
		"method", method,
		"path", path,
		"status", res.StatusCode,
		"duration_ms", time.Since(start).Milliseconds(),
		"auth", "Bearer [REDACTED]",
	)

	if res.StatusCode >= 400 {
		return nil, json.RawMessage(respBody), c.mapHTTPError(res.StatusCode, respBody)
	}
	return respBody, json.RawMessage(respBody), nil
}

func (c *HTTPClient) mapHTTPError(statusCode int, raw []byte) error {
	var apiErr APIError
	_ = json.Unmarshal(raw, &apiErr)

	err := &HTTPError{
		StatusCode: statusCode,
		APIError:   &apiErr,
		RawBody:    raw,
	}
	switch statusCode {
	case http.StatusBadRequest:
		return fmt.Errorf("%w: %w", ErrBadRequest, err)
	case http.StatusUnauthorized:
		return fmt.Errorf("%w: %w", ErrUnauthorized, err)
	case http.StatusNotFound:
		return fmt.Errorf("%w: %w", ErrNotFound, err)
	default:
		return err
	}
}
