package notify

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

const resendBaseURL = "https://api.resend.com"

// resendAPI is the interface Provisioner uses to call the Resend API.
// Extracted for testability.
type resendAPI interface {
	createDomain(ctx context.Context, name, region string) (domainID string, records []resendDNSRecord, err error)
	verifyDomain(ctx context.Context, domainID string) error
	deleteDomain(ctx context.Context, domainID string) error
}

// resendClient calls the Resend API for domain management.
type resendClient struct {
	apiKey     string
	httpClient *http.Client
}

// resendDNSRecord is a DNS record returned by Resend after domain creation.
// The "record" JSON field contains the DNS record type (e.g., "TXT", "MX").
type resendDNSRecord struct {
	Record   string `json:"record"` // DNS record type: "TXT", "MX", "CNAME"
	Name     string `json:"name"`   // relative name (e.g., "resend._domainkey")
	Value    string `json:"value"`  // record content
	Priority int    `json:"priority,omitempty"`
}

// resendCreateDomainResponse is the shape returned by POST /domains.
type resendCreateDomainResponse struct {
	ID      string            `json:"id"`
	Name    string            `json:"name"`
	Records []resendDNSRecord `json:"records"`
}

func newResendClient(apiKey string) *resendClient {
	return &resendClient{
		apiKey: apiKey,
		httpClient: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// createDomain creates a new Resend domain and returns the domain ID and DNS records to set.
func (r *resendClient) createDomain(ctx context.Context, name, region string) (domainID string, records []resendDNSRecord, err error) {
	payload := map[string]string{"name": name, "region": region}
	respBody, err := r.doRequest(ctx, http.MethodPost, "/domains", payload)
	if err != nil {
		return "", nil, fmt.Errorf("create resend domain %s: %w", name, err)
	}

	var resp resendCreateDomainResponse
	if err := json.Unmarshal(respBody, &resp); err != nil {
		return "", nil, fmt.Errorf("unmarshal resend domain response: %w", err)
	}
	return resp.ID, resp.Records, nil
}

// verifyDomain triggers domain verification on Resend.
func (r *resendClient) verifyDomain(ctx context.Context, domainID string) error {
	_, err := r.doRequest(ctx, http.MethodPost, "/domains/"+domainID+"/verify", nil)
	if err != nil {
		return fmt.Errorf("verify resend domain %s: %w", domainID, err)
	}
	return nil
}

// deleteDomain removes a Resend domain by ID.
func (r *resendClient) deleteDomain(ctx context.Context, domainID string) error {
	_, err := r.doRequest(ctx, http.MethodDelete, "/domains/"+domainID, nil)
	if err != nil {
		return fmt.Errorf("delete resend domain %s: %w", domainID, err)
	}
	return nil
}

// doRequest executes an HTTP request against the Resend API.
func (r *resendClient) doRequest(ctx context.Context, method, path string, bodyData any) ([]byte, error) {
	var reqBody io.Reader
	if bodyData != nil {
		jsonBody, err := json.Marshal(bodyData)
		if err != nil {
			return nil, fmt.Errorf("marshal request: %w", err)
		}
		reqBody = bytes.NewReader(jsonBody)
	}

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

	req.Header.Set("Authorization", "Bearer "+r.apiKey)
	if bodyData != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := r.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("http do: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode == http.StatusNoContent {
		return nil, nil
	}

	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
	}

	return nil, fmt.Errorf("resend API error (HTTP %d): %s", resp.StatusCode, string(respBody))
}
