package agent

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	storeagent "github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

type reviewContextHandlerStore struct {
	listTaskErr       error
	ensureTaskErr     error
	listThreadErr     error
	ensureThreadErr   error
	createErrs        []error
	contexts          []storeagent.ReviewContext
	threadContexts    []storeagent.ReviewContext
	createCalls       []storeagent.UpsertReviewContextParams
	listTaskCalls     []string
	ensureTaskCalls   []string
	listThreadCalls   []string
	ensureThreadCalls []string
}

func (s *reviewContextHandlerStore) CreateReviewContextForAgent(_ context.Context, _ storeagent.AuthenticatedAgent, params storeagent.UpsertReviewContextParams) (storeagent.ReviewContext, error) {
	s.createCalls = append(s.createCalls, params)
	index := len(s.createCalls) - 1
	if index < len(s.createErrs) && s.createErrs[index] != nil {
		return storeagent.ReviewContext{}, s.createErrs[index]
	}
	if index < len(s.contexts) {
		return s.contexts[index], nil
	}
	return storeagent.ReviewContext{
		ID:            "rcx_handler_context",
		WorkspaceID:   params.WorkspaceID,
		TaskScopeID:   params.TaskScopeID,
		ThreadID:      params.ThreadID,
		SourceAgentID: params.SourceAgentID,
		MachineID:     params.MachineID,
		RootPath:      params.RootPath,
		BaseRef:       params.BaseRef,
		HeadRef:       params.HeadRef,
		DisplayName:   params.DisplayName,
		ProjectID:     "prj_handler",
		CreatedAt:     time.Unix(1, 0).UTC(),
	}, nil
}

func (s *reviewContextHandlerStore) GetReviewContext(context.Context, string, string) (*storeagent.ReviewContext, error) {
	return nil, storeerr.ErrNotFound
}

func (s *reviewContextHandlerStore) EnsureAgentThreadAccess(_ context.Context, _ string, _ string, threadID string) error {
	s.ensureThreadCalls = append(s.ensureThreadCalls, threadID)
	return s.ensureThreadErr
}

func (s *reviewContextHandlerStore) EnsureAgentTaskAccess(_ context.Context, _ storeagent.AuthenticatedAgent, taskScopeID string) error {
	s.ensureTaskCalls = append(s.ensureTaskCalls, taskScopeID)
	return s.ensureTaskErr
}

func (s *reviewContextHandlerStore) ListReviewContextsForThreadForAgent(_ context.Context, _ string, threadID string) ([]storeagent.ReviewContext, error) {
	s.listThreadCalls = append(s.listThreadCalls, threadID)
	if s.listThreadErr != nil {
		return nil, s.listThreadErr
	}
	return s.threadContexts, nil
}

func (s *reviewContextHandlerStore) ListReviewContextsForThreadForAgentPage(_ context.Context, _ string, _ string, _ storeagent.ReviewContextListOptions) (storeagent.ReviewContextPage, error) {
	if s.listThreadErr != nil {
		return storeagent.ReviewContextPage{}, s.listThreadErr
	}
	return storeagent.ReviewContextPage{Contexts: s.threadContexts}, nil
}

func (s *reviewContextHandlerStore) ListReviewContextsForTaskForAgent(_ context.Context, _ storeagent.AuthenticatedAgent, taskScopeID string) ([]storeagent.ReviewContext, error) {
	s.listTaskCalls = append(s.listTaskCalls, taskScopeID)
	if s.listTaskErr != nil {
		return nil, s.listTaskErr
	}
	return s.contexts, nil
}

func (s *reviewContextHandlerStore) DeleteReviewContextForAgent(context.Context, storeagent.AuthenticatedAgent, string) error {
	return nil
}

type reviewContextHandlerValidator struct {
	err      error
	machines []string
	roots    []string
	baseRefs []string
	headRefs []string
}

func (v *reviewContextHandlerValidator) ValidateAgentOutputTarget(context.Context, string, string, string, string) error {
	return nil
}

func (v *reviewContextHandlerValidator) GetAgentReviewSummary(_ context.Context, machineID string, _ string, rootPath string, baseRef string, headRef string, _ string) (storeagent.AgentReviewSummary, error) {
	v.machines = append(v.machines, machineID)
	v.roots = append(v.roots, rootPath)
	v.baseRefs = append(v.baseRefs, baseRef)
	v.headRefs = append(v.headRefs, headRef)
	if v.err != nil {
		return storeagent.AgentReviewSummary{}, v.err
	}
	return storeagent.AgentReviewSummary{Status: "ready"}, nil
}

func reviewContextHandlerRouter(store ReviewContextStore, validator ReviewContextValidator) http.Handler {
	router := gin.New()
	RegisterRoutes(router, Dependencies{
		Auth:                   fakeAgentAuthenticator{agent: reviewContextHandlerAgent()},
		ReviewContextStore:     store,
		ReviewContextValidator: validator,
	})
	return router
}

