// Package crossseed is the Phase 9 cross-seed REST client. The match
// event stream arrives via the Phase 4 webhook handler; this client polls
// cross-seed's API for activity stats + the "search now" operator action.
package crossseed

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"
)

// SecretsAccessor mirrors the narrow Get interface used by every
// Phase 1/3/5/9 integration.
type SecretsAccessor interface {
	Get(ctx context.Context, key string) ([]byte, error)
}

// APIKeySecretKey returns the canonical secrets-store key for a cross-seed
// API key.
func APIKeySecretKey(toolID string) string {
	return "automation_tool:" + toolID + ":api_key"
}

// Client is the read-side wrapper.
type Client struct {
	id      string
	baseURL string
	http    *http.Client
	secrets SecretsAccessor
}

// Config is the constructor shape.
type Config struct {
	ID      string
	BaseURL string
	HTTP    *http.Client
	Secrets SecretsAccessor
}

// New constructs a client.
func New(cfg Config) (*Client, error) {
	if cfg.ID == "" || cfg.BaseURL == "" {
		return nil, errors.New("crossseed: id and base_url required")
	}
	hc := cfg.HTTP
	if hc == nil {
		hc = &http.Client{Timeout: 20 * time.Second}
	}
	return &Client{
		id:      cfg.ID,
		baseURL: strings.TrimRight(cfg.BaseURL, "/"),
		http:    hc,
		secrets: cfg.Secrets,
	}, nil
}

// ID returns the operator-facing id.
func (c *Client) ID() string { return c.id }

// Search asks cross-seed to look for matches now. Returns the HTTP status
// for operator visibility.
func (c *Client) Search(ctx context.Context) (int, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/webhook?action=search", nil)
	if err != nil {
		return 0, err
	}
	if err := c.attachAPIKey(ctx, req); err != nil {
		return 0, err
	}
	resp, err := c.http.Do(req)
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()
	return resp.StatusCode, nil
}

// Stats returns cross-seed activity stats. The exact shape varies across
// cross-seed versions; we return the raw decoded JSON.
func (c *Client) Stats(ctx context.Context) (map[string]any, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/api/stats", nil)
	if err != nil {
		return nil, err
	}
	if err := c.attachAPIKey(ctx, req); err != nil {
		return nil, err
	}
	resp, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
		return nil, fmt.Errorf("crossseed: stats %d: %s", resp.StatusCode, body)
	}
	var m map[string]any
	if err := json.NewDecoder(io.LimitReader(resp.Body, 8<<20)).Decode(&m); err != nil {
		return nil, err
	}
	return m, nil
}

// Health is a reachability check.
func (c *Client) Health(ctx context.Context) error {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/", nil)
	if err != nil {
		return err
	}
	resp, err := c.http.Do(req)
	if err != nil {
		return err
	}
	_ = resp.Body.Close()
	if resp.StatusCode >= 500 {
		return fmt.Errorf("crossseed: health status %d", resp.StatusCode)
	}
	return nil
}

func (c *Client) attachAPIKey(ctx context.Context, req *http.Request) error {
	if c.secrets == nil {
		return nil
	}
	key, err := c.secrets.Get(ctx, APIKeySecretKey(c.id))
	if err != nil {
		// Operator may not have set an API key; some cross-seed deployments
		// run unauthenticated.
		return nil
	}
	req.Header.Set("X-Api-Key", strings.TrimSpace(string(key)))
	return nil
}
