package graphs

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

	"agent-desk/internal/ai/runtime/tooling"
	"agent-desk/internal/models"
	"agent-desk/internal/pkg/enums"
	"agent-desk/internal/services"

	"github.com/glebarez/sqlite"
	"github.com/mlogclub/simple/sqls"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

func TestHandoffGraphOffHoursSendsNoticeWithoutConfirmation(t *testing.T) {
	db := setupHandoffGraphTestDB(t)
	aiAgent := createHandoffGraphAIAgent(t, db, "1")
	conversation := createHandoffGraphConversation(t, db, aiAgent.ID)

	reply, err := NewHandoffGraph(conversation, aiAgent).Run(context.Background(), `{"reason":"用户要求转人工"}`)
	if err != nil {
		t.Fatalf("Run() error = %v", err)
	}
	var result tooling.ToolResult
	if err := json.Unmarshal([]byte(reply), &result); err != nil {
		t.Fatalf("expected graph tool result JSON, got %q: %v", reply, err)
	}
	if !result.Handled || !result.Terminal || result.ShouldRetry {
		t.Fatalf("unexpected graph result flags: %+v", result)
	}
	if !result.ReplySent {
		t.Fatalf("expected graph result to mark replySent, got %+v", result)
	}
	if result.Action != "off_hours_handoff" || result.ReplyText != services.HandoffOffHoursMessage {
		t.Fatalf("unexpected off-hours graph result: %+v", result)
	}

	message := services.MessageService.FindOne(sqls.NewCnd().Eq("conversation_id", conversation.ID).Desc("id"))
	if message == nil {
		t.Fatalf("expected off-hours notice message")
	}
	if message.Content != services.HandoffOffHoursMessage {
		t.Fatalf("expected off-hours notice, got %q", message.Content)
	}

	current := services.ConversationService.Get(conversation.ID)
	if current == nil {
		t.Fatalf("expected conversation")
	}
	if current.Status != enums.IMConversationStatusAIServing {
		t.Fatalf("expected conversation to stay ai-serving, got %d", current.Status)
	}
	if current.HandoffAt != nil {
		t.Fatalf("expected handoff_at to remain nil, got %v", current.HandoffAt)
	}
}

func TestHandoffGraphWithActiveScheduleStillRequestsConfirmation(t *testing.T) {
	db := setupHandoffGraphTestDB(t)
	aiAgent := createHandoffGraphAIAgent(t, db, "1")
	createHandoffGraphTeam(t, db, 1)
	createHandoffGraphActiveSchedule(t, db, 1)
	conversation := createHandoffGraphConversation(t, db, aiAgent.ID)

	reply, err := NewHandoffGraph(conversation, aiAgent).Run(context.Background(), `{"reason":"用户要求转人工"}`)
	if err == nil {
		t.Fatalf("expected confirmation interrupt")
	}
	if !strings.Contains(err.Error(), InterruptTypeHandoffConfirmation) {
		t.Fatalf("expected handoff confirmation interrupt, got %v", err)
	}
	if reply != "" {
		t.Fatalf("expected no reply before confirmation, got %q", reply)
	}

	var count int64
	if err := db.Model(&models.Message{}).Where("conversation_id = ?", conversation.ID).Count(&count).Error; err != nil {
		t.Fatalf("count messages error = %v", err)
	}
	if count != 0 {
		t.Fatalf("expected no notice before confirmation, got %d messages", count)
	}
}

func setupHandoffGraphTestDB(t *testing.T) *gorm.DB {
	t.Helper()

	dbName := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
	db, err := gorm.Open(sqlite.Open("file:"+dbName+"?mode=memory&cache=shared"), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			TablePrefix:   "t_",
			SingularTable: true,
		},
	})
	if err != nil {
		t.Fatalf("open sqlite error = %v", err)
	}
	t.Cleanup(func() {
		sqlDB, err := db.DB()
		if err == nil {
			_ = sqlDB.Close()
		}
	})
	if err := db.AutoMigrate(
		&models.AIAgent{},
		&models.AgentTeam{},
		&models.AgentTeamSchedule{},
		&models.Conversation{},
		&models.ConversationEventLog{},
		&models.ConversationReadState{},
		&models.Message{},
		&models.ChannelMessageOutbox{},
	); err != nil {
		t.Fatalf("auto migrate error = %v", err)
	}
	sqls.SetDB(db)
	return db
}

func createHandoffGraphAIAgent(t *testing.T, db *gorm.DB, teamIDs string) models.AIAgent {
	t.Helper()

	item := models.AIAgent{
		Name:        "测试AI",
		ServiceMode: enums.IMConversationServiceModeAIFirst,
		TeamIDs:     teamIDs,
		Status:      enums.StatusOk,
	}
	if err := db.Create(&item).Error; err != nil {
		t.Fatalf("create ai agent error = %v", err)
	}
	return item
}

func createHandoffGraphTeam(t *testing.T, db *gorm.DB, id int64) {
	t.Helper()

	if err := db.Create(&models.AgentTeam{ID: id, Name: "售后支持组", Status: enums.StatusOk}).Error; err != nil {
		t.Fatalf("create team error = %v", err)
	}
}

func createHandoffGraphActiveSchedule(t *testing.T, db *gorm.DB, teamID int64) {
	t.Helper()

	now := time.Now()
	if err := db.Create(&models.AgentTeamSchedule{
		TeamID:  teamID,
		StartAt: now.Add(-time.Hour),
		EndAt:   now.Add(time.Hour),
		Status:  enums.StatusOk,
	}).Error; err != nil {
		t.Fatalf("create schedule error = %v", err)
	}
}

func createHandoffGraphConversation(t *testing.T, db *gorm.DB, aiAgentID int64) models.Conversation {
	t.Helper()

	now := time.Now()
	item := models.Conversation{
		AIAgentID:     aiAgentID,
		ChannelID:     1,
		CustomerID:    1,
		CustomerName:  "测试访客",
		Status:        enums.IMConversationStatusAIServing,
		ServiceMode:   enums.IMConversationServiceModeAIFirst,
		LastMessageAt: now,
		LastActiveAt:  now,
	}
	if err := db.Create(&item).Error; err != nil {
		t.Fatalf("create conversation error = %v", err)
	}
	return item
}
