package handlers

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

	"gorm.io/gorm"
	"wa-assistant/backend/models"
	"wa-assistant/backend/services"
)

func serviceTestDB(t *testing.T) (*gorm.DB, models.AIForm, models.AIFormSession) {
	db := workflowTestDB(t)
	if err := db.AutoMigrate(&models.AIForm{}, &models.AIFormSession{}, &models.AIFormSubmission{}); err != nil {
		t.Fatal(err)
	}
	steps := []aiFormStepConfig{{Key: "name", Label: "Boleh dibantu namanya?", Type: "text", Required: true}, {Key: "quantity", Label: "Untuk berapa orang?", Type: "number", Required: true}, {Key: "schedule", Label: "Kapan jadwalnya?", Type: "text", Required: true}, {Key: "note", Label: "Catatan tambahan?", Type: "text"}}
	raw, _ := json.Marshal(steps)
	form := models.AIForm{AgentID: 1, TenantID: 1, Name: "Booking konsultasi", Enabled: true, Handoff: true, StepsJSON: string(raw)}
	if err := db.Create(&form).Error; err != nil {
		t.Fatal(err)
	}
	session := models.AIFormSession{AgentID: 1, TenantID: 1, FormID: form.ID, Sender: "fixture-customer", Status: "collecting", DataJSON: "{}", ExpiresAt: time.Now().Add(time.Hour)}
	if err := db.Create(&session).Error; err != nil {
		t.Fatal(err)
	}
	return db, form, session
}

func TestServiceConversationCollectsMultipleFieldsAndOnlyAsksMissing(t *testing.T) {
	db, form, session := serviceTestDB(t)
	result := applyAIFormExtraction(session, form, "nama Rina, besok jam 10", services.ServiceExtraction{Fields: map[string]services.ServiceFieldValue{"name": {Value: "Rina"}, "schedule": {Value: "besok jam 10"}}})
	if result.reply != "Untuk berapa orang?" {
		t.Fatalf("repeated known data: %s", result.reply)
	}
	db.First(&session, session.ID)
	result = applyAIFormExtraction(session, form, "3", services.ServiceExtraction{Fields: map[string]services.ServiceFieldValue{"quantity": {Value: "3"}}})
	db.First(&session, session.ID)
	if session.Status != "confirming" || !strings.Contains(result.reply, "Rina") || !strings.Contains(result.reply, "konfirmasi") || strings.Contains(result.reply, "Catatan tambahan?") {
		t.Fatalf("unexpected confirmation %+v %+v", session, result)
	}
	var count int64
	db.Model(&models.AIFormSubmission{}).Count(&count)
	if count != 0 {
		t.Fatal("submitted before customer confirmation")
	}
	result = handleAIFormAnswer(session, "konfirmasi", "")
	if !result.handoff || !strings.Contains(result.handoffSummary, "Rina") {
		t.Fatalf("handoff lost collected data: %+v", result)
	}
	db.Model(&models.AIFormSubmission{}).Count(&count)
	if count != 1 {
		t.Fatal(count)
	}
	confirmAIForm(session, form, parseAIFormSteps(form))
	db.Model(&models.AIFormSubmission{}).Count(&count)
	if count != 1 {
		t.Fatal("duplicate submission")
	}
}

func TestServiceConversationSideQuestionPreservesDataAndValidation(t *testing.T) {
	db, form, session := serviceTestDB(t)
	db.Model(&session).Update("data_json", `{"name":"Rina"}`)
	db.First(&session, session.ID)
	result := applyAIFormExtraction(session, form, "Biayanya berapa?", services.ServiceExtraction{Question: "Biayanya berapa?", Fields: map[string]services.ServiceFieldValue{"quantity": {Value: "tidak tahu"}}})
	if result.handled || result.context == "" || !strings.Contains(result.handoffSummary, "Rina") {
		t.Fatalf("side question swallowed: %+v", result)
	}
	db.First(&session, session.ID)
	var data map[string]string
	json.Unmarshal([]byte(session.DataJSON), &data)
	if data["name"] != "Rina" || data["quantity"] != "" {
		t.Fatalf("invalid data: %v", data)
	}
}

func TestServiceConfirmationRejectsIncompleteOrForeignActions(t *testing.T) {
	db, form, session := serviceTestDB(t)
	result := handleAIFormAnswer(session, "", "ai_form:999:confirm")
	if !strings.Contains(result.reply, "sesi sebelumnya") {
		t.Fatal(result.reply)
	}
	result = confirmAIForm(session, form, parseAIFormSteps(form))
	if !strings.Contains(result.reply, "namanya") {
		t.Fatal(result.reply)
	}
	var count int64
	db.Model(&models.AIFormSubmission{}).Count(&count)
	if count != 0 {
		t.Fatal("incomplete data submitted")
	}
}

func TestServiceProviderFailurePreservesDraftForHandoff(t *testing.T) {
	db, form, session := serviceTestDB(t)
	db.Model(&session).Update("data_json", `{"name":"Rina"}`)
	db.First(&session, session.ID)
	old := extractAIFormFields
	t.Cleanup(func() { extractAIFormFields = old })
	extractAIFormFields = func(context.Context, uint, string, []services.ServiceField, map[string]string, string, string) (services.ServiceExtraction, error) {
		return services.ServiceExtraction{}, errors.New("fixture unavailable")
	}
	result := collectAIFormConversation(session, form, "besok jam 10", false)
	if !result.handoff || !strings.Contains(result.handoffSummary, "Rina") {
		t.Fatalf("missing handoff: %+v", result)
	}
}

func TestServiceSimulatorConfirmsWithoutCreatingSubmission(t *testing.T) {
	db, form, _ := serviceTestDB(t)
	state := &aiFormSimulation{FormID: form.ID, Data: map[string]string{"name": "Rina", "quantity": "3", "schedule": "besok jam 10"}, Status: "confirming"}
	result, next := simulateAIForm(context.Background(), models.Agent{ID: 1}, state, "konfirmasi", false)
	if !result.handled || !result.handoff || next != nil || !strings.Contains(result.reply, "Simulasi") {
		t.Fatalf("%+v %+v", result, next)
	}
	var count int64
	db.Model(&models.AIFormSubmission{}).Count(&count)
	if count != 0 {
		t.Fatal("simulator created real submission")
	}
}

func TestServiceHandoffIncludesCollectedDraft(t *testing.T) {
	db, _, session := serviceTestDB(t)
	db.Model(&session).Update("data_json", `{"name":"Rina","schedule":"besok jam 10"}`)
	if _, err := ensureHandoffDetails(1, session.Sender, "Minta bicara sama CS", "customer_request"); err != nil {
		t.Fatal(err)
	}
	var handoff models.Handoff
	db.First(&handoff)
	if !strings.Contains(handoff.Summary, "Rina") || !strings.Contains(handoff.Summary, "besok jam 10") {
		t.Fatal("draft missing from handoff")
	}
}

func TestServiceSimulatorCannotAccessAnotherAgentsForm(t *testing.T) {
	_, form, _ := serviceTestDB(t)
	result, next := simulateAIForm(context.Background(), models.Agent{ID: 999}, &aiFormSimulation{FormID: form.ID}, "konfirmasi", false)
	if !result.handled || next != nil || !strings.Contains(result.reply, "tidak tersedia") {
		t.Fatalf("foreign form exposed: %+v", result)
	}
}
