// Package sender implements the actual SMS (Kavenegar) and Email (SMTP) delivery.
// Stdlib only — keeps the Go build hermetic/vendored.
package sender

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/smtp"
	"net/url"
	"strings"
	"time"
)

// ── SMS via Kavenegar ────────────────────────────────────────────────────────

type kavenegarResp struct {
	Return  struct {
		Status  int    `json:"status"`
		Message string `json:"message"`
	} `json:"return"`
	Entries []struct {
		MessageID int64 `json:"messageid"`
		Status    int   `json:"status"`
	} `json:"entries"`
}

// SendSMS sends a message via the Kavenegar REST API. Returns the provider message id.
func SendSMS(apiKey, line, receptor, message string) (string, error) {
	if apiKey == "" {
		return "", fmt.Errorf("kavenegar api key not configured")
	}
	endpoint := fmt.Sprintf("https://api.kavenegar.com/v1/%s/sms/send.json", url.PathEscape(apiKey))
	form := url.Values{}
	form.Set("receptor", receptor)
	form.Set("message", message)
	if line != "" {
		form.Set("sender", line)
	}

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.PostForm(endpoint, form)
	if err != nil {
		return "", fmt.Errorf("kavenegar request failed: %w", err)
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)

	var kr kavenegarResp
	if err := json.Unmarshal(body, &kr); err != nil {
		return "", fmt.Errorf("kavenegar unexpected response (%d): %s", resp.StatusCode, truncate(string(body), 200))
	}
	if kr.Return.Status != 200 {
		return "", fmt.Errorf("kavenegar error %d: %s", kr.Return.Status, kr.Return.Message)
	}
	if len(kr.Entries) > 0 {
		return fmt.Sprintf("%d", kr.Entries[0].MessageID), nil
	}
	return "", nil
}

// ── Email via SMTP ───────────────────────────────────────────────────────────

type SMTPConfig struct {
	Host      string
	Port      int
	Username  string
	Password  string
	FromEmail string
	FromName  string
	UseTLS    bool // implicit TLS (e.g. port 465); otherwise STARTTLS is attempted
}

// SendEmail sends an HTML email. Supports implicit TLS (465) and STARTTLS (587/25).
func SendEmail(cfg SMTPConfig, to, subject, htmlBody string) error {
	if cfg.Host == "" {
		return fmt.Errorf("smtp host not configured")
	}
	port := cfg.Port
	if port == 0 {
		port = 587
	}
	from := cfg.FromEmail
	if from == "" {
		from = cfg.Username
	}
	fromHeader := from
	if cfg.FromName != "" {
		fromHeader = fmt.Sprintf("%s <%s>", cfg.FromName, from)
	}

	msg := strings.Join([]string{
		"From: " + fromHeader,
		"To: " + to,
		"Subject: " + mimeEncode(subject),
		"MIME-Version: 1.0",
		"Content-Type: text/html; charset=UTF-8",
		"",
		htmlBody,
	}, "\r\n")

	addr := fmt.Sprintf("%s:%d", cfg.Host, port)
	auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)

	// Implicit TLS (port 465 style)
	if cfg.UseTLS || port == 465 {
		conn, err := tls.Dial("tcp", addr, &tls.Config{ServerName: cfg.Host})
		if err != nil {
			return fmt.Errorf("smtp tls dial: %w", err)
		}
		client, err := smtp.NewClient(conn, cfg.Host)
		if err != nil {
			return fmt.Errorf("smtp client: %w", err)
		}
		defer client.Close()
		return sendVia(client, auth, from, to, msg)
	}

	// STARTTLS (587/25) — smtp.SendMail upgrades to TLS when the server offers it.
	return smtp.SendMail(addr, auth, from, []string{to}, []byte(msg))
}

func sendVia(client *smtp.Client, auth smtp.Auth, from, to, msg string) error {
	if ok, _ := client.Extension("AUTH"); ok {
		if err := client.Auth(auth); err != nil {
			return fmt.Errorf("smtp auth: %w", err)
		}
	}
	if err := client.Mail(from); err != nil {
		return err
	}
	if err := client.Rcpt(to); err != nil {
		return err
	}
	w, err := client.Data()
	if err != nil {
		return err
	}
	if _, err := w.Write([]byte(msg)); err != nil {
		return err
	}
	if err := w.Close(); err != nil {
		return err
	}
	return client.Quit()
}

func mimeEncode(s string) string {
	// RFC 2047 encoded-word for non-ASCII subjects (Persian).
	for _, r := range s {
		if r > 127 {
			return "=?UTF-8?B?" + base64Std(s) + "?="
		}
	}
	return s
}

func base64Std(s string) string {
	return base64.StdEncoding.EncodeToString([]byte(s))
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n]
}
