package svc

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

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

// Client provides HTTP communication with a sibling service.
// It wraps httpclient.Client with circuit breaker protection and
// automatic service URL resolution.
type Client struct {
	serviceName string
	baseURL     string
	httpClient  *httpclient.Client
}

// ClientConfig configures the service client.
type ClientConfig struct {
	// Timeout for individual requests (default: 10s)
	Timeout time.Duration

	// MaxRetries for transient failures (default: 3)
	MaxRetries int

	// CircuitBreaker config (uses defaults if nil)
	CircuitBreaker *httpclient.CircuitBreakerConfig
}

// DefaultClientConfig returns sensible defaults for service clients.
func DefaultClientConfig() ClientConfig {
	return ClientConfig{
		Timeout:    10 * time.Second,
		MaxRetries: 3,
	}
}

// NewClient creates a new client for communicating with a sibling service.
// The serviceName should match the component name (e.g., "auth-svc").
//
// Returns an error if the service URL is not configured (env var not set).
//
// Example:
//
//	client, err := svc.NewClient("auth-svc")
//	if err != nil {
//	    log.Fatal("auth service not available:", err)
//	}
func NewClient(serviceName string) (*Client, error) {
	return NewClientWithConfig(serviceName, DefaultClientConfig())
}

// NewClientWithConfig creates a new service client with custom configuration.
func NewClientWithConfig(serviceName string, config ClientConfig) (*Client, error) {
	url := ServiceURL(serviceName)
	if url == "" {
		return nil, fmt.Errorf("service %s not configured (missing %s_URL env var)", serviceName, toEnvKey(serviceName))
	}

	// Apply defaults
	if config.Timeout == 0 {
		config.Timeout = 10 * time.Second
	}
	if config.MaxRetries == 0 {
		config.MaxRetries = 3
	}

	// Build circuit breaker config
	var cbConfig httpclient.CircuitBreakerConfig
	if config.CircuitBreaker != nil {
		cbConfig = *config.CircuitBreaker
	} else {
		cbConfig = httpclient.DefaultCircuitBreakerConfig()
	}

	httpClient := httpclient.New(httpclient.Config{
		Timeout:        config.Timeout,
		MaxRetries:     config.MaxRetries,
		CircuitBreaker: httpclient.NewCircuitBreaker(cbConfig),
	})

	return &Client{
		serviceName: serviceName,
		baseURL:     url,
		httpClient:  httpClient,
	}, nil
}

// Get performs a GET request to the service.
func (c *Client) Get(ctx context.Context, path string) (*http.Response, error) {
	return c.Do(ctx, http.MethodGet, path, nil)
}

// Post performs a POST request with JSON body.
func (c *Client) Post(ctx context.Context, path string, body any) (*http.Response, error) {
	return c.DoJSON(ctx, http.MethodPost, path, body)
}

// Put performs a PUT request with JSON body.
func (c *Client) Put(ctx context.Context, path string, body any) (*http.Response, error) {
	return c.DoJSON(ctx, http.MethodPut, path, body)
}

// Delete performs a DELETE request.
func (c *Client) Delete(ctx context.Context, path string) (*http.Response, error) {
	return c.Do(ctx, http.MethodDelete, path, nil)
}

// Do performs an HTTP request to the service.
func (c *Client) Do(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
	url := c.baseURL + path
	req, err := http.NewRequestWithContext(ctx, method, url, body)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}
	return c.httpClient.Do(req)
}

// DoJSON performs an HTTP request with a JSON body.
func (c *Client) DoJSON(ctx context.Context, method, path string, body any) (*http.Response, 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)
	}

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

	return c.httpClient.Do(req)
}

// ServiceName returns the name of the service this client connects to.
func (c *Client) ServiceName() string {
	return c.serviceName
}

// BaseURL returns the base URL of the service.
func (c *Client) BaseURL() string {
	return c.baseURL
}

// DecodeResponse decodes a JSON response body into the given target.
// It closes the response body and returns an error if the status code indicates failure.
//
// Returns an error for non-2xx status codes, including the response body in the error message.
func DecodeResponse[T any](resp *http.Response) (T, error) {
	var result T
	defer resp.Body.Close()

	// Check for error status codes before attempting to decode
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, _ := io.ReadAll(resp.Body)
		return result, fmt.Errorf("HTTP %d: %s", resp.StatusCode, truncate(string(body), 200))
	}

	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return result, fmt.Errorf("decode response: %w", err)
	}
	return result, nil
}

// truncate limits a string to maxLen characters, adding "..." if truncated.
func truncate(s string, maxLen int) string {
	if len(s) <= maxLen {
		return s
	}
	return s[:maxLen] + "..."
}
