package v1

import (
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"testing"

	modelv1 "github.com/pingcap-inc/security/model/v1"
)

func TestUniqueCollaborators(t *testing.T) {
	tests := []struct {
		name        string
		responsible string
		candidates  []string
		want        []string
	}{
		{
			name:        "removes blank responsible and repeated recipients",
			responsible: primaryNotificationLarkUserID,
			candidates:  []string{"", primaryNotificationLarkUserID, "eae79ad6", "897a1cg1", "eae79ad6"},
			want:        []string{"eae79ad6", "897a1cg1"},
		},
		{
			name:        "keeps first occurrence order for dynamic collaborators",
			responsible: "dynamic-owner",
			candidates:  []string{"dynamic-hrbp", "eae79ad6", "dynamic-hrbp", "897a1cg1"},
			want:        []string{"dynamic-hrbp", "eae79ad6", "897a1cg1"},
		},
		{
			name:        "supports an empty collaborator list",
			responsible: primaryNotificationLarkUserID,
			want:        []string{},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := uniqueCollaborators(tt.responsible, tt.candidates...)
			if !reflect.DeepEqual(got, tt.want) {
				t.Fatalf("uniqueCollaborators() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestDownloadAlertRecipientDryRun(t *testing.T) {
	service := &downloadAlertService{}
	user := &UserInfo{
		UserID: "downloader",
		Leader: "manager",
		HRBP:   "must-not-receive",
		Email:  "downloader@example.invalid",
	}

	tests := []struct {
		name              string
		alertType         int
		wantResponsible   []string
		wantCollaborators []string
	}{
		{
			name:              "first alert notifies downloader and Leo Zhuang",
			alertType:         0,
			wantResponsible:   []string{"downloader"},
			wantCollaborators: []string{downloadInitialLarkUserID},
		},
		{
			name:              "second alert notifies manager Liu Yin and Leo Zhuang",
			alertType:         1,
			wantResponsible:   []string{"manager"},
			wantCollaborators: []string{primaryNotificationLarkUserID, downloadInitialLarkUserID},
		},
		{
			name:              "third alert keeps the escalation recipients",
			alertType:         2,
			wantResponsible:   []string{"manager"},
			wantCollaborators: []string{primaryNotificationLarkUserID, downloadInitialLarkUserID},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			form := service.makeBody("10m", "4", user.Email, tt.alertType, []string{"document"}, user)
			if got := contactValue(t, form, modelv1.ResponsiblePerson); !reflect.DeepEqual(got, tt.wantResponsible) {
				t.Fatalf("responsible recipients = %v, want %v", got, tt.wantResponsible)
			}
			if got := contactValue(t, form, modelv1.Collaborators); !reflect.DeepEqual(got, tt.wantCollaborators) {
				t.Fatalf("collaborator recipients = %v, want %v", got, tt.wantCollaborators)
			}
		})
	}
}

func TestDownloadAlertRecipientDeduplication(t *testing.T) {
	tests := []struct {
		name              string
		resolve           func() (string, []string)
		wantResponsible   string
		wantCollaborators []string
	}{
		{
			name: "downloader is Leo Zhuang",
			resolve: func() (string, []string) {
				return firstDownloadAlertRecipients(downloadInitialLarkUserID)
			},
			wantResponsible:   downloadInitialLarkUserID,
			wantCollaborators: []string{},
		},
		{
			name: "manager is Liu Yin",
			resolve: func() (string, []string) {
				return escalatedDownloadAlertRecipients(primaryNotificationLarkUserID)
			},
			wantResponsible:   primaryNotificationLarkUserID,
			wantCollaborators: []string{downloadInitialLarkUserID},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			responsible, collaborators := tt.resolve()
			if responsible != tt.wantResponsible {
				t.Fatalf("responsible recipient = %q, want %q", responsible, tt.wantResponsible)
			}
			if !reflect.DeepEqual(collaborators, tt.wantCollaborators) {
				t.Fatalf("collaborator recipients = %v, want %v", collaborators, tt.wantCollaborators)
			}
		})
	}
}

func TestNotificationRoutingDryRun(t *testing.T) {
	source := serviceSource(t)
	legacyRecipients := []string{
		"c5792e6d",
		"e359137a",
		"53cc88aa",
		"8g15cff5",
		"48fe7651",
		"e372ega5",
		"2f93dfgb",
		"9c7ca79f",
		"shuai.feng@",
		"xiaopeng.zhu@",
		"ken.liu@",
	}

	for _, recipient := range legacyRecipients {
		if strings.Contains(source, recipient) {
			t.Errorf("legacy static recipient %q remains in service source", recipient)
		}
	}

	monthlyReport, err := os.ReadFile(serviceSourcePath(t, "monthly-report.go"))
	if err != nil {
		t.Fatalf("read monthly-report.go: %v", err)
	}
	const expectedMonthlyRecipients = "to := \"liuy@pingcap.cn,jason.yu@pingcap.cn,shanghao.zhuang@pingcap.cn\""
	if !strings.Contains(string(monthlyReport), expectedMonthlyRecipients) {
		t.Fatalf("monthly report recipients do not match the requested three-person allowlist")
	}
}

func serviceSource(t *testing.T) string {
	t.Helper()
	dir := filepath.Dir(serviceSourcePath(t, "notification_recipients.go"))
	entries, err := os.ReadDir(dir)
	if err != nil {
		t.Fatalf("read service source directory: %v", err)
	}

	var source strings.Builder
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
			continue
		}
		contents, err := os.ReadFile(filepath.Join(dir, entry.Name()))
		if err != nil {
			t.Fatalf("read %s: %v", entry.Name(), err)
		}
		source.Write(contents)
	}

	return source.String()
}

func contactValue(t *testing.T, form []FormItem, id string) []string {
	t.Helper()
	for _, item := range form {
		if item.Id != id {
			continue
		}
		value, ok := item.Value.([]string)
		if !ok {
			t.Fatalf("contact form %q has value type %T, want []string", id, item.Value)
		}

		return value
	}

	t.Fatalf("contact form %q is missing", id)

	return nil
}

func serviceSourcePath(t *testing.T, filename string) string {
	t.Helper()
	_, thisFile, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("locate test source")
	}

	return filepath.Join(filepath.Dir(thisFile), filename)
}
