package mailrelay

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

// Forwarder hands a raw message off to the hub for delivery.
//
// v1 is SINGLE-SHOT (§9 rule 9): exactly one attempt, no retry loop, no durable
// spool. The hub's HTTP status is returned to the caller; the SMTP layer maps that
// status to an SMTP reply so the sending app surfaces the real outcome. Forward has
// no SMTP knowledge — the seam keeps the engine unit-testable with a fake.
type Forwarder interface {
	Forward(ctx context.Context, raw []byte, mailFrom string, rcptTo []string) (status int, body string, err error)
}

// MailForwardRequest is the JSON envelope POSTed to the hub POST /api/v1/mail.
// RawMIME is a []byte so encoding/json base64-encodes it for safe transport and the
// hub gets the message back byte-for-byte (raw passthrough — never parsed/re-encoded).
type MailForwardRequest struct {
	RawMIME  []byte   `json:"raw_mime"`
	MailFrom string   `json:"mail_from"`
	RcptTo   []string `json:"rcpt_to"`
}

// HubForwarder POSTs to the hub /api/v1/mail with the controller's existing hub
// Bearer key — the same credential the notifier and report pusher already hold, so
// there is no second place the hub credential lives (the Shape-1 decision).
type HubForwarder struct {
	hubURL string
	apiKey string
	client *http.Client
}

// NewHubForwarder builds a forwarder against the hub base URL (e.g. https://hub.felhom.eu).
func NewHubForwarder(hubURL, apiKey string) *HubForwarder {
	return &HubForwarder{
		hubURL: strings.TrimRight(hubURL, "/"),
		apiKey: apiKey,
		// Generous timeout: the hub leg dials Resend SMTP synchronously. Still a single attempt.
		client: &http.Client{Timeout: 30 * time.Second},
	}
}

// Forward POSTs the raw message once and returns the hub's HTTP status + a short body
// excerpt. A transport error (hub unreachable) returns (0, "", err); the SMTP layer
// turns that into a transient 4xx so the app shows the user an error (no hang, no
// silent drop — §7 scenario D).
func (f *HubForwarder) Forward(ctx context.Context, raw []byte, mailFrom string, rcptTo []string) (int, string, error) {
	payload := MailForwardRequest{RawMIME: raw, MailFrom: mailFrom, RcptTo: rcptTo}
	jsonData, err := json.Marshal(payload)
	if err != nil {
		return 0, "", fmt.Errorf("marshaling mail forward: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, f.hubURL+"/api/v1/mail", bytes.NewReader(jsonData))
	if err != nil {
		return 0, "", fmt.Errorf("building request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+f.apiKey)
	req.Header.Set("Content-Type", "application/json")

	// SINGLE attempt — no retry loop (a retry would risk a duplicate send; §9 rule 9).
	resp, err := f.client.Do(req)
	if err != nil {
		return 0, "", fmt.Errorf("hub unreachable: %w", err)
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
	return resp.StatusCode, string(body), nil
}
