// Package notify provides a Go client for the orchard9 notify service.
//
// The notify service handles email delivery with provider routing, retries,
// delivery tracking, and suppression handling.
//
// Basic usage:
//
//	client, err := notify.NewClient(notify.Config{
//	    URL:    os.Getenv("NOTIFY_URL"),
//	    APIKey: os.Getenv("NOTIFY_API_KEY"),
//	})
//	if err != nil {
//	    log.Fatal(err)
//	}
//
//	resp, err := client.SendEmail(ctx, &notify.SendRequest{
//	    To:   "user@example.com",
//	    From: "noreply@myapp.threesix.ai",
//	    Content: notify.Content{Subject: "Hello", Text: "World"},
//	    Meta:    notify.Meta{Host: "myapp.threesix.ai"},
//	})
package notify

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

	"{{GO_MODULE}}/pkg/httpclient"
)

const (
	defaultTimeout    = 30 * time.Second
	defaultMaxRetries = 3
)

// Config holds configuration for the notify client.
type Config struct {
	URL        string        // Required: base URL (e.g. "https://notify.threesix.ai")
	APIKey     string        // Required: send API key (notify_send_xxx)
	Timeout    time.Duration // Optional: defaults to 30s
	MaxRetries int           // Optional: defaults to 3
	Logger     *slog.Logger  // Optional: defaults to slog.Default()
}

// Client is the notify API client.
type Client struct {
	httpClient *httpclient.Client
	baseURL    string
	apiKey     string
	logger     *slog.Logger
}

// NewClient creates a new notify API client.
func NewClient(config Config) (*Client, error) {
	if config.URL == "" {
		return nil, fmt.Errorf("%w: URL is required", ErrInvalidConfig)
	}
	if config.APIKey == "" {
		return nil, fmt.Errorf("%w: API key is required", ErrInvalidConfig)
	}

	if _, err := url.Parse(config.URL); err != nil {
		return nil, fmt.Errorf("%w: invalid URL: %v", ErrInvalidConfig, err)
	}

	if config.Timeout == 0 {
		config.Timeout = defaultTimeout
	}
	if config.MaxRetries == 0 {
		config.MaxRetries = defaultMaxRetries
	}
	if config.Logger == nil {
		config.Logger = slog.Default()
	}

	return &Client{
		httpClient: httpclient.New(httpclient.Config{
			Timeout:    config.Timeout,
			MaxRetries: config.MaxRetries,
			Logger:     config.Logger,
		}),
		baseURL: config.URL,
		apiKey:  config.APIKey,
		logger:  config.Logger,
	}, nil
}

// SendEmail sends an email through the notify service.
// Returns the message ID and status on success (202 Accepted).
func (c *Client) SendEmail(ctx context.Context, req *SendRequest) (*SendResponse, error) {
	respBody, err := c.doRequest(ctx, http.MethodPost, "/email", req)
	if err != nil {
		return nil, err
	}

	var resp SendResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return nil, fmt.Errorf("unmarshal response: %w", err)
	}
	return &resp, nil
}

// GetMessage retrieves the full status of a sent message.
func (c *Client) GetMessage(ctx context.Context, id string) (*Message, error) {
	respBody, err := c.doRequest(ctx, http.MethodGet, "/messages/"+id, nil)
	if err != nil {
		return nil, err
	}

	var msg Message
	if err := json.Unmarshal(respBody, &msg); err != nil {
		return nil, fmt.Errorf("unmarshal response: %w", err)
	}
	return &msg, nil
}

// doRequest is a helper for making HTTP requests to the notify API.
func (c *Client) doRequest(ctx context.Context, method, path string, bodyData interface{}) ([]byte, error) {
	var reqBody io.Reader
	if bodyData != nil {
		jsonBody, err := json.Marshal(bodyData)
		if err != nil {
			return nil, fmt.Errorf("marshal request: %w", err)
		}
		reqBody = bytes.NewReader(jsonBody)
	}

	req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, reqBody)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.apiKey)
	if bodyData != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

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

	// 2xx = success
	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return respBody, nil
	}

	// Parse error response
	var errResp ErrorResponse
	baseErr := classifyError(resp.StatusCode, "")

	if err := json.Unmarshal(respBody, &errResp); err != nil {
		return nil, &APIError{
			StatusCode: resp.StatusCode,
			Message:    string(respBody),
			err:        baseErr,
		}
	}

	// Re-classify with the error code from the response.
	if errResp.Code != "" {
		baseErr = classifyError(resp.StatusCode, errResp.Code)
	}

	return nil, &APIError{
		StatusCode: resp.StatusCode,
		Message:    errResp.Error,
		Code:       errResp.Code,
		err:        baseErr,
	}
}
