package admin

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

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

	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

func TestRegisterRoutesOwnsAdminRoutes(t *testing.T) {
	t.Parallel()

	router := gin.New()
	RegisterRoutes(router, Dependencies{})

	counts := adminRouteCounts(router)
	for _, route := range adminRouteKeys() {
		require.Equal(t, 1, counts[route], route)
	}
}

func TestAdminRoutesRequirePlatformOperator(t *testing.T) {
	t.Parallel()

	for _, test := range []struct {
		name       string
		user       identity.User
		wantStatus int
	}{
		{name: "rejects non admin", user: identity.User{ID: "usr_member"}, wantStatus: http.StatusForbidden},
		{name: "allows admin", user: identity.User{ID: "usr_admin", IsAdmin: true}, wantStatus: http.StatusOK},
	} {
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()

			router := gin.New()
			RegisterRoutes(router, Dependencies{
				Store: &fakeAdminStore{
					authenticated: identity.AuthenticatedUser{User: test.user},
				},
			})

			recorder := httptest.NewRecorder()
			request := httptest.NewRequest(http.MethodGet, "/api/v1/admin/overview", nil)
			request.Header.Set("Authorization", "Bearer session-token")
			router.ServeHTTP(recorder, request)

			require.Equal(t, test.wantStatus, recorder.Code)
		})
	}
}

func TestRegisterInternalRoutesRequireServiceCredential(t *testing.T) {
	t.Parallel()

	router := gin.New()
	RegisterInternalRoutes(router, Dependencies{
		InternalAdminToken:      "secret",
		WorkspacePlacementAdmin: &fakeWorkspacePlacementAdminStore{},
	})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/internal/admin/workspace-groups", nil)
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusUnauthorized, recorder.Code)

	recorder = httptest.NewRecorder()
	request = httptest.NewRequest(http.MethodGet, "/internal/admin/workspace-groups", nil)
	request.Header.Set("Authorization", "Bearer secret")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code)
	require.JSONEq(t, `{"groups":[]}`, recorder.Body.String())
}

func TestCreateWorkspaceGroupMatchesAgentTeamOpsContract(t *testing.T) {
	t.Parallel()

	store := &fakeWorkspacePlacementAdminStore{}
	router := gin.New()
	RegisterInternalRoutes(router, Dependencies{
		InternalAdminToken:      "secret",
		WorkspacePlacementAdmin: store,
	})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/internal/admin/workspace-groups", jsonBody(t, map[string]any{
		"workspaceGroupId": "workspace-large-a",
		"displayName":      "Large A",
	}))
	request.Header.Set("Authorization", "Bearer secret")
	request.Header.Set("Idempotency-Key", "idem-create")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusCreated, recorder.Code, recorder.Body.String())
	require.Equal(t, "workspace-large-a", store.createRequest.WorkspaceGroupID)
	require.JSONEq(t, `{"group":{"workspaceGroupId":"workspace-large-a","displayName":"Large A","state":"active","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}}`, recorder.Body.String())
}

func TestStartWorkspaceMigrationUsesOperatorHeaderAndIdempotency(t *testing.T) {
	t.Parallel()

	store := &fakeWorkspacePlacementAdminStore{}
	router := gin.New()
	RegisterInternalRoutes(router, Dependencies{
		InternalAdminToken:      "secret",
		WorkspacePlacementAdmin: store,
	})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/internal/admin/workspace-migrations", jsonBody(t, map[string]any{
		"workspaceId":   "wsp_1",
		"sourceGroupId": "workspace-default",
		"targetGroupId": "workspace-large-a",
		"reason":        "move large workspace",
	}))
	request.Header.Set("Authorization", "Bearer secret")
	request.Header.Set("Idempotency-Key", "idem-move")
	request.Header.Set("X-AgentTeam-Ops-Operator-Id", "opsusr_1")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusCreated, recorder.Code, recorder.Body.String())
	require.Equal(t, "opsusr_1", store.startRequest.RequestedBy)
	require.Equal(t, "idem-move", store.startRequest.IdempotencyKey)
	require.JSONEq(t, `{"migration":{"migrationId":"wpm_1","workspaceId":"wsp_1","sourceGroupId":"workspace-default","targetGroupId":"workspace-large-a","state":"draining","placementEpochStart":0,"placementEpochCurrent":0,"requestedBy":"opsusr_1","reason":"move large workspace","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}}`, recorder.Body.String())
}

