package elevenlabs

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

	"pitch-voiceover/pkg/httpkit"
)

const (
	defaultBaseURL    = "https://api.elevenlabs.io/v1"
	defaultTimeout    = 60 * time.Second
	defaultMaxRetries = 3
)

// Config holds configuration options for the ElevenLabs client.
type Config struct {
	// APIKey is required for authentication with the ElevenLabs API.
	APIKey string

	// BaseURL is the API base URL (optional, defaults to https://api.elevenlabs.io/v1).
	BaseURL string

	// Timeout is the HTTP request timeout (optional, defaults to 60s).
	Timeout time.Duration

	// MaxRetries is the number of retry attempts (optional, defaults to 3).
	MaxRetries int

	// Logger is the structured logger (optional, defaults to slog.Default()).
	Logger *slog.Logger
}

// Client is the ElevenLabs API client.
type Client struct {
	kit    *httpkit.Client
	config *Config
	logger *slog.Logger
}

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

	if config.BaseURL == "" {
		config.BaseURL = defaultBaseURL
	}

	if config.Timeout == 0 {
		config.Timeout = defaultTimeout
	}

	if config.MaxRetries == 0 {
		config.MaxRetries = defaultMaxRetries
	}

	if config.Logger == nil {
		config.Logger = slog.Default()
	}

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

	// ElevenLabs uses xi-api-key header, not Bearer token
	kit := httpkit.NewClient(httpkit.Config{
		Timeout:    config.Timeout,
		MaxRetries: config.MaxRetries,
		Logger:     config.Logger,
		Headers: map[string]string{
			"xi-api-key": config.APIKey,
		},
	})

	return &Client{
		kit:    kit,
		config: &config,
		logger: config.Logger,
	}, nil
}

// doRequest executes an HTTP request expecting a JSON response.
func (c *Client) doRequest(ctx context.Context, method, path string, body any) ([]byte, error) {
	resp, err := c.kit.Do(ctx, httpkit.Request{
		Method: method,
		URL:    c.config.BaseURL + path,
		Body:   body,
	})
	if err != nil {
		return nil, c.enrichError(err)
	}
	return resp, nil
}

// doRequestRaw executes an HTTP request expecting a binary response (e.g., audio).
// This bypasses httpkit's JSON handling for streaming audio responses.
func (c *Client) doRequestRaw(ctx context.Context, method, path string, body any, format OutputFormat) ([]byte, error) {
	var bodyReader io.Reader
	if body != nil {
		data, err := json.Marshal(body)
		if err != nil {
			return nil, fmt.Errorf("marshal request body: %w", err)
		}
		bodyReader = bytes.NewReader(data)
	}

	reqURL := c.config.BaseURL + path
	if format != "" {
		reqURL += "?output_format=" + string(format)
	}

	req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}

	req.Header.Set("xi-api-key", c.config.APIKey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "audio/mpeg")

	client := &http.Client{Timeout: c.config.Timeout}
	resp, err := client.Do(req)
	if err != nil {
		if errors.Is(err, context.DeadlineExceeded) {
			return nil, fmt.Errorf("%w: %v", ErrTimeout, err)
		}
		return nil, err
	}
	defer func() { _ = resp.Body.Close() }()

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

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

	// Parse error response
	return nil, c.parseErrorResponse(resp.StatusCode, respBody)
}

// enrichError parses provider-specific error details from httpkit.APIError.
func (c *Client) enrichError(err error) error {
	var apiErr *httpkit.APIError
	if !errors.As(err, &apiErr) {
		return err
	}

	return c.parseErrorResponse(apiErr.StatusCode, []byte(apiErr.Body))
}

// parseErrorResponse parses an ElevenLabs error response body.
func (c *Client) parseErrorResponse(statusCode int, body []byte) error {
	var errResp ErrorResponse
	if jsonErr := json.Unmarshal(body, &errResp); jsonErr != nil {
		// Couldn't parse as JSON, return raw body
		return NewAPIError(statusCode, string(body), "", httpkit.ClassifyStatus(statusCode))
	}

	// Map ElevenLabs-specific status codes to sentinel errors
	underlying := httpkit.ClassifyStatus(statusCode)
	status := errResp.Detail.Status
	message := errResp.Detail.Message

	// Check for quota exceeded
	if strings.Contains(strings.ToLower(message), "quota") ||
		strings.Contains(strings.ToLower(status), "quota") {
		underlying = ErrQuotaExceeded
	}

	// Check for voice not found
	if statusCode == http.StatusNotFound &&
		(strings.Contains(strings.ToLower(message), "voice") ||
			strings.Contains(strings.ToLower(status), "voice_not_found")) {
		underlying = ErrVoiceNotFound
	}

	return NewAPIError(statusCode, message, status, underlying)
}
