package handlers

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"wa-assistant/backend/models"
	"wa-assistant/backend/services"
)

func TestFollowUpTaskDraftFailureReasons(t *testing.T) {
	// Synthetic private data makes an accidental raw-error response fail without
	// reading credentials or invoking an external AI service.
	privateError := errors.New("provider failed: Bearer fixture-private-token https://private.example/account")
	type failureCase struct {
		name          string
		result        services.ChatResult
		err           error
		cancelRequest bool
		code          string
		messagePart   string
	}
	groups := []struct {
		name  string
		cases []failureCase
	}{
		{"provider", []failureCase{
			{name: "unavailable", result: services.ChatResult{Escalate: true, HandoffReason: "provider_unavailable"}, err: privateError, code: "draft_provider_unavailable", messagePart: "Layanan AI belum tersedia"},
			{name: "credit_exhausted", result: services.ChatResult{Escalate: true, HandoffReason: "provider_credit_exhausted"}, err: privateError, code: "draft_provider_credit_exhausted", messagePart: "saldo layanan AI tidak cukup"},
		}},
		{"timeout", []failureCase{
			{name: "wrapped_deadline", result: services.ChatResult{Escalate: true, HandoffReason: "agent_unavailable"}, err: fmt.Errorf("AI: %w", context.DeadlineExceeded), code: "draft_timeout", messagePart: "melewati batas waktu"},
		}},
		{"cancel", []failureCase{
			{name: "wrapped_cancellation", err: fmt.Errorf("AI: %w", context.Canceled), code: "draft_cancelled", messagePart: "dibatalkan"},
			{name: "request_cancelled", result: services.ChatResult{Escalate: true, HandoffReason: "agent_unavailable"}, err: privateError, cancelRequest: true, code: "draft_cancelled", messagePart: "dibatalkan"},
		}},
		{"verification", []failureCase{
			{name: "knowledge_unavailable", result: services.ChatResult{Escalate: true, HandoffReason: "knowledge_unavailable"}, code: "draft_knowledge_unavailable", messagePart: "Pengetahuan belum bisa diakses"},
			{name: "insufficient_evidence", result: services.ChatResult{Escalate: true, HandoffReason: "insufficient_evidence"}, code: "draft_insufficient_evidence", messagePart: "belum dapat memastikan informasi"},
			{name: "unsupported_numbers", result: services.ChatResult{Escalate: true, HandoffReason: "answer_validation_failed", Trace: services.RetrievalTrace{AnswerCheckIssue: "unsupported_numbers"}}, code: "draft_verification_failed", messagePart: "Angka dalam draf belum didukung"},
			{name: "review_unavailable", result: services.ChatResult{Escalate: true, HandoffReason: "answer_validation_failed", Trace: services.RetrievalTrace{AnswerCheckIssue: "review_unavailable"}}, code: "draft_verification_failed", messagePart: "Pemeriksaan isi draf belum berhasil"},
			{name: "unknown_check_issue", result: services.ChatResult{Escalate: true, HandoffReason: "answer_validation_failed", Trace: services.RetrievalTrace{AnswerCheckIssue: "fixture-private-token"}}, code: "draft_verification_failed", messagePart: "Draf AI belum lolos pemeriksaan"},
			{name: "knowledge_changed", result: services.ChatResult{Escalate: true, HandoffReason: "knowledge_changed"}, code: "draft_knowledge_changed", messagePart: "Pengetahuan berubah"},
			{name: "operational_changed", result: services.ChatResult{Escalate: true, HandoffReason: "operational_changed"}, code: "draft_operational_changed", messagePart: "Data bisnis berubah"},
			{name: "knowledge_conflict", result: services.ChatResult{Escalate: true, HandoffReason: "knowledge_conflict"}, code: "draft_knowledge_conflict", messagePart: "saling bertentangan"},
		}},
		{"escalation", []failureCase{
			{name: "customer_request", result: services.ChatResult{Escalate: true, HandoffReason: "customer_request"}, code: "draft_manual_review", messagePart: "perlu ditinjau langsung oleh CS"},
			{name: "sensitive_issue", result: services.ChatResult{Escalate: true, HandoffReason: "sensitive_issue"}, code: "draft_manual_review", messagePart: "perlu ditinjau langsung oleh CS"},
			{name: "unknown_reason", result: services.ChatResult{Escalate: true, HandoffReason: "fixture-private-token"}, code: "draft_unavailable", messagePart: "Penyebabnya belum dapat dipastikan"},
		}},
		{"empty", []failureCase{
			{name: "blank_reply", result: services.ChatResult{Reply: " \n "}, code: "draft_empty", messagePart: "AI tidak menghasilkan teks draf"},
		}},
		{"directive", []failureCase{
			{name: "internal_instruction", result: services.ChatResult{Reply: "[[START_FORM:fixture-private-token]]"}, code: "draft_invalid_content", messagePart: "Draf berisi instruksi internal"},
		}},
		{"unknown_failure", []failureCase{
			{name: "raw_error", err: privateError, code: "draft_unavailable", messagePart: "Penyebabnya belum dapat dipastikan"},
			{name: "error_with_prior_check_issue", err: privateError, result: services.ChatResult{Escalate: true, HandoffReason: "agent_unavailable", Trace: services.RetrievalTrace{AnswerCheckIssue: "unsupported_numbers"}}, code: "draft_unavailable", messagePart: "Penyebabnya belum dapat dipastikan"},
		}},
	}
	for _, group := range groups {
		t.Run(group.name, func(t *testing.T) {
			for _, tc := range group.cases {
				t.Run(tc.name, func(t *testing.T) {
					db, user, router := followUpTaskTestSetup(t)
					task := models.FollowUpTask{AgentID: 1, Number: "6281234567890", Reason: "Melanjutkan konsultasi", Draft: "Draf manual milik CS", DueAt: time.Now().Add(time.Hour), Status: "pending", Source: "manual", SourceKey: "draft-failure", Version: 1}
					if err := db.Create(&task).Error; err != nil {
						t.Fatal(err)
					}
					requestContext, cancel := context.WithCancel(context.Background())
					defer cancel()
					oldChat := generateFollowUpTaskDraftChat
					t.Cleanup(func() { generateFollowUpTaskDraftChat = oldChat })
					calls := 0
					generateFollowUpTaskDraftChat = func(agentID uint, prompt, tone, message string, history []models.ChatHistory, options ...services.ChatOptions) (services.ChatResult, error) {
						calls++
						if agentID != task.AgentID || len(options) != 1 || !options[0].DryRun || !options[0].FollowUpDraft {
							t.Fatal("draft lost its scoped, read-only AI options")
						}
						if tc.cancelRequest {
							cancel()
						}
						return tc.result, tc.err
					}
					path := fmt.Sprintf("/api/agents/1/follow-up-tasks/%d/draft", task.ID)
					request := httptest.NewRequest(http.MethodPost, path, nil).WithContext(requestContext)
					request.Header.Set("Authorization", "Bearer "+issueToken(user))
					response := httptest.NewRecorder()
					router.ServeHTTP(response, request)
					if calls != 1 || response.Code != http.StatusUnprocessableEntity {
						t.Fatalf("calls=%d status=%d body=%s", calls, response.Code, response.Body.String())
					}
					var body struct {
						Error string `json:"error"`
						Code  string `json:"code"`
					}
					if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
						t.Fatal(err)
					}
					if body.Code != tc.code || !strings.Contains(body.Error, tc.messagePart) || !strings.Contains(body.Error, "tulis pesan sendiri") {
						t.Errorf("cause not explained: code=%q error=%q; want %q containing %q and manual option", body.Code, body.Error, tc.code, tc.messagePart)
					}
					for _, forbidden := range []string{"fixture-private-token", "private.example", "Bearer", "[[", "unsupported_numbers"} {
						if strings.Contains(response.Body.String(), forbidden) {
							t.Errorf("private or internal detail exposed: %q", forbidden)
						}
					}
					var current models.FollowUpTask
					if err := db.First(&current, task.ID).Error; err != nil || current.Status != task.Status || current.Version != task.Version || current.Draft != task.Draft {
						t.Fatalf("failed generation changed task: %+v err=%v", current, err)
					}
				})
			}
		})
	}
}