func reviewContextHandlerAgent() storeagent.AuthenticatedAgent {
	return storeagent.AuthenticatedAgent{
		AgentID:     "agt_0000000000test",
		WorkspaceID: "wsp_test",
		MemberID:    "mem_agent",
		MachineID:   "mac_agent",
	}
}

func reviewContextHandlerRequest(t *testing.T, router http.Handler, method string, path string, body string) *httptest.ResponseRecorder {
	t.Helper()
	request := httptest.NewRequest(method, path, strings.NewReader(body))
	request.Header.Set("Authorization", "Bearer test-token")
	request.Header.Set("Content-Type", "application/json")
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	return response
}

func TestReviewContextHandlerGetTaskInvokesAuthorizedAgentPath(t *testing.T) {
	store := &reviewContextHandlerStore{contexts: []storeagent.ReviewContext{{
		ID:            "rcx_task_authorized",
		WorkspaceID:   "wsp_test",
		TaskScopeID:   "tsk_authorized",
		SourceAgentID: "agt_0000000000test",
		MachineID:     "mac_agent",
		RootPath:      "/repo/authorized",
		DisplayName:   "Authorized review",
		CreatedAt:     time.Unix(1, 0).UTC(),
	}}}
	response := reviewContextHandlerRequest(t, reviewContextHandlerRouter(store, &reviewContextHandlerValidator{}), http.MethodGet,
		"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/tasks/tsk_authorized/review-contexts", "")

	require.Equal(t, http.StatusOK, response.Code)
	// GetTask invokes the store's authorized list boundary; the production
	// Store method performs EnsureAgentTaskAccess before querying rows.
	require.Empty(t, store.ensureTaskCalls)
	require.Equal(t, []string{"tsk_authorized"}, store.listTaskCalls)
	var payload struct {
		Contexts []map[string]any `json:"contexts"`
	}
	require.NoError(t, json.Unmarshal(response.Body.Bytes(), &payload))
	require.Len(t, payload.Contexts, 1)
	require.Equal(t, "rcx_task_authorized", payload.Contexts[0]["id"])
}

func TestReviewContextHandlerGetTaskNonEnumeratesInvalidInactiveCrossWorkspaceAndUnauthorizedTasks(t *testing.T) {
	for _, testCase := range []struct {
		name   string
		taskID string
		err    error
	}{
		{name: "inactive", taskID: "tsk_inactive", err: storeerr.ErrNotFound},
		{name: "cross workspace", taskID: "tsk_foreign", err: storeerr.ErrNotFound},
		{name: "unauthorized", taskID: "tsk_private", err: storeerr.ErrNotFound},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			store := &reviewContextHandlerStore{listTaskErr: testCase.err}
			response := reviewContextHandlerRequest(t, reviewContextHandlerRouter(store, &reviewContextHandlerValidator{}), http.MethodGet,
				"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/tasks/"+testCase.taskID+"/review-contexts", "")
			require.Equal(t, http.StatusNotFound, response.Code)
			body := response.Body.String()
			require.NotContains(t, body, testCase.taskID)
			require.NotContains(t, body, "rcx_foreign")
			require.NotContains(t, body, "/repo/private")
			require.NotContains(t, body, "mac_private")
		})
	}
}

func TestReviewContextHandlerRejectsBareTaskRouteBeforeStore(t *testing.T) {
	store := &reviewContextHandlerStore{}
	response := reviewContextHandlerRequest(t, reviewContextHandlerRouter(store, &reviewContextHandlerValidator{}), http.MethodGet,
		"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/tasks/task-raw/review-contexts", "")
	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Empty(t, store.listTaskCalls)
	require.NotContains(t, response.Body.String(), "task-raw")
}

