package cmd

import (
	"fmt"
	"strings"
	"testing"

	"github.com/steveyegge/gastown/internal/config"
)

func TestMatchWorkerPattern(t *testing.T) {
	tests := []struct {
		name    string
		pattern string
		caller  string
		want    bool
	}{
		// Exact matches
		{
			name:    "exact match",
			pattern: "gastown/polecats/capable",
			caller:  "gastown/polecats/capable",
			want:    true,
		},
		{
			name:    "exact match with different name",
			pattern: "gastown/polecats/toast",
			caller:  "gastown/polecats/capable",
			want:    false,
		},

		// Wildcard at end
		{
			name:    "wildcard matches polecat",
			pattern: "gastown/polecats/*",
			caller:  "gastown/polecats/capable",
			want:    true,
		},
		{
			name:    "wildcard matches different polecat",
			pattern: "gastown/polecats/*",
			caller:  "gastown/polecats/toast",
			want:    true,
		},
		{
			name:    "wildcard doesn't match wrong rig",
			pattern: "gastown/polecats/*",
			caller:  "beads/polecats/capable",
			want:    false,
		},
		{
			name:    "wildcard doesn't match nested path",
			pattern: "gastown/polecats/*",
			caller:  "gastown/polecats/sub/capable",
			want:    false,
		},

		// Crew patterns
		{
			name:    "crew wildcard matches",
			pattern: "gastown/crew/*",
			caller:  "gastown/crew/max",
			want:    true,
		},
		{
			name:    "crew wildcard doesn't match polecats",
			pattern: "gastown/crew/*",
			caller:  "gastown/polecats/capable",
			want:    false,
		},

		// Different rigs
		{
			name:    "different rig wildcard",
			pattern: "beads/polecats/*",
			caller:  "beads/polecats/capable",
			want:    true,
		},

		// Edge cases
		{
			name:    "empty pattern",
			pattern: "",
			caller:  "gastown/polecats/capable",
			want:    false,
		},
		{
			name:    "empty caller",
			pattern: "gastown/polecats/*",
			caller:  "",
			want:    false,
		},
		{
			name:    "pattern is just wildcard",
			pattern: "*",
			caller:  "anything",
			want:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := matchWorkerPattern(tt.pattern, tt.caller)
			if got != tt.want {
				t.Errorf("matchWorkerPattern(%q, %q) = %v, want %v",
					tt.pattern, tt.caller, got, tt.want)
			}
		})
	}
}

func TestIsEligibleWorker(t *testing.T) {
	tests := []struct {
		name     string
		caller   string
		patterns []string
		want     bool
	}{
		{
			name:     "matches first pattern",
			caller:   "gastown/polecats/capable",
			patterns: []string{"gastown/polecats/*", "gastown/crew/*"},
			want:     true,
		},
		{
			name:     "matches second pattern",
			caller:   "gastown/crew/max",
			patterns: []string{"gastown/polecats/*", "gastown/crew/*"},
			want:     true,
		},
		{
			name:     "matches none",
			caller:   "beads/polecats/capable",
			patterns: []string{"gastown/polecats/*", "gastown/crew/*"},
			want:     false,
		},
		{
			name:     "empty patterns list",
			caller:   "gastown/polecats/capable",
			patterns: []string{},
			want:     false,
		},
		{
			name:     "nil patterns",
			caller:   "gastown/polecats/capable",
			patterns: nil,
			want:     false,
		},
		{
			name:     "exact match in list",
			caller:   "mayor/",
			patterns: []string{"mayor/", "gastown/witness"},
			want:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isEligibleWorker(tt.caller, tt.patterns)
			if got != tt.want {
				t.Errorf("isEligibleWorker(%q, %v) = %v, want %v",
					tt.caller, tt.patterns, got, tt.want)
			}
		})
	}
}

// TestMailReleaseValidation tests the validation logic for the release command.
// This tests that release correctly identifies:
// - Messages not claimed (still in queue)
// - Messages claimed by a different worker
// - Messages without queue labels (non-queue messages)
func TestMailReleaseValidation(t *testing.T) {
	tests := []struct {
		name        string
		msgInfo     *messageInfo
		caller      string
		wantErr     bool
		errContains string
	}{
		{
			name: "caller matches assignee - valid release",
			msgInfo: &messageInfo{
				ID:        "hq-test1",
				Title:     "Test Message",
				Assignee:  "gastown/polecats/nux",
				QueueName: "work/gastown",
				Status:    "in_progress",
			},
			caller:  "gastown/polecats/nux",
			wantErr: false,
		},
		{
			name: "message still in queue - not claimed",
			msgInfo: &messageInfo{
				ID:        "hq-test2",
				Title:     "Test Message",
				Assignee:  "queue:work/gastown",
				QueueName: "work/gastown",
				Status:    "open",
			},
			caller:      "gastown/polecats/nux",
			wantErr:     true,
			errContains: "not claimed",
		},
		{
			name: "claimed by different worker",
			msgInfo: &messageInfo{
				ID:        "hq-test3",
				Title:     "Test Message",
				Assignee:  "gastown/polecats/other",
				QueueName: "work/gastown",
				Status:    "in_progress",
			},
			caller:      "gastown/polecats/nux",
			wantErr:     true,
			errContains: "was claimed by",
		},
		{
			name: "not a queue message",
			msgInfo: &messageInfo{
				ID:        "hq-test4",
				Title:     "Test Message",
				Assignee:  "gastown/polecats/nux",
				QueueName: "", // No queue label
				Status:    "open",
			},
			caller:      "gastown/polecats/nux",
			wantErr:     true,
			errContains: "not a queue message",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateRelease(tt.msgInfo, tt.caller)
			if tt.wantErr {
				if err == nil {
					t.Error("expected error, got nil")
					return
				}
				if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
					t.Errorf("error %q should contain %q", err.Error(), tt.errContains)
				}
			} else {
				if err != nil {
					t.Errorf("unexpected error: %v", err)
				}
			}
		})
	}
}

