package app

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

	"github.com/stretchr/testify/require"
)

type fakeFeedbackReminderSource struct {
	targets []FeedbackReminderTarget
}

func (s *fakeFeedbackReminderSource) ListTargets(context.Context, time.Time) ([]FeedbackReminderTarget, error) {
	return append([]FeedbackReminderTarget(nil), s.targets...), nil
}

type fakeFeedbackReminderJira struct {
	assignees map[string]FeedbackReminderAssignee
}

func (j *fakeFeedbackReminderJira) CurrentAssignee(_ context.Context, issueKey string) (FeedbackReminderAssignee, error) {
	return j.assignees[issueKey], nil
}

type fakeFeedbackReminderDirectory struct {
	byEmail map[string]string
	byName  map[string]string
}

func (d *fakeFeedbackReminderDirectory) ResolveByEmails(context.Context, []string) (map[string]string, error) {
	return d.byEmail, nil
}

func (d *fakeFeedbackReminderDirectory) ResolveByExactNames(context.Context, []string) (map[string]string, error) {
	return d.byName, nil
}

type fakeFeedbackReminderSender struct {
	chatID string
	uuid   string
	groups []FeedbackReminderGroup
}

func (s *fakeFeedbackReminderSender) SendFeedbackReminder(_ context.Context, chatID, uuid string, groups []FeedbackReminderGroup) error {
	s.chatID = chatID
	s.uuid = uuid
	s.groups = groups
	return nil
}

func TestFeedbackReminderGroupsLinkedTicketsByCurrentAssignee(t *testing.T) {
	t.Parallel()
	sender := &fakeFeedbackReminderSender{}
	reminder := &FeedbackReminder{
		source: &fakeFeedbackReminderSource{targets: []FeedbackReminderTarget{
			{IssueKey: "TCID-13057"}, {IssueKey: "ST-2901"}, {IssueKey: "TCID-13101"},
		}},
		jira: &fakeFeedbackReminderJira{assignees: map[string]FeedbackReminderAssignee{
			"TCID-13057": {DisplayName: "Puah Yi Kai", EmailAddress: "puah@example.com"},
			"ST-2901":    {DisplayName: "Puah Yi Kai", EmailAddress: "puah@example.com"},
			"TCID-13101": {DisplayName: "Yasuo Honda"},
		}},
		directory: &fakeFeedbackReminderDirectory{
			byEmail: map[string]string{"puah@example.com": "ou_puah"},
			byName:  map[string]string{"yasuo honda": "ou_yasuo"},
		},
		sender: sender, chatID: "oc_feedback", jiraBaseURL: "https://jira.example", lookbackDays: 14,
		now: func() time.Time { return time.Date(2026, 9, 1, 17, 0, 0, 0, time.UTC) },
	}

	result, err := reminder.Run(context.Background(), "fb-reminder-manual:test")
	require.NoError(t, err)
	require.Equal(t, FeedbackReminderResult{TicketCount: 3, AssigneeCount: 2, Sent: true}, result)
	require.Equal(t, "oc_feedback", sender.chatID)
	require.Equal(t, "fb-reminder-manual:test", sender.uuid)
	require.Len(t, sender.groups, 2)
	require.Equal(t, "ou_puah", sender.groups[0].OpenID)
	require.Equal(t, []FeedbackReminderTicket{
		{IssueKey: "TCID-13057", JiraURL: "https://jira.example/browse/TCID-13057"},
		{IssueKey: "ST-2901", JiraURL: "https://jira.example/browse/ST-2901"},
	}, sender.groups[0].Tickets)
	require.Equal(t, "ou_yasuo", sender.groups[1].OpenID)
}

func TestFeedbackReminderFailsClosedWhenAssigneeMentionCannotBeResolved(t *testing.T) {
	t.Parallel()
	sender := &fakeFeedbackReminderSender{}
	reminder := &FeedbackReminder{
		source: &fakeFeedbackReminderSource{targets: []FeedbackReminderTarget{{IssueKey: "TCID-1"}}},
		jira: &fakeFeedbackReminderJira{assignees: map[string]FeedbackReminderAssignee{
			"TCID-1": {DisplayName: "Missing User"},
		}},
		directory: &fakeFeedbackReminderDirectory{byEmail: map[string]string{}, byName: map[string]string{}},
		sender:    sender, chatID: "oc_feedback", jiraBaseURL: "https://jira.example", lookbackDays: 14, now: time.Now,
	}

	_, err := reminder.Run(context.Background(), "fb-reminder:test")
	require.ErrorContains(t, err, "cannot resolve Feishu mention")
	require.Empty(t, sender.groups)
}

