package admin

import (
	"context"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

// InternalAdminHandler owns private service-to-service placement admin routes.
type InternalAdminHandler struct {
	store WorkspacePlacementAdminStore
}

func (handler *InternalAdminHandler) ListWorkspaceGroups(ctx *gin.Context) {
	if !handler.requireStore(ctx) {
		return
	}
	groups, err := handler.store.ListWorkspaceGroups(ctx.Request.Context())
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, dto.WorkspaceGroupsResponse{Groups: toWorkspaceGroupDTOs(groups)})
}

func (handler *InternalAdminHandler) CreateWorkspaceGroup(ctx *gin.Context) {
	if !handler.requireStore(ctx) {
		return
	}
	if _, ok := requireIdempotencyKey(ctx); !ok {
		return
	}
	request, err := api.BindJSON[dto.WorkspaceGroupCreateRequest](ctx)
	if err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid request body")
		return
	}
	workspaceGroupID := strings.TrimSpace(request.WorkspaceGroupID)
	if workspaceGroupID == "" {
		workspaceGroupID = request.ID
	}
	group, err := handler.store.CreateWorkspaceGroup(ctx.Request.Context(), workspace.CreateWorkspaceGroupRequest{
		WorkspaceGroupID: workspaceGroupID,
		DisplayName:      request.DisplayName,
		ResourceProfile:  request.ResourceProfile,
		ServiceRef:       request.ServiceRef,
	})
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusCreated, dto.WorkspaceGroupResponse{Group: toWorkspaceGroupDTO(group)})
}

func (handler *InternalAdminHandler) DisableWorkspaceGroup(ctx *gin.Context) {
	if !handler.requireStore(ctx) {
		return
	}
	if _, ok := requireIdempotencyKey(ctx); !ok {
		return
	}
	group, err := handler.store.DisableWorkspaceGroup(ctx.Request.Context(), ctx.Param("groupId"))
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, dto.WorkspaceGroupResponse{Group: toWorkspaceGroupDTO(group)})
}

func (handler *InternalAdminHandler) ListWorkspacePlacements(ctx *gin.Context) {
	if !handler.requireStore(ctx) {
		return
	}
	placements, err := handler.store.ListWorkspacePlacements(ctx.Request.Context(), workspace.WorkspacePlacementListFilter{
		WorkspaceID:      ctx.Query("workspaceId"),
		WorkspaceGroupID: ctx.Query("workspaceGroupId"),
		State:            ctx.Query("state"),
		IncludeInactive:  adminBoolQuery(ctx, "includeInactive"),
	})
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, dto.WorkspacePlacementsResponse{Placements: toWorkspacePlacementDTOs(placements)})
}

func (handler *InternalAdminHandler) VerifyWorkspacePlacements(ctx *gin.Context) {
	if !handler.requireStore(ctx) {
		return
	}
	result, err := handler.store.VerifyWorkspacePlacementTopology(ctx.Request.Context())
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, toWorkspacePlacementVerificationDTO(result))
}

func (handler *InternalAdminHandler) StartWorkspaceMigration(ctx *gin.Context) {
	if !handler.requireStore(ctx) {
		return
	}
	request, err := api.BindJSON[dto.WorkspacePlacementMigrationStartRequest](ctx)
	if err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid request body")
		return
	}
	idempotencyKey, ok := requireIdempotencyKey(ctx)
	if !ok {
		return
	}
	requestedBy := strings.TrimSpace(request.RequestedBy)
	if requestedBy == "" {
		requestedBy = api.InternalAdminOperator(ctx)
	}
	migration, err := handler.store.StartWorkspacePlacementMigration(ctx.Request.Context(), workspace.StartWorkspacePlacementMigrationRequest{
		WorkspaceID:    request.WorkspaceID,
		SourceGroupID:  request.SourceGroupID,
		TargetGroupID:  request.TargetGroupID,
		RequestedBy:    requestedBy,
		Reason:         request.Reason,
		IdempotencyKey: idempotencyKey,
	})
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusCreated, dto.WorkspacePlacementMigrationResponse{Migration: toWorkspacePlacementMigrationDTO(migration)})
}

func (handler *InternalAdminHandler) GetActiveWorkspaceMigration(ctx *gin.Context) {
	if !handler.requireStore(ctx) {
		return
	}
	migration, err := handler.store.GetActiveWorkspacePlacementMigration(ctx.Request.Context(), ctx.Query("workspaceId"))
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	if migration == nil {
		ctx.JSON(http.StatusOK, dto.WorkspaceActiveMigrationResponse{Migration: nil})
		return
	}
	migrationDTO := toWorkspacePlacementMigrationDTO(*migration)
	ctx.JSON(http.StatusOK, dto.WorkspaceActiveMigrationResponse{Migration: &migrationDTO})
}

func (handler *InternalAdminHandler) GetWorkspaceMigration(ctx *gin.Context) {
	handler.writeMigration(ctx, false, func(ctx context.Context, migrationID string) (workspace.WorkspacePlacementMigration, error) {
		return handler.store.GetWorkspacePlacementMigration(ctx, migrationID)
	})
}

func (handler *InternalAdminHandler) MoveWorkspaceMigration(ctx *gin.Context) {
	handler.writeMigration(ctx, true, func(ctx context.Context, migrationID string) (workspace.WorkspacePlacementMigration, error) {
		return handler.store.MoveWorkspacePlacementMigration(ctx, migrationID)
	})
}

