package rules

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

	"github.com/ultisuite/ulti-backend/internal/mail/webhooks"
)

func testMessage() *Message {
	return &Message{
		ID:             "msg-1",
		From:           "Alice <alice@example.com>",
		To:             []string{"bob@example.com", "carol@example.com"},
		Subject:        "Invoice Q1",
		BodyText:       "Please review the attached invoice.",
		HasAttachments: true,
		Labels:         []string{"work", "finance"},
	}
}

func TestMatchCondition_fieldsAndOperators(t *testing.T) {
	msg := testMessage()

	tests := []struct {
		name  string
		cond  Condition
		match bool
	}{
		{"from contains", Condition{Field: "from", Operator: "contains", Value: "alice@"}, true},
		{"from equals case insensitive", Condition{Field: "from", Operator: "equals", Value: "alice <alice@example.com>"}, true},
		{"to contains", Condition{Field: "to", Operator: "contains", Value: "carol@"}, true},
		{"subject starts_with", Condition{Field: "subject", Operator: "starts_with", Value: "invoice"}, true},
		{"body ends_with", Condition{Field: "body", Operator: "ends_with", Value: "invoice."}, true},
		{"has_attachment true", Condition{Field: "has_attachment", Operator: "equals", Value: "true"}, true},
		{"has_attachment false", Condition{Field: "has_attachment", Operator: "equals", Value: "false"}, false},
		{"not_contains", Condition{Field: "subject", Operator: "not_contains", Value: "spam"}, true},
		{"unknown field", Condition{Field: "unknown", Operator: "contains", Value: "x"}, false},
		{"unknown operator", Condition{Field: "subject", Operator: "unknown_op", Value: "Invoice"}, false},
		{"regex match", Condition{Field: "subject", Operator: "regex", Value: `(?i)invoice`}, true},
		{"regex no match", Condition{Field: "subject", Operator: "regex", Value: `^Spam`}, false},
		{"not_regex", Condition{Field: "subject", Operator: "not_regex", Value: `^Spam`}, true},
		{"label has", Condition{Field: "label", Operator: "has", Value: "work"}, true},
		{"label not_has", Condition{Field: "label", Operator: "not_has", Value: "spam"}, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := matchCondition(tt.cond, msg, nil); got != tt.match {
				t.Fatalf("matchCondition() = %v, want %v", got, tt.match)
			}
		})
	}
}

func TestMatchesAll(t *testing.T) {
	msg := testMessage()

	t.Run("all match", func(t *testing.T) {
		conditions := []Condition{
			{Field: "from", Operator: "contains", Value: "alice"},
			{Field: "subject", Operator: "contains", Value: "invoice"},
		}
		if !matchesAll(conditions, msg) {
			t.Fatal("matchesAll() = false, want true")
		}
	})

	t.Run("one fails", func(t *testing.T) {
		conditions := []Condition{
			{Field: "from", Operator: "contains", Value: "alice"},
			{Field: "subject", Operator: "equals", Value: "other"},
		}
		if matchesAll(conditions, msg) {
			t.Fatal("matchesAll() = true, want false")
		}
	})

	t.Run("empty conditions", func(t *testing.T) {
		if !matchesAll(nil, msg) {
			t.Fatal("matchesAll(nil) = false, want true")
		}
	})
}

func TestExecuteAction_unknownType(t *testing.T) {
	e := &Engine{}
	err := e.executeAction(context.Background(), Action{Type: "unknown_action", Value: "x@example.com"}, &Message{ID: "msg-1"}, nil)
	if err == nil {
		t.Fatal("executeAction() error = nil, want unknown action type error")
	}
	if !strings.Contains(err.Error(), "unknown action type: unknown_action") {
		t.Fatalf("executeAction() error = %v, want unknown action type", err)
	}
}

func TestActionResultJSON(t *testing.T) {
	results := []ActionResult{
		{Type: "label", Value: "important", OK: true},
		{Type: "webhook", Value: "tpl-1", OK: false, Error: "request failed: timeout"},
	}

	data, err := json.Marshal(results)
	if err != nil {
		t.Fatalf("json.Marshal() error = %v", err)
	}

	var decoded []ActionResult
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if len(decoded) != 2 {
		t.Fatalf("len(decoded) = %d, want 2", len(decoded))
	}
	if decoded[0].Type != "label" || !decoded[0].OK || decoded[0].Error != "" {
		t.Fatalf("decoded[0] = %+v, want label ok=true empty error", decoded[0])
	}
	if decoded[1].Type != "webhook" || decoded[1].OK || decoded[1].Error == "" {
		t.Fatalf("decoded[1] = %+v, want webhook ok=false with error", decoded[1])
	}
}

