package mail

import (
	"os"
	"path/filepath"
	"testing"
)

func TestDetectTownRoot(t *testing.T) {
	// Create temp directory structure
	tmpDir := t.TempDir()
	townRoot := filepath.Join(tmpDir, "town")
	mayorDir := filepath.Join(townRoot, "mayor")
	rigDir := filepath.Join(townRoot, "gastown", "polecats", "test")

	// Create mayor/town.json marker
	if err := os.MkdirAll(mayorDir, 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(mayorDir, "town.json"), []byte("{}"), 0644); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(rigDir, 0755); err != nil {
		t.Fatal(err)
	}

	tests := []struct {
		name     string
		startDir string
		want     string
	}{
		{
			name:     "from town root",
			startDir: townRoot,
			want:     townRoot,
		},
		{
			name:     "from rig subdirectory",
			startDir: rigDir,
			want:     townRoot,
		},
		{
			name:     "from mayor directory",
			startDir: mayorDir,
			want:     townRoot,
		},
		{
			name:     "from non-town directory",
			startDir: tmpDir,
			want:     "", // No town.json marker above tmpDir
		},
	}

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

func TestIsTownLevelAddress(t *testing.T) {
	tests := []struct {
		address string
		want    bool
	}{
		{"mayor", true},
		{"mayor/", true},
		{"deacon", true},
		{"deacon/", true},
		{"gastown/refinery", false},
		{"gastown/polecats/Toast", false},
		{"gastown/", false},
		{"", false},
	}

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

func TestAddressToSessionID(t *testing.T) {
	tests := []struct {
		address string
		want    string
	}{
		{"mayor", "hq-mayor"},
		{"mayor/", "hq-mayor"},
		{"deacon", "hq-deacon"},
		{"gastown/refinery", "gt-gastown-refinery"},
		{"gastown/Toast", "gt-gastown-Toast"},
		{"beads/witness", "gt-beads-witness"},
		{"gastown/", ""},   // Empty target
		{"gastown", ""},    // No slash
		{"", ""},           // Empty address
	}

	for _, tt := range tests {
		t.Run(tt.address, func(t *testing.T) {
			got := addressToSessionID(tt.address)
			if got != tt.want {
				t.Errorf("addressToSessionID(%q) = %q, want %q", tt.address, got, tt.want)
			}
		})
	}
}

func TestIsSelfMail(t *testing.T) {
	tests := []struct {
		from string
		to   string
		want bool
	}{
		{"mayor/", "mayor/", true},
		{"mayor", "mayor/", true},
		{"mayor/", "mayor", true},
		{"gastown/Toast", "gastown/Toast", true},
		{"gastown/Toast/", "gastown/Toast", true},
		{"mayor/", "deacon/", false},
		{"gastown/Toast", "gastown/Nux", false},
		{"", "", true},
	}

	for _, tt := range tests {
		t.Run(tt.from+"->"+tt.to, func(t *testing.T) {
			got := isSelfMail(tt.from, tt.to)
			if got != tt.want {
				t.Errorf("isSelfMail(%q, %q) = %v, want %v", tt.from, tt.to, got, tt.want)
			}
		})
	}
}

func TestShouldBeWisp(t *testing.T) {
	r := &Router{}

	tests := []struct {
		name    string
		msg     *Message
		want    bool
	}{
		{
			name: "explicit wisp flag",
			msg:  &Message{Subject: "Regular message", Wisp: true},
			want: true,
		},
		{
			name: "POLECAT_STARTED subject",
			msg:  &Message{Subject: "POLECAT_STARTED: Toast"},
			want: true,
		},
		{
			name: "polecat_done subject (lowercase)",
			msg:  &Message{Subject: "polecat_done: work complete"},
			want: true,
		},
		{
			name: "NUDGE subject",
			msg:  &Message{Subject: "NUDGE: check your hook"},
			want: true,
		},
		{
			name: "START_WORK subject",
			msg:  &Message{Subject: "START_WORK: gt-123"},
			want: true,
		},
		{
			name: "regular message",
			msg:  &Message{Subject: "Please review this PR"},
			want: false,
		},
		{
			name: "handoff message (not auto-wisp)",
			msg:  &Message{Subject: "HANDOFF: context notes"},
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := r.shouldBeWisp(tt.msg)
			if got != tt.want {
				t.Errorf("shouldBeWisp(%v) = %v, want %v", tt.msg.Subject, got, tt.want)
			}
		})
	}
}

func TestResolveBeadsDir(t *testing.T) {
	// With town root set
	r := NewRouterWithTownRoot("/work/dir", "/home/user/gt")
	got := r.resolveBeadsDir("gastown/Toast")
	want := "/home/user/gt/.beads"
	if got != want {
		t.Errorf("resolveBeadsDir with townRoot = %q, want %q", got, want)
	}

	// Without town root (fallback to workDir)
	r2 := &Router{workDir: "/work/dir", townRoot: ""}
	got2 := r2.resolveBeadsDir("mayor/")
	want2 := "/work/dir/.beads"
	if got2 != want2 {
		t.Errorf("resolveBeadsDir without townRoot = %q, want %q", got2, want2)
	}
}

func TestNewRouterWithTownRoot(t *testing.T) {
	r := NewRouterWithTownRoot("/work/rig", "/home/gt")
	if r.workDir != "/work/rig" {
		t.Errorf("workDir = %q, want '/work/rig'", r.workDir)
	}
	if r.townRoot != "/home/gt" {
		t.Errorf("townRoot = %q, want '/home/gt'", r.townRoot)
	}
}

// ============ Mailing List Tests ============

func TestIsListAddress(t *testing.T) {
	tests := []struct {
		address string
		want    bool
	}{
		{"list:oncall", true},
		{"list:cleanup/gastown", true},
		{"list:", true}, // Edge case: empty list name (will fail on expand)
		{"mayor/", false},
		{"gastown/witness", false},
		{"listoncall", false}, // Missing colon
		{"", false},
	}

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

func TestParseListName(t *testing.T) {
	tests := []struct {
		address string
		want    string
	}{
		{"list:oncall", "oncall"},
		{"list:cleanup/gastown", "cleanup/gastown"},
		{"list:", ""},
		{"list:alerts", "alerts"},
	}

	for _, tt := range tests {
		t.Run(tt.address, func(t *testing.T) {
			got := parseListName(tt.address)
			if got != tt.want {
				t.Errorf("parseListName(%q) = %q, want %q", tt.address, got, tt.want)
			}
		})
	}
}

func TestIsQueueAddress(t *testing.T) {
	tests := []struct {
		address string
		want    bool
	}{
		{"queue:work", true},
		{"queue:gastown/polecats", true},
		{"queue:", true}, // Edge case: empty queue name (will fail on expand)
		{"mayor/", false},
		{"gastown/witness", false},
		{"queuework", false}, // Missing colon
		{"list:oncall", false},
		{"", false},
	}

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

func TestParseQueueName(t *testing.T) {
	tests := []struct {
		address string
		want    string
	}{
		{"queue:work", "work"},
		{"queue:gastown/polecats", "gastown/polecats"},
		{"queue:", ""},
		{"queue:priority-high", "priority-high"},
	}

	for _, tt := range tests {
		t.Run(tt.address, func(t *testing.T) {
			got := parseQueueName(tt.address)
			if got != tt.want {
				t.Errorf("parseQueueName(%q) = %q, want %q", tt.address, got, tt.want)
			}
		})
	}
}

func TestExpandList(t *testing.T) {
	// Create temp directory with messaging config
	tmpDir := t.TempDir()
	configDir := filepath.Join(tmpDir, "config")
	if err := os.MkdirAll(configDir, 0755); err != nil {
		t.Fatal(err)
	}

	// Write messaging.json with test lists
	configContent := `{
  "type": "messaging",
  "version": 1,
  "lists": {
    "oncall": ["mayor/", "gastown/witness"],
    "cleanup/gastown": ["gastown/witness", "deacon/"]
  }
}`
	if err := os.WriteFile(filepath.Join(configDir, "messaging.json"), []byte(configContent), 0644); err != nil {
		t.Fatal(err)
	}

	r := NewRouterWithTownRoot(tmpDir, tmpDir)

	tests := []struct {
		name      string
		listName  string
		want      []string
		wantErr   bool
		errString string
	}{
		{
			name:     "oncall list",
			listName: "oncall",
			want:     []string{"mayor/", "gastown/witness"},
		},
		{
			name:     "cleanup/gastown list",
			listName: "cleanup/gastown",
			want:     []string{"gastown/witness", "deacon/"},
		},
		{
			name:      "unknown list",
			listName:  "nonexistent",
			wantErr:   true,
			errString: "unknown mailing list",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := r.expandList(tt.listName)
			if tt.wantErr {
				if err == nil {
					t.Errorf("expandList(%q) expected error, got nil", tt.listName)
				} else if tt.errString != "" && !contains(err.Error(), tt.errString) {
					t.Errorf("expandList(%q) error = %v, want containing %q", tt.listName, err, tt.errString)
				}
				return
			}
			if err != nil {
				t.Errorf("expandList(%q) unexpected error: %v", tt.listName, err)
				return
			}
			if len(got) != len(tt.want) {
				t.Errorf("expandList(%q) = %v, want %v", tt.listName, got, tt.want)
				return
			}
			for i, addr := range got {
				if addr != tt.want[i] {
					t.Errorf("expandList(%q)[%d] = %q, want %q", tt.listName, i, addr, tt.want[i])
				}
			}
		})
	}
}

func TestExpandListNoTownRoot(t *testing.T) {
	r := &Router{workDir: "/tmp", townRoot: ""}
	_, err := r.expandList("oncall")
	if err == nil {
		t.Error("expandList with no townRoot should error")
	}
	if !contains(err.Error(), "no town root") {
		t.Errorf("expandList error = %v, want containing 'no town root'", err)
	}
}

func TestExpandQueue(t *testing.T) {
	// Create temp directory with messaging config
	tmpDir := t.TempDir()
	configDir := filepath.Join(tmpDir, "config")
	if err := os.MkdirAll(configDir, 0755); err != nil {
		t.Fatal(err)
	}

	// Write messaging.json with test queues
	configContent := `{
  "type": "messaging",
  "version": 1,
  "queues": {
    "work/gastown": {"workers": ["gastown/polecats/*"], "max_claims": 3},
    "priority-high": {"workers": ["mayor/", "gastown/witness"]}
  }
}`
	if err := os.WriteFile(filepath.Join(configDir, "messaging.json"), []byte(configContent), 0644); err != nil {
		t.Fatal(err)
	}

	r := NewRouterWithTownRoot(tmpDir, tmpDir)

	tests := []struct {
		name        string
		queueName   string
		wantWorkers []string
		wantMax     int
		wantErr     bool
		errString   string
	}{
		{
			name:        "work/gastown queue",
			queueName:   "work/gastown",
			wantWorkers: []string{"gastown/polecats/*"},
			wantMax:     3,
		},
		{
			name:        "priority-high queue",
			queueName:   "priority-high",
			wantWorkers: []string{"mayor/", "gastown/witness"},
			wantMax:     0, // Not specified, defaults to 0
		},
		{
			name:      "unknown queue",
			queueName: "nonexistent",
			wantErr:   true,
			errString: "unknown queue",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := r.expandQueue(tt.queueName)
			if tt.wantErr {
				if err == nil {
					t.Errorf("expandQueue(%q) expected error, got nil", tt.queueName)
				} else if tt.errString != "" && !contains(err.Error(), tt.errString) {
					t.Errorf("expandQueue(%q) error = %v, want containing %q", tt.queueName, err, tt.errString)
				}
				return
			}
			if err != nil {
				t.Errorf("expandQueue(%q) unexpected error: %v", tt.queueName, err)
				return
			}
			if len(got.Workers) != len(tt.wantWorkers) {
				t.Errorf("expandQueue(%q).Workers = %v, want %v", tt.queueName, got.Workers, tt.wantWorkers)
				return
			}
			for i, worker := range got.Workers {
				if worker != tt.wantWorkers[i] {
					t.Errorf("expandQueue(%q).Workers[%d] = %q, want %q", tt.queueName, i, worker, tt.wantWorkers[i])
				}
			}
			if got.MaxClaims != tt.wantMax {
				t.Errorf("expandQueue(%q).MaxClaims = %d, want %d", tt.queueName, got.MaxClaims, tt.wantMax)
			}
		})
	}
}

func TestExpandQueueNoTownRoot(t *testing.T) {
	r := &Router{workDir: "/tmp", townRoot: ""}
	_, err := r.expandQueue("work")
	if err == nil {
		t.Error("expandQueue with no townRoot should error")
	}
	if !contains(err.Error(), "no town root") {
		t.Errorf("expandQueue error = %v, want containing 'no town root'", err)
	}
}

// ============ Announce Address Tests ============

func TestIsAnnounceAddress(t *testing.T) {
	tests := []struct {
		address string
		want    bool
	}{
		{"announce:bulletin", true},
		{"announce:gastown/updates", true},
		{"announce:", true}, // Edge case: empty announce name (will fail on expand)
		{"mayor/", false},
		{"gastown/witness", false},
		{"announcebulletin", false}, // Missing colon
		{"list:oncall", false},
		{"queue:work", false},
		{"", false},
	}

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

func TestParseAnnounceName(t *testing.T) {
	tests := []struct {
		address string
		want    string
	}{
		{"announce:bulletin", "bulletin"},
		{"announce:gastown/updates", "gastown/updates"},
		{"announce:", ""},
		{"announce:priority-alerts", "priority-alerts"},
	}

	for _, tt := range tests {
		t.Run(tt.address, func(t *testing.T) {
			got := parseAnnounceName(tt.address)
			if got != tt.want {
				t.Errorf("parseAnnounceName(%q) = %q, want %q", tt.address, got, tt.want)
			}
		})
	}
}

// contains checks if s contains substr (helper for error checking)
func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
}

func containsHelper(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}

// ============ @group Address Tests ============

func TestIsGroupAddress(t *testing.T) {
	tests := []struct {
		address string
		want    bool
	}{
		{"@rig/gastown", true},
		{"@town", true},
		{"@witnesses", true},
		{"@crew/gastown", true},
		{"@dogs", true},
		{"@overseer", true},
		{"@polecats/gastown", true},
		{"mayor/", false},
		{"gastown/Toast", false},
		{"", false},
		{"rig/gastown", false}, // Missing @
	}

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

func TestParseGroupAddress(t *testing.T) {
	tests := []struct {
		address      string
		wantType     GroupType
		wantRoleType string
		wantRig      string
		wantNil      bool
	}{
		// Special patterns
		{"@overseer", GroupTypeOverseer, "", "", false},
		{"@town", GroupTypeTown, "", "", false},

		// Role-based patterns (all agents of a role type)
		{"@witnesses", GroupTypeRole, "witness", "", false},
		{"@dogs", GroupTypeRole, "dog", "", false},
		{"@refineries", GroupTypeRole, "refinery", "", false},
		{"@deacons", GroupTypeRole, "deacon", "", false},

		// Rig pattern (all agents in a rig)
		{"@rig/gastown", GroupTypeRig, "", "gastown", false},
		{"@rig/beads", GroupTypeRig, "", "beads", false},

		// Rig+role patterns
		{"@crew/gastown", GroupTypeRigRole, "crew", "gastown", false},
		{"@polecats/gastown", GroupTypeRigRole, "polecat", "gastown", false},

		// Invalid patterns
		{"mayor/", "", "", "", true},
		{"@invalid", "", "", "", true},
		{"@crew/", "", "", "", true}, // Empty rig
		{"@rig", "", "", "", true},   // Missing rig name
		{"", "", "", "", true},
	}

	for _, tt := range tests {
		t.Run(tt.address, func(t *testing.T) {
			got := parseGroupAddress(tt.address)

			if tt.wantNil {
				if got != nil {
					t.Errorf("parseGroupAddress(%q) = %+v, want nil", tt.address, got)
				}
				return
			}

			if got == nil {
				t.Errorf("parseGroupAddress(%q) = nil, want non-nil", tt.address)
				return
			}

			if got.Type != tt.wantType {
				t.Errorf("parseGroupAddress(%q).Type = %q, want %q", tt.address, got.Type, tt.wantType)
			}
			if got.RoleType != tt.wantRoleType {
				t.Errorf("parseGroupAddress(%q).RoleType = %q, want %q", tt.address, got.RoleType, tt.wantRoleType)
			}
			if got.Rig != tt.wantRig {
				t.Errorf("parseGroupAddress(%q).Rig = %q, want %q", tt.address, got.Rig, tt.wantRig)
			}
			if got.Original != tt.address {
				t.Errorf("parseGroupAddress(%q).Original = %q, want %q", tt.address, got.Original, tt.address)
			}
		})
	}
}

func TestAgentBeadToAddress(t *testing.T) {
	tests := []struct {
		name   string
		bead   *agentBead
		want   string
	}{
		{
			name: "nil bead",
			bead: nil,
			want: "",
		},
		{
			name: "town-level mayor",
			bead: &agentBead{ID: "gt-mayor"},
			want: "mayor/",
		},
		{
			name: "town-level deacon",
			bead: &agentBead{ID: "gt-deacon"},
			want: "deacon/",
		},
		{
			name: "rig singleton witness",
			bead: &agentBead{ID: "gt-gastown-witness"},
			want: "gastown/witness",
		},
		{
			name: "rig singleton refinery",
			bead: &agentBead{ID: "gt-gastown-refinery"},
			want: "gastown/refinery",
		},
		{
			name: "rig crew worker",
			bead: &agentBead{ID: "gt-gastown-crew-max"},
			want: "gastown/max",
		},
		{
			name: "rig polecat worker",
			bead: &agentBead{ID: "gt-gastown-polecat-Toast"},
			want: "gastown/Toast",
		},
		{
			name: "rig polecat with hyphenated name",
			bead: &agentBead{ID: "gt-gastown-polecat-my-agent"},
			want: "gastown/my-agent",
		},
		{
			name: "non-gt prefix (invalid)",
			bead: &agentBead{ID: "bd-gastown-witness"},
			want: "",
		},
		{
			name: "empty ID",
			bead: &agentBead{ID: ""},
			want: "",
		},
	}

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

func TestExpandAnnounce(t *testing.T) {
	// Create temp directory with messaging config
	tmpDir := t.TempDir()
	configDir := filepath.Join(tmpDir, "config")
	if err := os.MkdirAll(configDir, 0755); err != nil {
		t.Fatal(err)
	}

	// Write messaging.json with test announces
	configContent := `{
  "type": "messaging",
  "version": 1,
  "announces": {
    "alerts": {"readers": ["@town"], "retain_count": 10},
    "status/gastown": {"readers": ["gastown/witness", "mayor/"], "retain_count": 5}
  }
}`
	if err := os.WriteFile(filepath.Join(configDir, "messaging.json"), []byte(configContent), 0644); err != nil {
		t.Fatal(err)
	}

	r := NewRouterWithTownRoot(tmpDir, tmpDir)

	tests := []struct {
		name         string
		announceName string
		wantReaders  []string
		wantRetain   int
		wantErr      bool
		errString    string
	}{
		{
			name:         "alerts announce",
			announceName: "alerts",
			wantReaders:  []string{"@town"},
			wantRetain:   10,
		},
		{
			name:         "status/gastown announce",
			announceName: "status/gastown",
			wantReaders:  []string{"gastown/witness", "mayor/"},
			wantRetain:   5,
		},
		{
			name:         "unknown announce",
			announceName: "nonexistent",
			wantErr:      true,
			errString:    "unknown announce channel",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := r.expandAnnounce(tt.announceName)
			if tt.wantErr {
				if err == nil {
					t.Errorf("expandAnnounce(%q) expected error, got nil", tt.announceName)
				} else if tt.errString != "" && !contains(err.Error(), tt.errString) {
					t.Errorf("expandAnnounce(%q) error = %v, want containing %q", tt.announceName, err, tt.errString)
				}
				return
			}
			if err != nil {
				t.Errorf("expandAnnounce(%q) unexpected error: %v", tt.announceName, err)
				return
			}
			if len(got.Readers) != len(tt.wantReaders) {
				t.Errorf("expandAnnounce(%q).Readers = %v, want %v", tt.announceName, got.Readers, tt.wantReaders)
				return
			}
			for i, reader := range got.Readers {
				if reader != tt.wantReaders[i] {
					t.Errorf("expandAnnounce(%q).Readers[%d] = %q, want %q", tt.announceName, i, reader, tt.wantReaders[i])
				}
			}
			if got.RetainCount != tt.wantRetain {
				t.Errorf("expandAnnounce(%q).RetainCount = %d, want %d", tt.announceName, got.RetainCount, tt.wantRetain)
			}
		})
	}
}

func TestExpandAnnounceNoTownRoot(t *testing.T) {
	r := &Router{workDir: "/tmp", townRoot: ""}
	_, err := r.expandAnnounce("alerts")
	if err == nil {
		t.Error("expandAnnounce with no townRoot should error")
	}
	if !contains(err.Error(), "no town root") {
		t.Errorf("expandAnnounce error = %v, want containing 'no town root'", err)
	}
}
