package services

import (
	"context"
	"encoding/json"
	"strings"
	"testing"
	"time"

	"github.com/cloudwego/eino/components/tool"
	"wa-assistant/backend/models"
)

func TestFollowUpToolPersistsScopedReminderOnceAndHonorsDryRun(t *testing.T) {
	db := agenticTestDB(t)
	if err := db.AutoMigrate(&models.FollowUpTask{}, &models.OptOut{}, &models.Contact{}); err != nil {
		t.Fatal(err)
	}
	due := time.Now().In(FollowUpLocation).AddDate(0, 0, 1)
	due = time.Date(due.Year(), due.Month(), due.Day(), 16, 0, 0, 0, FollowUpLocation)
	input, _ := json.Marshal(map[string]any{"due_at": due.Format(time.RFC3339), "reason": "Melanjutkan konsultasi", "request_text": "ingatkan besok sore", "agent_id": 2, "sender": "628999999999"})
	opt := ChatOptions{Sender: "6281234567890", LatestCustomerMessage: "Kak ingatkan besok sore ya", SourceMessageID: "wa-reminder-1"}
	for i := 0; i < 2; i++ {
		state := &agentTurnState{userMessage: opt.LatestCustomerMessage}
		base, err := makeFollowUpTool(1, opt, state)
		if err != nil {
			t.Fatal(err)
		}
		out, err := base.(tool.InvokableTool).InvokableRun(context.Background(), string(input))
		if err != nil || strings.Contains(out, "not_prepared") {
			t.Fatalf("prepare: %s %v", out, err)
		}
		if !strings.Contains(state.businessEvidence, "pengingat") {
			t.Fatal("successful action missing from answer evidence")
		}
	}
	var rows []models.FollowUpTask
	db.Find(&rows)
	if len(rows) != 1 || rows[0].AgentID != 1 || rows[0].Number != opt.Sender || rows[0].Status != "pending" || !rows[0].DueAt.Equal(due) {
		t.Fatalf("scope or deduplication failed: %+v", rows)
	}
	// A CS completion is never undone by re-delivery of the original WA message.
	db.Model(&rows[0]).Update("status", "completed")
	base, _ := makeFollowUpTool(1, opt, &agentTurnState{userMessage: opt.LatestCustomerMessage})
	out, _ := base.(tool.InvokableTool).InvokableRun(context.Background(), string(input))
	if !strings.Contains(out, "not_prepared") {
		t.Fatal("completed reminder reopened")
	}
	base, _ = makeFollowUpTool(2, ChatOptions{DryRun: true}, &agentTurnState{userMessage: "ingatkan besok sore"})
	out, err := base.(tool.InvokableTool).InvokableRun(context.Background(), string(input))
	if err != nil || !strings.Contains(out, `"dry_run":true`) {
		t.Fatal(out, err)
	}
	var count int64
	db.Model(&models.FollowUpTask{}).Count(&count)
	if count != 1 {
		t.Fatal("simulation wrote a real reminder")
	}
}

func TestFollowUpToolRejectsUnsupportedRequestsAndOptOut(t *testing.T) {
	db := agenticTestDB(t)
	db.AutoMigrate(&models.FollowUpTask{}, &models.OptOut{}, &models.Contact{})
	for _, tc := range []struct {
		message, quote string
		due            time.Time
	}{
		{"Apa harga paketnya?", "ingatkan besok sore", time.Now().Add(24 * time.Hour)},
		{"Jangan ingatkan besok sore", "ingatkan besok sore", time.Now().Add(24 * time.Hour)},
		{"ingatkan besok sore", "ingatkan besok sore", time.Now().Add(-time.Hour)},
	} {
		base, _ := makeFollowUpTool(1, ChatOptions{Sender: "6281234567890", LatestCustomerMessage: tc.message, SourceMessageID: "example"}, &agentTurnState{userMessage: tc.message})
		input, _ := json.Marshal(prepareFollowUpInput{DueAt: tc.due.Format(time.RFC3339), Reason: "Follow-up", RequestText: tc.quote})
		out, err := base.(tool.InvokableTool).InvokableRun(context.Background(), string(input))
		if err != nil || !strings.Contains(out, "not_prepared") {
			t.Fatal("unsupported action accepted", out, err)
		}
	}
	db.Create(&models.OptOut{AgentID: 1, Sender: "6281234567890"})
	base, _ := makeFollowUpTool(1, ChatOptions{Sender: "6281234567890", LatestCustomerMessage: "ingatkan besok sore", SourceMessageID: "example"}, &agentTurnState{})
	tomorrow := time.Now().In(FollowUpLocation).AddDate(0, 0, 1)
	due := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 16, 0, 0, 0, FollowUpLocation)
	input, _ := json.Marshal(prepareFollowUpInput{DueAt: due.Format(time.RFC3339), Reason: "Follow-up", RequestText: "ingatkan besok sore"})
	out, _ := base.(tool.InvokableTool).InvokableRun(context.Background(), string(input))
	if !strings.Contains(out, "STOP") {
		t.Fatal(out)
	}
	var count int64
	db.Model(&models.FollowUpTask{}).Count(&count)
	if count != 0 {
		t.Fatal("unrequested reminder persisted")
	}
}

func TestFollowUpRequestedTimeUsesWIBAndRejectsInventedDate(t *testing.T) {
	now := time.Date(2026, 9, 30, 23, 30, 0, 0, FollowUpLocation)
	for _, tc := range []struct {
		message, when string
		valid         bool
	}{
		{"ingatkan besok sore", "2026-10-01T16:00:00+07:00", true},
		{"ingatkan besok sore", "2026-10-02T16:00:00+07:00", false},
		{"ingatkan besok sore", "2026-10-01T09:00:00+07:00", false},
		{"ingatkan besok", "2026-10-01T09:00:00+07:00", false},
		{"ingatkan lusa jam 3 sore", "2026-10-02T15:00:00+07:00", true},
		{"ingatkan 2 jam lagi", "2026-10-01T01:30:00+07:00", true},
	} {
		due, _ := time.Parse(time.RFC3339, tc.when)
		if err := validateRequestedFollowUpTime(tc.message, due, now); (err == nil) != tc.valid {
			t.Fatalf("%s %s: %v", tc.message, tc.when, err)
		}
	}
}

func TestFollowUpReviewContextIsScopedAndInvalidatesOnRevoke(t *testing.T) {
	db := agenticTestDB(t)
	one := models.ChatHistory{AgentID: 1, Sender: "6281234567890", Message: "Tolong hubungi saya", CreatedAt: time.Now()}
	db.Create(&one)
	db.Create(&models.ChatHistory{AgentID: 2, Sender: one.Sender, Message: "SECRET"})
	first, err := ReadFollowUpContext(context.Background(), 1, one.Sender)
	if err != nil || len(first.Messages) != 1 {
		t.Fatal(first, err)
	}
	db.Model(&one).Update("revoked", true)
	next, _ := ReadFollowUpContext(context.Background(), 1, one.Sender)
	if first.Token == next.Token || next.Messages[0].Message != "" {
		t.Fatal("revoked text still approved")
	}
}