func (handler *InternalAdminHandler) CompleteWorkspaceMigration(ctx *gin.Context) {
	handler.writeMigration(ctx, true, func(ctx context.Context, migrationID string) (workspace.WorkspacePlacementMigration, error) {
		return handler.store.CompleteWorkspacePlacementMigration(ctx, migrationID)
	})
}

func (handler *InternalAdminHandler) RollbackWorkspaceMigration(ctx *gin.Context) {
	handler.writeMigration(ctx, true, func(ctx context.Context, migrationID string) (workspace.WorkspacePlacementMigration, error) {
		return handler.store.RollbackWorkspacePlacementMigration(ctx, migrationID)
	})
}

func (handler *InternalAdminHandler) writeMigration(ctx *gin.Context, requireIdempotency bool, fn func(ctx context.Context, migrationID string) (workspace.WorkspacePlacementMigration, error)) {
	if !handler.requireStore(ctx) {
		return
	}
	if requireIdempotency {
		if _, ok := requireIdempotencyKey(ctx); !ok {
			return
		}
	}
	migration, err := fn(ctx.Request.Context(), ctx.Param("migrationId"))
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, dto.WorkspacePlacementMigrationResponse{Migration: toWorkspacePlacementMigrationDTO(migration)})
}

func (handler *InternalAdminHandler) requireStore(ctx *gin.Context) bool {
	if handler == nil || handler.store == nil {
		api.Error(ctx, http.StatusInternalServerError, "Workspace placement admin store is not configured")
		return false
	}
	return true
}

func toWorkspaceGroupDTOs(groups []workspace.WorkspaceGroup) []dto.WorkspaceGroup {
	if groups == nil {
		return []dto.WorkspaceGroup{}
	}
	out := make([]dto.WorkspaceGroup, 0, len(groups))
	for _, group := range groups {
		out = append(out, toWorkspaceGroupDTO(group))
	}
	return out
}

func toWorkspaceGroupDTO(group workspace.WorkspaceGroup) dto.WorkspaceGroup {
	return dto.WorkspaceGroup{
		WorkspaceGroupID: group.WorkspaceGroupID,
		DisplayName:      group.DisplayName,
		State:            group.State,
		ResourceProfile:  group.ResourceProfile,
		ServiceRef:       group.ServiceRef,
		CreatedAt:        group.CreatedAt,
		UpdatedAt:        group.UpdatedAt,
	}
}

func toWorkspacePlacementDTOs(placements []workspace.WorkspacePlacement) []dto.WorkspacePlacement {
	if placements == nil {
		return []dto.WorkspacePlacement{}
	}
	out := make([]dto.WorkspacePlacement, 0, len(placements))
	for _, placement := range placements {
		out = append(out, dto.WorkspacePlacement{
			WorkspaceID:      placement.WorkspaceID,
			WorkspaceGroupID: placement.WorkspaceGroupID,
			State:            placement.State,
			PlacementEpoch:   placement.PlacementEpoch,
			CreatedAt:        placement.CreatedAt,
			UpdatedAt:        placement.UpdatedAt,
		})
	}
	return out
}

func toWorkspacePlacementVerificationDTO(result workspace.WorkspacePlacementTopologyVerification) dto.WorkspacePlacementVerification {
	return dto.WorkspacePlacementVerification{
		TotalWorkspaceCount:         result.TotalWorkspaceCount,
		WorkspaceGroupCount:         result.WorkspaceGroupCount,
		ActiveWorkspaceGroupCount:   result.ActiveWorkspaceGroupCount,
		PlacementCount:              result.PlacementCount,
		MissingPlacementCount:       result.MissingPlacementCount,
		UnknownGroupCount:           result.UnknownGroupPlacementCount,
		UnknownGroupPlacementCount:  result.UnknownGroupPlacementCount,
		InactiveGroupPlacementCount: result.InactiveGroupPlacementCount,
		InactivePlacementCount:      result.InactivePlacementCount,
		MalformedPlacementCount:     result.MalformedPlacementCount,
		StaleMigrationCount:         result.ActiveMigrationCount,
		ActiveMigrationCount:        result.ActiveMigrationCount,
	}
}

func toWorkspacePlacementMigrationDTO(migration workspace.WorkspacePlacementMigration) dto.WorkspacePlacementMigration {
	return dto.WorkspacePlacementMigration{
		MigrationID:           migration.MigrationID,
		WorkspaceID:           migration.WorkspaceID,
		SourceGroupID:         migration.SourceGroupID,
		TargetGroupID:         migration.TargetGroupID,
		State:                 migration.State,
		PlacementEpochStart:   migration.PlacementEpochStart,
		PlacementEpochCurrent: migration.PlacementEpochCurrent,
		RequestedBy:           migration.RequestedBy,
		Reason:                migration.Reason,
		LastError:             migration.LastError,
		CreatedAt:             migration.CreatedAt,
		UpdatedAt:             migration.UpdatedAt,
		CompletedAt:           migration.CompletedAt,
	}
}

func requireIdempotencyKey(ctx *gin.Context) (string, bool) {
	idempotencyKey := strings.TrimSpace(ctx.GetHeader("Idempotency-Key"))
	if idempotencyKey == "" {
		idempotencyKey = strings.TrimSpace(ctx.GetHeader("X-Idempotency-Key"))
	}
	if idempotencyKey == "" {
		api.Error(ctx, http.StatusBadRequest, "Idempotency-Key is required")
		return "", false
	}
	return idempotencyKey, true
}
