package main

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

// SendLark posts an interactive card with all flagged tickets to the Lark webhook.
// `window` is shown at the top of the card so the recipient knows which slice
// of time the alert covers. Scanned/flagged counts are intentionally NOT shown
// in the card — they live in the cron log instead.
func SendLark(cfg *Config, items []NotifyItem, window Window) error {
	if len(items) == 0 {
		return nil
	}

	var sb strings.Builder

	// One short line, e.g. "Past 1h (as of 2026-04-08 11:25 +0800)".
	endTime := window.End
	if endTime.IsZero() {
		endTime = time.Now()
	}
	fmt.Fprintf(&sb, "Past %s (as of %s)\n\n---\n\n",
		formatDuration(window.Lookback),
		endTime.In(time.FixedZone("UTC+8", 8*3600)).Format("2006-01-02 15:04 -0700"))

	for i, it := range items {
		if i > 0 {
			sb.WriteString("\n\n---\n\n")
		}
		fmt.Fprintf(&sb, "**[%s](%s)** — %s\n\n", it.Ticket.Key, it.Ticket.URL, escapeMD(it.Ticket.Summary))
		createdStr := "-"
		if !it.Ticket.Created.IsZero() {
			createdStr = it.Ticket.Created.In(time.FixedZone("UTC+8", 8*3600)).Format("2006-01-02 15:04")
		}
		fmt.Fprintf(&sb, "Status: %s | Priority: %s | Assignee: %s | Created: %s\n\n",
			or(it.Ticket.Status, "-"), or(it.Ticket.Priority, "-"), or(it.Ticket.Assignee, "-"), createdStr)
		fmt.Fprintf(&sb, "**Why / 原因:**\n\nEN: %s\n\nZH: %s",
			escapeMD(it.ReasonEN), escapeMD(it.ReasonZH))
	}

	card := map[string]any{
		"msg_type": "interactive",
		"card": map[string]any{
			"config": map[string]any{"wide_screen_mode": true},
			"header": map[string]any{
				"title":    map[string]any{"tag": "plain_text", "content": "🚨 Jira tickets needing attention"},
				"template": "red",
			},
			"elements": []any{
				map[string]any{
					"tag":     "div",
					"text":    map[string]any{"tag": "lark_md", "content": sb.String()},
				},
			},
		},
	}

	body, _ := json.Marshal(card)
	req, err := http.NewRequest("POST", cfg.LarkWebhook, bytes.NewReader(body))
	if err != nil {
		return err
	}
	req.Header.Set("content-type", "application/json")

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("lark request: %w", err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)
	if resp.StatusCode >= 300 {
		return fmt.Errorf("lark %d: %s", resp.StatusCode, string(raw))
	}
	// Lark returns {"code":0, ...} on success.
	var r struct {
		Code int    `json:"code"`
		Msg  string `json:"msg"`
	}
	_ = json.Unmarshal(raw, &r)
	if r.Code != 0 {
		return fmt.Errorf("lark code=%d msg=%s", r.Code, r.Msg)
	}
	return nil
}

type NotifyItem struct {
	Ticket   Ticket
	ReasonEN string
	ReasonZH string
}

func escapeMD(s string) string {
	s = strings.ReplaceAll(s, "\n", " ")
	return s
}

func or(a, b string) string {
	if a == "" {
		return b
	}
	return a
}

// formatDuration renders durations like "1h", "30m", "1h30m" instead of
// Go's default "1h0m0s".
func formatDuration(d time.Duration) string {
	d = d.Round(time.Minute)
	h := int(d / time.Hour)
	m := int((d % time.Hour) / time.Minute)
	switch {
	case h > 0 && m > 0:
		return fmt.Sprintf("%dh%dm", h, m)
	case h > 0:
		return fmt.Sprintf("%dh", h)
	default:
		return fmt.Sprintf("%dm", m)
	}
}