func TestHTTPFeedbackReminderSourceUsesReadAPIContract(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
		require.Equal(t, "/v1/auto-analysis/feedback-reminder-targets", request.URL.Path)
		require.Equal(t, "2026-08-20T09:00:00Z", request.URL.Query().Get("since"))
		response.Header().Set("Content-Type", "application/json")
		_, _ = response.Write([]byte(`{"status":"ok","targets":[{"issueKey":"TCID-13057","runCreatedAt":"2026-09-01T08:00:00Z"}]}`))
	}))
	defer server.Close()

	source := &HTTPFeedbackReminderSource{baseURL: server.URL, client: server.Client()}
	targets, err := source.ListTargets(context.Background(), time.Date(2026, 8, 20, 9, 0, 0, 0, time.UTC))
	require.NoError(t, err)
	require.Equal(t, []FeedbackReminderTarget{{
		IssueKey: "TCID-13057", RunCreatedAt: time.Date(2026, 9, 1, 8, 0, 0, 0, time.UTC),
	}}, targets)
}

func TestCoreDirectoryResolvesOneExactEnglishName(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
		require.Equal(t, "/api/v1/core/user/users", request.URL.Path)
		require.Equal(t, "Yasuo Honda", request.URL.Query().Get("userKeyword"))
		require.Equal(t, "true", request.URL.Query().Get("includeUnavailable"))
		require.Equal(t, "20", request.URL.Query().Get("perPage"))
		response.Header().Set("Content-Type", "application/json")
		_, _ = response.Write([]byte(`{"users":[{"larkUserOpenId":"ou_yasuo","larkUserName":"本田康雄","larkUserEnName":"Yasuo Honda"}]}`))
	}))
	defer server.Close()

	directory := &CoreLarkFeedbackReminderDirectory{coreUserBaseURL: server.URL, client: server.Client()}
	resolved, err := directory.ResolveByExactNames(context.Background(), []string{"Yasuo Honda"})
	require.NoError(t, err)
	require.Equal(t, map[string]string{"yasuo honda": "ou_yasuo"}, resolved)
}

func TestCoreDirectoryResolvesExactEmail(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
		require.Equal(t, "/api/v1/core/user/users/assignee@example.com", request.URL.Path)
		response.Header().Set("Content-Type", "application/json")
		_, _ = response.Write([]byte(`{"user":{"email":"assignee@example.com","larkUserOpenId":"ou_assignee"}}`))
	}))
	defer server.Close()

	directory := &CoreLarkFeedbackReminderDirectory{coreUserBaseURL: server.URL, client: server.Client()}
	resolved, err := directory.ResolveByEmails(context.Background(), []string{"Assignee@Example.com"})
	require.NoError(t, err)
	require.Equal(t, map[string]string{"assignee@example.com": "ou_assignee"}, resolved)
}

func TestNextFeedbackReminderRunUsesShanghaiWeekdaysAtFive(t *testing.T) {
	t.Parallel()
	location, err := time.LoadLocation("Asia/Shanghai")
	require.NoError(t, err)
	tests := []struct {
		name string
		now  string
		want string
	}{
		{name: "same weekday before five", now: "2026-08-31T16:59:00+08:00", want: "2026-08-31T17:00:00+08:00"},
		{name: "next weekday after five", now: "2026-08-31T17:01:00+08:00", want: "2026-09-01T17:00:00+08:00"},
		{name: "skip weekend", now: "2026-09-04T17:01:00+08:00", want: "2026-09-07T17:00:00+08:00"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			now, err := time.Parse(time.RFC3339, test.now)
			require.NoError(t, err)
			require.Equal(t, test.want, nextFeedbackReminderRun(now, location, 17).Format(time.RFC3339))
		})
	}
}

func TestBoundedReminderUUIDFitsLarkLimit(t *testing.T) {
	t.Parallel()
	require.Len(t, boundedReminderUUID(strings.Repeat("x", 80)), 50)
}

func TestFeedbackReminderPostUsesMentionsAndNamedHyperlinks(t *testing.T) {
	t.Parallel()
	content, err := feedbackReminderPostContent([]FeedbackReminderGroup{{
		DisplayName: "Puah Yi Kai",
		OpenID:      "ou_puah",
		Tickets: []FeedbackReminderTicket{{
			IssueKey: "TCID-13057",
			JiraURL:  "https://tidb.atlassian.net/browse/TCID-13057",
		}},
	}})
	require.NoError(t, err)
	require.Contains(t, content, `"tag":"at"`)
	require.Contains(t, content, `"tag":"a"`)
	require.Contains(t, content, `"text":"TCID-13057"`)
	require.Contains(t, content, `"href":"https://tidb.atlassian.net/browse/TCID-13057"`)
	var payload map[string]any
	require.NoError(t, json.Unmarshal([]byte(content), &payload))
	require.Contains(t, payload, "en_us")
}
