package acp_test

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

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/yan1h/agent-coding-workflow/internal/acp"
)

// mountDashboardHandler builds a Handler whose SessionService is backed by the
// in-memory fakeAcpRepo so run-history endpoints can be exercised without a DB.
func mountDashboardHandler(t *testing.T, callerUID uuid.UUID, isAdmin bool, repo *fakeAcpRepo) chi.Router {
	t.Helper()
	enc := testEncryptor(t)
	ss := acp.NewSessionService(repo, nil, nil, nil, nil, nil, nil, nil, nil,
		acp.SessionServiceConfig{}, nil)
	r := chi.NewRouter()
	h := acp.NewHandler(
		nil, // akSvc — not exercised
		ss,
		nil, // sup — not exercised
		repo,
		nil, // permSvc — not exercised
		fakeResolver{uid: callerUID},
		fakeAdminLookup{admin: map[uuid.UUID]bool{callerUID: isAdmin}},
		enc,
		acp.WSConfig{},
	)
	h.Mount(r)
	return r
}

func getJSONArray(t *testing.T, r chi.Router, path, token string) (*httptest.ResponseRecorder, []map[string]any) {
	t.Helper()
	req := httptest.NewRequest("GET", path, nil)
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	rr := httptest.NewRecorder()
	r.ServeHTTP(rr, req)
	var out []map[string]any
	if rr.Body.Len() > 0 {
		_ = json.Unmarshal(rr.Body.Bytes(), &out)
	}
	return rr, out
}

func TestDashboardHandler_Unauthenticated_401(t *testing.T) {
	t.Parallel()
	repo := &fakeAcpRepo{}
	r := mountDashboardHandler(t, uuid.New(), false, repo)
	rr, _ := doJSON(t, r, "GET", "/api/v1/acp/dashboard", "", nil)
	assert.Equal(t, http.StatusUnauthorized, rr.Code)
}

func TestDashboardHandler_OwnerScoped(t *testing.T) {
	t.Parallel()
	uid := uuid.New()
	repo := &fakeAcpRepo{
		rollupResult: acp.SessionRollup{Total: 2, Succeeded: 2, TotalCostUSD: 0.4},
		byDayResult:  []acp.SessionDayRow{{Day: time.Now().UTC(), Total: 2, Succeeded: 2, TotalCostUSD: 0.4}},
	}
	r := mountDashboardHandler(t, uid, false, repo)

	rr, body := doJSON(t, r, "GET", "/api/v1/acp/dashboard", "valid", nil)
	require.Equal(t, http.StatusOK, rr.Code, "body=%s", rr.Body.String())

	rollup := body["rollup"].(map[string]any)
	assert.Equal(t, float64(2), rollup["total"])
	assert.Equal(t, float64(1), rollup["success_rate"])

	// 非 admin → repo filter scoped to caller.
	require.NotNil(t, repo.rollupFilter.UserID)
	assert.Equal(t, uid, *repo.rollupFilter.UserID)
}

func TestDashboardHandler_Admin_NotScoped_WithProjectFilter(t *testing.T) {
	t.Parallel()
	uid := uuid.New()
	pid := uuid.New()
	repo := &fakeAcpRepo{rollupResult: acp.SessionRollup{Total: 5, Succeeded: 4}}
	r := mountDashboardHandler(t, uid, true, repo)

	rr, _ := doJSON(t, r, "GET", "/api/v1/acp/dashboard?project_id="+pid.String(), "valid", nil)
	require.Equal(t, http.StatusOK, rr.Code)

	assert.Nil(t, repo.rollupFilter.UserID) // admin → 全量
	require.NotNil(t, repo.rollupFilter.ProjectID)
	assert.Equal(t, pid, *repo.rollupFilter.ProjectID)
}

func TestDashboardHandler_BadProjectID_400(t *testing.T) {
	t.Parallel()
	repo := &fakeAcpRepo{}
	r := mountDashboardHandler(t, uuid.New(), true, repo)
	rr, _ := doJSON(t, r, "GET", "/api/v1/acp/dashboard?project_id=not-a-uuid", "valid", nil)
	assert.Equal(t, http.StatusBadRequest, rr.Code)
}

func TestTimelineHandler_NonOwner_NotFound(t *testing.T) {
	t.Parallel()
	sid := uuid.New()
	repo := &fakeAcpRepo{}
	repo.sessions = []*acp.Session{{ID: sid, UserID: uuid.New()}}
	r := mountDashboardHandler(t, uuid.New(), false, repo)

	rr, _ := doJSON(t, r, "GET", "/api/v1/acp/sessions/"+sid.String()+"/timeline", "valid", nil)
	assert.Equal(t, http.StatusNotFound, rr.Code)
}

func TestTimelineHandler_Owner_OK(t *testing.T) {
	t.Parallel()
	uid := uuid.New()
	sid := uuid.New()
	repo := &fakeAcpRepo{
		timelineResult: []acp.SessionTimelineBucket{
			{Direction: "out", RPCKind: "request", Method: "session/prompt", EventCount: 3},
		},
	}
	repo.sessions = []*acp.Session{{ID: sid, UserID: uid}}
	r := mountDashboardHandler(t, uid, false, repo)

	rr, arr := getJSONArray(t, r, "/api/v1/acp/sessions/"+sid.String()+"/timeline", "valid")
	require.Equal(t, http.StatusOK, rr.Code, "body=%s", rr.Body.String())
	require.Len(t, arr, 1)
	assert.Equal(t, "session/prompt", arr[0]["method"])
	assert.Equal(t, "request", arr[0]["kind"])
	assert.Equal(t, sid, repo.timelineSID)
}
