// Package webhook delivers HTTP POST notifications on task/workflow completion.
//
// v0.27.3: Retry (3 attempts, exponential backoff), HMAC-SHA256 signature,
// 10s timeout per attempt.
// v0.28.0: Added RunID, TokensUsed fields (D1 audit fix).
package webhook

import (
	"bytes"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"time"
)

const (
	maxAttempts    = 3
	timeout        = 10 * time.Second
	signatureHeader = "X-Switchboard-Signature"
)

// Payload is the JSON body sent to webhook endpoints.
type Payload struct {
	TaskID      string    `json:"task_id,omitempty"`
	RunID       string    `json:"run_id,omitempty"`
	TaskName    string    `json:"task_name,omitempty"`
	WorkflowID  string    `json:"workflow_id,omitempty"`
	ChannelID   string    `json:"channel_id,omitempty"`
	Status      string    `json:"status"`
	CompletedAt time.Time `json:"completed_at"`
	Output      string    `json:"output,omitempty"`       // last assistant message or relay payload
	TokensUsed  int       `json:"tokens_used,omitempty"`  // total tokens consumed
	StageData   any       `json:"stage_data,omitempty"`   // workflow stage data (if applicable)
	Error       string    `json:"error,omitempty"`
}

// Deliver sends a webhook payload to the given URL with HMAC signing.
// Retries up to 3 times with exponential backoff (1s, 5s, 25s).
// Runs synchronously — call in a goroutine for non-blocking delivery.
func Deliver(url, secret string, payload Payload) error {
	if url == "" {
		return nil
	}

	body, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("webhook marshal: %w", err)
	}

	// Compute HMAC signature
	signature := ""
	if secret != "" {
		mac := hmac.New(sha256.New, []byte(secret))
		mac.Write(body)
		signature = hex.EncodeToString(mac.Sum(nil))
	}

	client := &http.Client{Timeout: timeout}
	backoff := time.Second // 1s, 5s, 25s

	var lastErr error
	for attempt := 1; attempt <= maxAttempts; attempt++ {
		req, err := http.NewRequest("POST", url, bytes.NewReader(body))
		if err != nil {
			return fmt.Errorf("webhook request build: %w", err)
		}
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("User-Agent", "Switchboard-Webhook/1.0")
		if signature != "" {
			req.Header.Set(signatureHeader, signature)
		}

		resp, err := client.Do(req)
		if err != nil {
			lastErr = err
			log.Printf("[webhook] Attempt %d/%d failed for %s: %v", attempt, maxAttempts, url, err)
		} else {
			io.Copy(io.Discard, resp.Body)
			resp.Body.Close()

			if resp.StatusCode >= 200 && resp.StatusCode < 300 {
				if attempt > 1 {
					log.Printf("[webhook] Delivered to %s on attempt %d", url, attempt)
				}
				return nil
			}
			lastErr = fmt.Errorf("HTTP %d", resp.StatusCode)
			log.Printf("[webhook] Attempt %d/%d returned %d for %s", attempt, maxAttempts, resp.StatusCode, url)
		}

		if attempt < maxAttempts {
			time.Sleep(backoff)
			backoff *= 5
		}
	}

	return fmt.Errorf("webhook delivery failed after %d attempts: %w", maxAttempts, lastErr)
}

// GenerateSecret creates a random 32-byte hex-encoded webhook secret.
func GenerateSecret() string {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		// Fallback to timestamp-based (extremely unlikely)
		return fmt.Sprintf("%x", time.Now().UnixNano())
	}
	return hex.EncodeToString(b)
}
