package control

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

// Delivery is opt-in. Only a sanitized alert identifier, rule and message leave
// the control store; no provider payload or credential is included.
func (o *Operations) StartNotifications(ctx context.Context, endpoint string) error {
	if endpoint == "" {
		return nil
	}
	u, err := url.Parse(endpoint)
	if err != nil || u.Scheme != "https" || u.Host == "" || u.User != nil {
		return fmt.Errorf("notification endpoint must be HTTPS without URL credentials")
	}
	client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
	go func() {
		ticker := time.NewTicker(30 * time.Second)
		defer ticker.Stop()
		for {
			_ = o.deliverOne(ctx, client, endpoint, time.Now().UTC())
			select {
			case <-ctx.Done():
				return
			case <-ticker.C:
			}
		}
	}()
	return nil
}

func (o *Operations) deliverOne(ctx context.Context, client *http.Client, endpoint string, now time.Time) error {
	var id, alert, rule, message, status string
	var attempts int
	tx, err := o.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	err = tx.QueryRowContext(ctx, `SELECT d.id,d.alert_id,d.attempts,a.rule,a.message,a.status FROM deliveries d JOIN alerts a ON a.id=d.alert_id WHERE d.status IN ('pending','failed','sending') AND d.attempts<3 AND d.next_at<=? ORDER BY d.next_at LIMIT 1`, stamp(now)).Scan(&id, &alert, &attempts, &rule, &message, &status)
	if err != nil {
		return err
	}
	// A durable expiring claim permits recovery after a sender crash. The
	// destination must honor Idempotency-Key because HTTP is at least once.
	res, err := tx.ExecContext(ctx, "UPDATE deliveries SET status='sending',attempts=attempts+1,next_at=?,message='正在发送' WHERE id=? AND attempts=?", stamp(now.Add(time.Minute)), id, attempts)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n != 1 {
		return fmt.Errorf("delivery claim conflict")
	}
	if err = tx.Commit(); err != nil {
		return err
	}
	body, _ := json.Marshal(map[string]string{"alert_id": alert, "rule": rule, "status": status, "message": message, "environment": o.Environment})
	req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Idempotency-Key", id)
	response, sendErr := client.Do(req)
	delivery := "failed"
	detail := "通知发送失败；等待有限重试"
	if sendErr == nil {
		response.Body.Close()
		if response.StatusCode >= 200 && response.StatusCode < 300 {
			delivery = "sent"
			detail = "渠道已接受；不等于处理人已确认"
		}
	}
	if delivery == "failed" && attempts+1 >= 3 {
		detail = "通知失败已达3次；请在 Portal 处理并检查渠道"
	}
	_, err = o.db.ExecContext(ctx, "UPDATE deliveries SET status=?,message=?,next_at=? WHERE id=?", delivery, detail, stamp(now.Add(time.Duration(attempts+1)*time.Minute)), id)
	return err
}