// validateRelease checks if a message can be released by the caller.
// This is extracted for testing; the actual release command uses this logic inline.
func validateRelease(msgInfo *messageInfo, caller string) error {
	// Verify message is a queue message
	if msgInfo.QueueName == "" {
		return fmt.Errorf("message %s is not a queue message (no queue label)", msgInfo.ID)
	}

	// Verify caller is the one who claimed it
	if msgInfo.Assignee != caller {
		if strings.HasPrefix(msgInfo.Assignee, "queue:") {
			return fmt.Errorf("message %s is not claimed (still in queue)", msgInfo.ID)
		}
		return fmt.Errorf("message %s was claimed by %s, not %s", msgInfo.ID, msgInfo.Assignee, caller)
	}

	return nil
}

// TestMailAnnounces tests the announces command functionality.
func TestMailAnnounces(t *testing.T) {
	t.Run("listAnnounceChannels with nil config", func(t *testing.T) {
		// Test with nil announces map
		cfg := &config.MessagingConfig{
			Announces: nil,
		}

		// Reset flag to default
		mailAnnouncesJSON = false

		// This should not panic and should handle nil gracefully
		// We can't easily capture stdout in unit tests, but we can verify no panic
		err := listAnnounceChannels(cfg)
		if err != nil {
			t.Errorf("listAnnounceChannels with nil announces should not error: %v", err)
		}
	})

	t.Run("listAnnounceChannels with empty config", func(t *testing.T) {
		cfg := &config.MessagingConfig{
			Announces: make(map[string]config.AnnounceConfig),
		}

		mailAnnouncesJSON = false
		err := listAnnounceChannels(cfg)
		if err != nil {
			t.Errorf("listAnnounceChannels with empty announces should not error: %v", err)
		}
	})

	t.Run("readAnnounceChannel validates channel exists", func(t *testing.T) {
		cfg := &config.MessagingConfig{
			Announces: map[string]config.AnnounceConfig{
				"alerts": {
					Readers:     []string{"@town"},
					RetainCount: 100,
				},
			},
		}

		// Test with unknown channel
		err := readAnnounceChannel("/tmp", cfg, "nonexistent")
		if err == nil {
			t.Error("readAnnounceChannel should error for unknown channel")
		}
		if !strings.Contains(err.Error(), "unknown announce channel") {
			t.Errorf("error should mention 'unknown announce channel', got: %v", err)
		}
	})

	t.Run("readAnnounceChannel errors on nil announces", func(t *testing.T) {
		cfg := &config.MessagingConfig{
			Announces: nil,
		}

		err := readAnnounceChannel("/tmp", cfg, "alerts")
		if err == nil {
			t.Error("readAnnounceChannel should error for nil announces")
		}
		if !strings.Contains(err.Error(), "no announce channels configured") {
			t.Errorf("error should mention 'no announce channels configured', got: %v", err)
		}
	})
}

// TestAnnounceMessageParsing tests parsing of announce messages from beads output.
func TestAnnounceMessageParsing(t *testing.T) {
	tests := []struct {
		name   string
		labels []string
		want   string
	}{
		{
			name:   "extracts from label",
			labels: []string{"from:mayor/", "announce_channel:alerts"},
			want:   "mayor/",
		},
		{
			name:   "extracts from with rig path",
			labels: []string{"announce_channel:alerts", "from:gastown/witness"},
			want:   "gastown/witness",
		},
		{
			name:   "no from label",
			labels: []string{"announce_channel:alerts"},
			want:   "",
		},
		{
			name:   "empty labels",
			labels: []string{},
			want:   "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Simulate the label extraction logic from listAnnounceMessages
			var from string
			for _, label := range tt.labels {
				if strings.HasPrefix(label, "from:") {
					from = strings.TrimPrefix(label, "from:")
					break
				}
			}
			if from != tt.want {
				t.Errorf("extracting from label: got %q, want %q", from, tt.want)
			}
		})
	}
}