func TestGetActiveWorkspaceMigrationMatchesAgentTeamOpsContract(t *testing.T) {
	t.Parallel()

	store := &fakeWorkspacePlacementAdminStore{
		activeMigration: &workspace.WorkspacePlacementMigration{
			MigrationID:   "wpm_1",
			WorkspaceID:   "wsp_1",
			SourceGroupID: "workspace-default",
			TargetGroupID: "workspace-large-a",
			State:         workspace.PlacementMigrationStateMoving,
			RequestedBy:   "opsusr_1",
			Reason:        "move large workspace",
		},
	}
	router := gin.New()
	RegisterInternalRoutes(router, Dependencies{
		InternalAdminToken:      "secret",
		WorkspacePlacementAdmin: store,
	})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/internal/admin/workspace-migrations/active?workspaceId=wsp_1", nil)
	request.Header.Set("Authorization", "Bearer secret")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	require.Equal(t, "wsp_1", store.activeWorkspaceID)
	require.JSONEq(t, `{"migration":{"migrationId":"wpm_1","workspaceId":"wsp_1","sourceGroupId":"workspace-default","targetGroupId":"workspace-large-a","state":"moving","placementEpochStart":0,"placementEpochCurrent":0,"requestedBy":"opsusr_1","reason":"move large workspace","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z"}}`, recorder.Body.String())
}

func TestGetActiveWorkspaceMigrationReturnsNullWhenIdle(t *testing.T) {
	t.Parallel()

	router := gin.New()
	RegisterInternalRoutes(router, Dependencies{
		InternalAdminToken:      "secret",
		WorkspacePlacementAdmin: &fakeWorkspacePlacementAdminStore{},
	})

	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodGet, "/internal/admin/workspace-migrations/active?workspaceId=wsp_1", nil)
	request.Header.Set("Authorization", "Bearer secret")
	router.ServeHTTP(recorder, request)

	require.Equal(t, http.StatusOK, recorder.Code, recorder.Body.String())
	require.JSONEq(t, `{"migration":null}`, recorder.Body.String())
}

func TestRegisterInternalRoutesOwnsWorkspacePlacementRoutes(t *testing.T) {
	t.Parallel()

	router := gin.New()
	RegisterInternalRoutes(router, Dependencies{})

	counts := adminRouteCounts(router)
	for _, route := range internalAdminRouteKeys() {
		require.Equal(t, 1, counts[route], route)
	}
}

func adminRouteKeys() []string {
	return []string{
		"GET /api/v1/admin/overview",
		"GET /api/v1/admin/workspaces",
		"GET /api/v1/admin/users",
		"GET /api/v1/admin/registration-allowlist",
		"POST /api/v1/admin/registration-allowlist",
		"POST /api/v1/admin/registration-allowlist/:entryId/disable",
		"POST /api/v1/admin/orgs/:orgId/hosted-credit-grants",
		"GET /api/v1/admin/runtime/db-pool",
		"PATCH /api/v1/admin/runtime/db-pool",
	}
}

func internalAdminRouteKeys() []string {
	return []string{
		"GET /internal/admin/workspace-groups",
		"POST /internal/admin/workspace-groups",
		"POST /internal/admin/workspace-groups/:groupId/disable",
		"GET /internal/admin/workspace-placements",
		"POST /internal/admin/workspace-placements/verify",
		"POST /internal/admin/workspace-migrations",
		"GET /internal/admin/workspace-migrations/active",
		"GET /internal/admin/workspace-migrations/:migrationId",
		"POST /internal/admin/workspace-migrations/:migrationId/move",
		"POST /internal/admin/workspace-migrations/:migrationId/complete",
		"POST /internal/admin/workspace-migrations/:migrationId/rollback",
	}
}

func adminRouteCounts(router *gin.Engine) map[string]int {
	counts := make(map[string]int)
	for _, route := range router.Routes() {
		counts[route.Method+" "+route.Path]++
	}
	return counts
}

