package app

import (
	"context"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"testing"
	"time"

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

func TestHTTPFeedbackSourceReadsUnifiedCandidateContract(t *testing.T) {
	t.Parallel()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Query().Get("mode") == "latest" {
			writeJSON(w, http.StatusOK, map[string]any{"latestId": 41})
			return
		}
		require.Equal(t, "41", r.URL.Query().Get("afterId"))
		require.Equal(t, "10", r.URL.Query().Get("limit"))
		writeJSON(w, http.StatusOK, map[string]any{"candidates": []any{map[string]any{
			"id": 42, "signalKey": "feedback:17", "source": "feedback", "signalCode": "ao_wrong",
			"issueKey": "apid-29232", "jobId": 91, "runDbId": 101,
			"answerFingerprint": "sha256:target", "feedbackId": 17, "feedbackAction": "wrong",
			"reason": "facts lost", "createdAt": "2026-08-10T00:00:00.000Z",
		}}})
	}))
	defer server.Close()

	source := NewHTTPFeedbackSource(server.URL)
	latest, err := source.LatestID(context.Background())
	require.NoError(t, err)
	require.Equal(t, int64(41), latest)
	candidates, err := source.List(context.Background(), 41, 10)
	require.NoError(t, err)
	require.Len(t, candidates, 1)
	require.Equal(t, "APID-29232", candidates[0].IssueKey)
	require.Equal(t, int64(91), int64Value(candidates[0].JobID))
	require.Equal(t, "feedback", candidates[0].Source)
}

type fakeFeedbackSource struct {
	latest     int64
	candidates []FeedbackCandidate
}

func (s *fakeFeedbackSource) LatestID(context.Context) (int64, error) {
	return s.latest, nil
}

func (s *fakeFeedbackSource) List(_ context.Context, afterID int64, limit int) ([]FeedbackCandidate, error) {
	result := make([]FeedbackCandidate, 0, limit)
	for _, candidate := range s.candidates {
		if candidate.ID > afterID && len(result) < limit {
			result = append(result, candidate)
		}
	}
	return result, nil
}

func TestFeedbackPollerInitializesAtLatestWithoutReplayingHistory(t *testing.T) {
	store, err := OpenStore(filepath.Join(t.TempDir(), "jobs.json"))
	require.NoError(t, err)
	worker := NewWorker(store, blockingRunner{}, &recordingNotifier{}, time.Hour, time.Hour, "oc_notify")
	source := &fakeFeedbackSource{latest: 41, candidates: []FeedbackCandidate{{
		ID: 41, SignalKey: "runtime:41", Source: "runtime", SignalCode: "final_response_timeout", IssueKey: "APID-29232",
	}}}
	poller := NewFeedbackPoller(store, worker, source, time.Minute, 10)

	require.NoError(t, poller.pollOnce(context.Background()))
	cursor, initialized := store.CandidateCursor()
	require.True(t, initialized)
	require.Equal(t, int64(41), cursor)
	require.Empty(t, store.QueuedIDs())
}

func TestFeedbackPollerQueuesNewFeedbackAndPersistsCursor(t *testing.T) {
	path := filepath.Join(t.TempDir(), "jobs.json")
	store, err := OpenStore(path)
	require.NoError(t, err)
	require.NoError(t, store.SetCandidateCursor(41))
	worker := NewWorker(store, blockingRunner{}, &recordingNotifier{}, time.Hour, time.Hour, "oc_notify")
	reason := "the provider facts disappeared"
	fingerprint := "sha256:answer"
	jobID42 := int64(91)
	jobID43 := int64(92)
	runID42 := int64(101)
	runID43 := int64(102)
	feedbackID := int64(17)
	wrong := "wrong"
	source := &fakeFeedbackSource{candidates: []FeedbackCandidate{
		{ID: 42, SignalKey: "feedback:17", Source: "feedback", SignalCode: "ao_wrong", IssueKey: "APID-29232", JobID: &jobID42, RunDBID: &runID42, FeedbackID: &feedbackID, FeedbackAction: &wrong, Reason: &reason, AnswerFingerprint: &fingerprint},
		{ID: 43, SignalKey: "runtime:102:notice:final_response_timeout", Source: "runtime", SignalCode: "final_response_timeout", IssueKey: "APID-29265", JobID: &jobID43, RunDBID: &runID43, Reason: &reason, AnswerFingerprint: &fingerprint},
	}}
	poller := NewFeedbackPoller(store, worker, source, time.Minute, 10)

	require.NoError(t, poller.pollOnce(context.Background()))
	ids := store.QueuedIDs()
	require.Len(t, ids, 2)
	first, ok := store.Get(ids[0])
	require.True(t, ok)
	require.Equal(t, JobTriggerFeedback, first.Trigger)
	require.Equal(t, int64(42), first.CandidateID)
	require.Equal(t, int64(17), first.FeedbackID)
	require.Equal(t, int64(91), first.SourceJobID)
	require.Equal(t, "APID-29232", first.IssueKey)
	require.Empty(t, first.ChatID)
	require.Contains(t, first.Request, "provider facts disappeared")

	reloaded, err := OpenStore(path)
	require.NoError(t, err)
	cursor, initialized := reloaded.CandidateCursor()
	require.True(t, initialized)
	require.Equal(t, int64(43), cursor)
	reloadedWorker := NewWorker(reloaded, blockingRunner{}, &recordingNotifier{}, time.Hour, time.Hour, "oc_notify")
	reloadedPoller := NewFeedbackPoller(reloaded, reloadedWorker, source, time.Minute, 10)
	require.NoError(t, reloadedPoller.pollOnce(context.Background()))
	require.Len(t, reloaded.QueuedIDs(), 2)
}