func TestActionResultFrom(t *testing.T) {
	ok := actionResultFrom(Action{Type: "archive", Value: ""}, nil)
	if !ok.OK || ok.Error != "" {
		t.Fatalf("actionResultFrom(nil err) = %+v, want ok=true", ok)
	}

	fail := actionResultFrom(Action{Type: "webhook", Value: "tpl-1"}, context.DeadlineExceeded)
	if fail.OK || fail.Error == "" {
		t.Fatalf("actionResultFrom(err) = %+v, want ok=false with error", fail)
	}
}

func TestAggregateActionErrors(t *testing.T) {
	got := aggregateActionErrors([]ActionResult{
		{Type: "label", OK: true},
		{Type: "webhook", OK: false, Error: "timeout"},
		{Type: "move", OK: false, Error: "folder missing"},
	})
	want := "webhook: timeout; move: folder missing"
	if got != want {
		t.Fatalf("aggregateActionErrors() = %q, want %q", got, want)
	}
	if aggregateActionErrors(nil) != "" {
		t.Fatal("aggregateActionErrors(nil) want empty string")
	}
}

func TestParseFromAddress(t *testing.T) {
	tests := []struct {
		from, wantName, wantEmail string
	}{
		{"Alice <alice@example.com>", "Alice", "alice@example.com"},
		{`"Bob Smith" <bob@example.com>`, "Bob Smith", "bob@example.com"},
		{"carol@example.com", "", "carol@example.com"},
		{"", "", ""},
	}
	for _, tt := range tests {
		name, email := parseFromAddress(tt.from)
		if name != tt.wantName || email != tt.wantEmail {
			t.Fatalf("parseFromAddress(%q) = (%q, %q), want (%q, %q)", tt.from, name, email, tt.wantName, tt.wantEmail)
		}
	}
}

func TestMessageToWebhookContext(t *testing.T) {
	msg := testMessage()
	ctx := messageToWebhookContext(msg, nil)
	if ctx.SenderName != "Alice" || ctx.SenderEmail != "alice@example.com" {
		t.Fatalf("sender = (%q, %q), want (Alice, alice@example.com)", ctx.SenderName, ctx.SenderEmail)
	}
	if ctx.Subject != msg.Subject || ctx.BodyText != msg.BodyText {
		t.Fatal("subject/body not copied")
	}
	if ctx.Recipients != "bob@example.com, carol@example.com" {
		t.Fatalf("Recipients = %q", ctx.Recipients)
	}
	if !ctx.HasAttachment || ctx.MessageID != msg.ID {
		t.Fatalf("HasAttachment/MessageID = (%v, %q)", ctx.HasAttachment, ctx.MessageID)
	}
}

type mockWebhookExecutor struct {
	templateID string
	msgCtx     *webhooks.MessageContext
	err        error
}

func (m *mockWebhookExecutor) Execute(_ context.Context, templateID string, msgCtx *webhooks.MessageContext) error {
	m.templateID = templateID
	m.msgCtx = msgCtx
	return m.err
}

func TestExecuteAction_webhook(t *testing.T) {
	msg := testMessage()
	mock := &mockWebhookExecutor{}
	e := &Engine{webhookExec: mock}

	if err := e.executeAction(context.Background(), Action{Type: "webhook", Value: "tpl-abc"}, msg, nil); err != nil {
		t.Fatalf("executeAction() error = %v", err)
	}
	if mock.templateID != "tpl-abc" {
		t.Fatalf("templateID = %q, want tpl-abc", mock.templateID)
	}
	if mock.msgCtx.MessageID != msg.ID {
		t.Fatalf("msgCtx.MessageID = %q, want %q", mock.msgCtx.MessageID, msg.ID)
	}

	e.webhookExec = nil
	err := e.executeAction(context.Background(), Action{Type: "webhook", Value: "tpl-abc"}, msg, nil)
	if err == nil || !strings.Contains(err.Error(), "webhook executor not configured") {
		t.Fatalf("executeAction() without executor = %v, want not configured error", err)
	}
}
