package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"
)

func main() {
	log.SetFlags(log.LstdFlags | log.Lmsgprefix)
	log.SetPrefix("[ticket-notify] ")

	configPath := flag.String("config", defaultConfigPath(), "path to config.json")
	dryRun := flag.Bool("dry-run", false, "don't send to Lark, just print")
	lookbackFlag := flag.Duration("lookback", 0, "lookback window, e.g. 10m, 1h, 2h30m (overrides config.lookback_minutes)")
	endFlag := flag.String("end", "", "optional end time (RFC3339 or '2006-01-02 15:04', local TZ); if set, the window is [end-lookback, end]")
	debug := flag.Bool("debug", false, "dump raw Jira tickets and Gemini request/response for inspection")
	flag.Parse()

	SetDebug(*debug)

	cfg, err := LoadConfig(*configPath)
	if err != nil {
		log.Fatalf("config: %v", err)
	}

	window, err := resolveWindow(cfg, *lookbackFlag, *endFlag)
	if err != nil {
		log.Fatalf("window: %v", err)
	}
	if window.End.IsZero() {
		log.Printf("window: last %s (up to now)", window.Lookback)
	} else {
		log.Printf("window: %s → %s",
			window.End.Add(-window.Lookback).Format(time.RFC3339),
			window.End.Format(time.RFC3339))
	}

	state, err := LoadState(cfg.StateFile)
	if err != nil {
		log.Fatalf("state: %v", err)
	}

	tickets, err := FetchRecentTickets(cfg, window)
	if err != nil {
		log.Fatalf("jira: %v", err)
	}
	log.Printf("fetched %d recently-updated tickets", len(tickets))
	if len(tickets) == 0 {
		return
	}

	// Always re-evaluate every ticket in the window. A ticket that was boring
	// an hour ago may be on fire now. Dedup happens ONLY on the notification
	// side: once we've pushed a ticket to Lark, we never push it again (until
	// its state entry expires).
	flagged, erroredKeys, err := FilterTickets(cfg, tickets)
	if err != nil {
		log.Fatalf("gemini: %v", err)
	}
	if len(erroredKeys) > 0 {
		log.Printf("gemini errored on %d/%d tickets; they will be retried next run",
			len(erroredKeys), len(tickets))
	}
	log.Printf("gemini flagged %d tickets", len(flagged))

	// Suppress any flagged ticket whose key has already been notified.
	byKey := map[string]Ticket{}
	for _, t := range tickets {
		byKey[t.Key] = t
	}
	var items []NotifyItem
	var suppressed int
	for _, f := range flagged {
		t, ok := byKey[f.Key]
		if !ok {
			log.Printf("warn: gemini returned unknown key %q", f.Key)
			continue
		}
		if state.AlreadyNotified(f.Key) {
			suppressed++
			log.Printf("  suppress %s: already notified previously", f.Key)
			continue
		}
		items = append(items, NotifyItem{Ticket: t, ReasonEN: f.ReasonEN, ReasonZH: f.ReasonZH})
	}
	if suppressed > 0 {
		log.Printf("suppressed %d flagged tickets that were already notified", suppressed)
	}

	if len(items) == 0 {
		log.Printf("nothing new to notify")
		return
	}

	if *dryRun {
		for _, it := range items {
			log.Printf("DRY-RUN would notify: %s — %s\n  EN: %s\n  ZH: %s",
				it.Ticket.Key, it.Ticket.Summary, it.ReasonEN, it.ReasonZH)
		}
		return
	}

	if err := SendLark(cfg, items, window); err != nil {
		log.Fatalf("lark: %v", err)
	}
	log.Printf("notified lark with %d tickets", len(items))

	// Only mark as notified after a successful Lark push.
	for _, it := range items {
		state.MarkNotified(it.Ticket.Key)
	}
	if err := state.Save(cfg.StateFile); err != nil {
		log.Printf("save state: %v", err)
	}
}

// Window describes the Jira "updated" time range we care about.
// If End is zero, the window is the last Lookback duration up to "now".
// Otherwise the window is [End-Lookback, End].
type Window struct {
	Lookback time.Duration
	End      time.Time
}

func resolveWindow(cfg *Config, lookbackFlag time.Duration, endFlag string) (Window, error) {
	w := Window{Lookback: lookbackFlag}
	if w.Lookback <= 0 {
		w.Lookback = time.Duration(cfg.LookbackMinutes) * time.Minute
	}
	if w.Lookback <= 0 {
		return w, fmt.Errorf("lookback must be > 0")
	}
	if endFlag != "" {
		t, err := parseFlexibleTime(endFlag)
		if err != nil {
			return w, fmt.Errorf("parse -end: %w", err)
		}
		w.End = t
	}
	return w, nil
}

func parseFlexibleTime(s string) (time.Time, error) {
	layouts := []string{
		time.RFC3339,
		"2006-01-02T15:04:05",
		"2006-01-02 15:04:05",
		"2006-01-02 15:04",
		"2006/01/02 15:04",
	}
	for _, l := range layouts {
		if t, err := time.ParseInLocation(l, s, time.Local); err == nil {
			return t, nil
		}
	}
	return time.Time{}, fmt.Errorf("unrecognized time format %q", s)
}

func defaultConfigPath() string {
	if p := os.Getenv("TICKET_NOTIFY_CONFIG"); p != "" {
		return p
	}
	exe, err := os.Executable()
	if err == nil {
		return filepath.Join(filepath.Dir(exe), "config.json")
	}
	return "config.json"
}