func TestReviewContextHandlerUpdateTaskInvokesValidationAndMapsMismatchAndConflict(t *testing.T) {
	validator := &reviewContextHandlerValidator{}
	store := &reviewContextHandlerStore{contexts: []storeagent.ReviewContext{{
		ID:            "rcx_task_update",
		WorkspaceID:   "wsp_test",
		TaskScopeID:   "tsk_update",
		SourceAgentID: "agt_0000000000test",
		MachineID:     "mac_agent",
		RootPath:      "/repo/updated",
		BaseRef:       "main",
		HeadRef:       "feature",
		DisplayName:   "Updated review",
		CreatedAt:     time.Unix(1, 0).UTC(),
	}}, createErrs: []error{nil, storeerr.ErrConflict}}
	router := reviewContextHandlerRouter(store, validator)

	mismatch := reviewContextHandlerRequest(t, router, http.MethodPut,
		"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/tasks/tsk_update/review-context",
		`{"kind":"git_repo","rootPath":"/repo/updated","baseRef":"main","headRef":"feature","taskScope":{"kind":"task","scopeId":"tsk_other"}}`)
	require.Equal(t, http.StatusBadRequest, mismatch.Code)
	require.Empty(t, store.createCalls)
	require.NotContains(t, mismatch.Body.String(), "/repo/updated")

	first := reviewContextHandlerRequest(t, router, http.MethodPut,
		"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/tasks/tsk_update/review-context",
		`{"kind":"git_repo","rootPath":"/repo/updated","baseRef":"main","headRef":"feature","displayName":"Updated review"}`)
	require.Equal(t, http.StatusOK, first.Code)
	require.Len(t, store.createCalls, 1)
	require.Equal(t, "tsk_update", store.createCalls[0].TaskScopeID)
	require.Equal(t, "/repo/updated", store.createCalls[0].RootPath)
	require.Equal(t, "main", store.createCalls[0].BaseRef)
	require.Equal(t, "feature", store.createCalls[0].HeadRef)
	require.Equal(t, "mac_agent", validator.machines[0])

	conflict := reviewContextHandlerRequest(t, router, http.MethodPut,
		"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/tasks/tsk_update/review-context",
		`{"kind":"git_repo","rootPath":"/repo/updated-two","baseRef":"main","headRef":"feature","displayName":"Conflict"}`)
	require.Equal(t, http.StatusConflict, conflict.Code)
	require.NotContains(t, conflict.Body.String(), "/repo/updated-two")
	require.NotContains(t, conflict.Body.String(), "mac_agent")
	require.NotContains(t, conflict.Body.String(), "rcx_foreign")
}

func TestReviewContextHandlerUpdateTaskRejectsRepositoryAndRefValidationErrors(t *testing.T) {
	for _, testCase := range []struct {
		name string
		root string
		err  error
	}{
		{name: "non repository root", root: "/not-a-repository", err: errors.New("target is not a git repository")},
		{name: "invalid ref", root: "/repo/valid", err: errors.New("unknown revision: no-such-ref")},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			store := &reviewContextHandlerStore{}
			validator := &reviewContextHandlerValidator{err: testCase.err}
			response := reviewContextHandlerRequest(t, reviewContextHandlerRouter(store, validator), http.MethodPut,
				"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/tasks/tsk_validation/review-context",
				`{"kind":"git_repo","rootPath":"`+testCase.root+`","baseRef":"main","headRef":"feature"}`)
			require.Equal(t, http.StatusBadRequest, response.Code)
			require.Empty(t, store.createCalls)
			require.NotContains(t, response.Body.String(), testCase.root)
			require.NotContains(t, response.Body.String(), "mac_agent")
		})
	}
}

func TestReviewContextHandlerReplyThreadGetAndUpdateRegression(t *testing.T) {
	store := &reviewContextHandlerStore{
		threadContexts: []storeagent.ReviewContext{{
			ID:            "rcx_thread_existing",
			WorkspaceID:   "wsp_test",
			ThreadID:      "chn_thread",
			SourceAgentID: "agt_0000000000test",
			MachineID:     "mac_agent",
			RootPath:      "/repo/thread",
			DisplayName:   "Thread review",
			CreatedAt:     time.Unix(1, 0).UTC(),
		}},
		contexts: []storeagent.ReviewContext{{
			ID:            "rcx_thread_updated",
			WorkspaceID:   "wsp_test",
			ThreadID:      "chn_thread",
			SourceAgentID: "agt_0000000000test",
			MachineID:     "mac_agent",
			RootPath:      "/repo/thread-updated",
			DisplayName:   "Thread updated",
			CreatedAt:     time.Unix(1, 0).UTC(),
		}},
	}
	router := reviewContextHandlerRouter(store, &reviewContextHandlerValidator{})
	get := reviewContextHandlerRequest(t, router, http.MethodGet,
		"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/threads/chn_thread/review-context", "")
	require.Equal(t, http.StatusOK, get.Code)
	require.Equal(t, []string{"chn_thread"}, store.ensureThreadCalls)
	require.Equal(t, []string{"chn_thread"}, store.listThreadCalls)
	require.Contains(t, get.Body.String(), "rcx_thread_existing")

	update := reviewContextHandlerRequest(t, router, http.MethodPut,
		"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/threads/chn_thread/review-context",
		`{"kind":"git_repo","rootPath":"/repo/thread-updated","baseRef":"main","headRef":"feature","displayName":"Thread updated"}`)
	require.Equal(t, http.StatusOK, update.Code)
	require.Len(t, store.createCalls, 1)
	require.Equal(t, "chn_thread", store.createCalls[0].ThreadID)
	require.Empty(t, store.createCalls[0].TaskScopeID)
	require.Contains(t, update.Body.String(), "rcx_thread_updated")
}

var _ ReviewContextStore = (*reviewContextHandlerStore)(nil)
var _ ReviewContextValidator = (*reviewContextHandlerValidator)(nil)
