package services

import (
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	sqlmysql "github.com/go-sql-driver/mysql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"wa-assistant/backend/config"
	"wa-assistant/backend/database"
	"wa-assistant/backend/models"
)

// Opt-in acceptance against a read-only snapshot of assistant 1. All agent
// execution uses SQLite; no customer history, WA session, or real writes occur.
func liveDeepSeekAssistantSnapshot(t *testing.T) (aiPreset, models.Agent, []models.Knowledge, []models.Product) {
	t.Helper()
	host := config.Env("DB_HOST", "localhost")
	if host != "localhost" && host != "127.0.0.1" && host != "::1" {
		t.Fatal("Snapshot tests are restricted to the local database")
	}
	cfg := sqlmysql.NewConfig()
	cfg.User, cfg.Passwd = config.Env("DB_USER", "root"), config.Env("DB_PASS", "")
	cfg.Net, cfg.Addr = "tcp", net.JoinHostPort(host, config.Env("DB_PORT", "3306"))
	cfg.DBName, cfg.ParseTime, cfg.Timeout = config.Env("DB_NAME", "wa_assistant"), true, 3*time.Second
	source, err := gorm.Open(mysql.Open(cfg.FormatDSN()), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
	if err != nil {
		t.Fatal("Local AI snapshot could not be opened")
	}
	sqlDB, _ := source.DB()
	defer sqlDB.Close()
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	tx := source.WithContext(ctx).Begin(&sql.TxOptions{ReadOnly: true})
	if tx.Error != nil {
		t.Fatal("Read-only snapshot unavailable")
	}
	defer tx.Rollback()
	original := database.DB
	database.DB = tx
	defer func() { database.DB = original }()
	p := deepSeekPreset()
	if p.APIKey == "" {
		t.Fatal("DeepSeek key is not configured")
	}
	var agent models.Agent
	var knowledge []models.Knowledge
	var products []models.Product
	if tx.Select("id", "tenant_id", "name", "system_prompt", "tone", "response_length", "agentic_enabled").First(&agent, 1).Error != nil ||
		tx.Where("agent_id = ? AND active = ? AND review_status = ?", 1, true, "published").Find(&knowledge).Error != nil ||
		tx.Where("agent_id = ?", 1).Find(&products).Error != nil {
		t.Fatal("Assistant knowledge snapshot could not be read")
	}
	t.Setenv("OPENROUTER_API_KEY", apiKeyFromDB("api_key", "OPENROUTER_API_KEY"))
	t.Setenv("CHATLOOP_LIVE_EMBEDDING_MODEL", apiConfigFromDB("embedding_model", "", defaultEmbeddingModel))
	t.Setenv("DEEPSEEK_API_KEY", p.APIKey)
	t.Setenv("DEEPSEEK_MODEL", p.Model)
	return p, agent, knowledge, products
}

func TestLiveDeepSeekAnswerControl(t *testing.T) {
	if os.Getenv("CHATLOOP_RUN_LIVE_DEEPSEEK") != "1" {
		t.Skip("opt-in paid DeepSeek acceptance")
	}
	p, agent, knowledge, products := liveDeepSeekAssistantSnapshot(t)
	transport := http.DefaultTransport
	http.DefaultTransport = providerRoundTrip(func(req *http.Request) (*http.Response, error) {
		response, err := transport.RoundTrip(req)
		if err == nil && response.StatusCode >= 400 && req.URL.Host == "api.deepseek.com" {
			body, readErr := io.ReadAll(io.LimitReader(response.Body, 16384))
			response.Body.Close()
			response.Body = io.NopCloser(bytes.NewReader(body))
			var failure struct {
				Error struct {
					Message string `json:"message"`
				} `json:"error"`
			}
			if readErr == nil && json.Unmarshal(body, &failure) == nil {
				t.Logf("provider rejected HTTP %d: %s", response.StatusCode, clipContextText(strings.ReplaceAll(failure.Error.Message, p.APIKey, "[redacted]"), 500))
			}
		}
		return response, err
	})
	t.Cleanup(func() { http.DefaultTransport = transport })
	db := agenticTestDB(t)
	if err := db.AutoMigrate(&models.AppSetting{}, &models.Agent{}, &models.Product{}); err != nil {
		t.Fatal(err)
	}
	if err := db.Create(&agent).Error; err != nil {
		t.Fatal(err)
	}
	if len(knowledge) > 0 {
		if err := db.Create(&knowledge).Error; err != nil {
			t.Fatal(err)
		}
	}
	if len(products) > 0 {
		if err := db.Create(&products).Error; err != nil {
			t.Fatal(err)
		}
	}
	if err := db.Create(&models.AppSetting{Key: "ai_provider", Value: "deepseek"}).Error; err != nil {
		t.Fatal(err)
	}
	InvalidateKB(1)
	logDir := os.Getenv("CHATLOOP_AI_TEST_LOG_DIR")
	if logDir == "" {
		logDir = t.TempDir()
	}
	store := NewSimulationLogStore(logDir)
	t.Logf("model=%s published_sources=%d products=%d", p.Model, len(knowledge), len(products))
	for _, tc := range []struct {
		name, question, mode string
		history              []models.ChatHistory
		handoff, sources     bool
	}{
		{name: "greeting", question: "hallo", mode: "short"},
		{name: "offer_after_greeting", question: "anda jual apa", mode: "short", sources: true, history: []models.ChatHistory{{Message: "hallo", Reply: "Halo kak! 👋 Ada yang bisa saya bantu terkait produk kursus atau source code NgertiKode.id?"}}},
		{name: "balanced_features", question: "Apa fitur source code Wa Blast plus AI Assistant dan apakah dijamin anti-banned?", mode: "balanced", sources: true},
		{name: "detailed_features", question: "Apa fitur source code Wa Blast plus AI Assistant dan apakah dijamin anti-banned?", mode: "detailed", sources: true},
		{name: "missing_fact", question: "Ada kursus merakit pesawat terbang? Kalau informasinya tidak tersedia, teruskan ke CS ya.", mode: "short", handoff: true},
		{name: "customer_request", question: "Saya mau bicara dengan CS manusia.", mode: "short", handoff: true},
	} {
		t.Run(tc.name, func(t *testing.T) {
			a := agent
			a.ResponseLength = tc.mode
			id := NewSimulationID()
			r, err := store.Start(a.ID, id, NewSimulationID(), map[string]any{"question": tc.question, "history": tc.history, "response_mode": tc.mode})
			if err != nil {
				t.Fatal(err)
			}
			result, callErr := chatAgentic(a, a.SystemPrompt, a.Tone, tc.question, tc.history, ChatOptions{Context: r.Context(context.Background()), DryRun: true})
			status := "completed"
			if result.Escalate {
				status = "handoff"
			}
			if callErr != nil {
				status = "error"
			}
			r.Finish(status, map[string]any{"reply": result.Reply, "provider": result.Trace.Provider, "model": result.Model, "handoff_reason": result.HandoffReason, "answer_check_issue": result.Trace.AnswerCheckIssue, "answer_sources": result.Trace.AnswerSources, "tool_names": result.Trace.ToolNames, "response_validated": result.Trace.ResponseValidated})
			t.Logf("turn=%s status=%s mode=%s tools=%s sources=%s chars=%d/%d issue=%s reply=%s", id, status, result.Trace.ResponseMode, result.Trace.ToolNames, result.Trace.KnowledgeIDs, len([]rune(result.Reply)), result.Trace.ResponseMaxChars, result.Trace.AnswerCheckIssue, result.Reply)
			if callErr != nil {
				t.Fatalf("agent failed: %v", callErr)
			}
			if result.Escalate != tc.handoff {
				t.Fatalf("handoff=%t reason=%s", result.Escalate, result.HandoffReason)
			}
			if !tc.handoff && (!result.Trace.ResponseValidated || !strings.Contains(result.Trace.ToolNames, "check_answer")) {
				t.Fatal("answer was not checked")
			}
			if tc.sources && (len(result.Trace.AnswerSources) == 0 || !strings.Contains(result.Trace.ToolNames, "search_knowledge")) {
				t.Fatal("business question did not use published knowledge")
			}
			if tc.name == "offer_after_greeting" {
				var selectedEvidence strings.Builder
				for _, source := range result.Trace.AnswerSources {
					selectedEvidence.WriteString(source.Question + " " + source.Excerpt)
				}
				if strings.Contains(strings.ToLower(result.Reply), "pos") && !strings.Contains(strings.ToLower(selectedEvidence.String()), "pos") {
					t.Error("product example from persona was accepted without selected source evidence")
				}
			}
			if !tc.handoff && result.Trace.ResponseMode != tc.mode {
				t.Fatal("saved response mode was not applied")
			}
		})
	}
}