type fakeWorkspacePlacementAdminStore struct {
	groups            []workspace.WorkspaceGroup
	activeMigration   *workspace.WorkspacePlacementMigration
	createRequest     workspace.CreateWorkspaceGroupRequest
	startRequest      workspace.StartWorkspacePlacementMigrationRequest
	activeWorkspaceID string
	err               error
}

func (fake *fakeWorkspacePlacementAdminStore) ListWorkspaceGroups(context.Context) ([]workspace.WorkspaceGroup, error) {
	return fake.groups, fake.err
}

func (fake *fakeWorkspacePlacementAdminStore) CreateWorkspaceGroup(_ context.Context, request workspace.CreateWorkspaceGroupRequest) (workspace.WorkspaceGroup, error) {
	fake.createRequest = request
	if fake.err != nil {
		return workspace.WorkspaceGroup{}, fake.err
	}
	return workspace.WorkspaceGroup{
		WorkspaceGroupID: request.WorkspaceGroupID,
		DisplayName:      request.DisplayName,
		State:            workspace.WorkspaceGroupStateActive,
		ResourceProfile:  request.ResourceProfile,
		ServiceRef:       request.ServiceRef,
	}, nil
}

func (fake *fakeWorkspacePlacementAdminStore) DisableWorkspaceGroup(context.Context, string) (workspace.WorkspaceGroup, error) {
	return workspace.WorkspaceGroup{}, firstErr(fake.err)
}

func (fake *fakeWorkspacePlacementAdminStore) ListWorkspacePlacements(context.Context, workspace.WorkspacePlacementListFilter) ([]workspace.WorkspacePlacement, error) {
	return nil, fake.err
}

func (fake *fakeWorkspacePlacementAdminStore) VerifyWorkspacePlacementTopology(context.Context) (workspace.WorkspacePlacementTopologyVerification, error) {
	return workspace.WorkspacePlacementTopologyVerification{}, fake.err
}

func (fake *fakeWorkspacePlacementAdminStore) StartWorkspacePlacementMigration(_ context.Context, request workspace.StartWorkspacePlacementMigrationRequest) (workspace.WorkspacePlacementMigration, error) {
	fake.startRequest = request
	return workspace.WorkspacePlacementMigration{
		MigrationID:   "wpm_1",
		WorkspaceID:   request.WorkspaceID,
		SourceGroupID: request.SourceGroupID,
		TargetGroupID: request.TargetGroupID,
		State:         workspace.PlacementMigrationStateDraining,
		RequestedBy:   request.RequestedBy,
		Reason:        request.Reason,
	}, fake.err
}

func (fake *fakeWorkspacePlacementAdminStore) GetActiveWorkspacePlacementMigration(_ context.Context, workspaceID string) (*workspace.WorkspacePlacementMigration, error) {
	fake.activeWorkspaceID = workspaceID
	return fake.activeMigration, fake.err
}

func (fake *fakeWorkspacePlacementAdminStore) GetWorkspacePlacementMigration(context.Context, string) (workspace.WorkspacePlacementMigration, error) {
	return workspace.WorkspacePlacementMigration{}, fake.err
}

func (fake *fakeWorkspacePlacementAdminStore) MoveWorkspacePlacementMigration(context.Context, string) (workspace.WorkspacePlacementMigration, error) {
	return workspace.WorkspacePlacementMigration{}, fake.err
}

func (fake *fakeWorkspacePlacementAdminStore) CompleteWorkspacePlacementMigration(context.Context, string) (workspace.WorkspacePlacementMigration, error) {
	return workspace.WorkspacePlacementMigration{}, fake.err
}

func (fake *fakeWorkspacePlacementAdminStore) RollbackWorkspacePlacementMigration(context.Context, string) (workspace.WorkspacePlacementMigration, error) {
	return workspace.WorkspacePlacementMigration{}, fake.err
}

func firstErr(err error) error {
	if err != nil {
		return err
	}
	return errors.New("not implemented")
}

func jsonBody(t *testing.T, value any) *bytes.Reader {
	t.Helper()
	payload, err := json.Marshal(value)
	require.NoError(t, err)
	return bytes.NewReader(payload)
}
