// Package roles owns MeshX Role API handlers.
package roles

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

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
	roleservice "github.com/tidbcloud/AgentTeam/internal/service/roles"
)

const maxPerPage = 100

// Service is the Role service surface required by the HTTP handler.
type Service interface {
	Capabilities(ctx context.Context, workspaceID, subject string) (roleservice.Capabilities, error)
	ListRoles(ctx context.Context, workspaceID, subject, view, query string, page, perPage int) (roleservice.Page, error)
	ListPresetRoles(ctx context.Context, workspaceID, subject, locale string) ([]roleservice.Role, error)
	GetRole(ctx context.Context, workspaceID, subject, id string) (roleservice.Role, error)
	GetRoleVersion(ctx context.Context, workspaceID, subject, id string, versionID string) (roleservice.Role, error)
	ListRoleVersions(ctx context.Context, workspaceID, subject, roleID string, page, perPage int) (roleservice.RoleVersionPage, error)
	RollbackRoleVersion(ctx context.Context, workspaceID, subject, roleID, expectedRoleVersionID, targetRoleVersionID string) (roleservice.Role, error)
	GetPresetRole(ctx context.Context, workspaceID, subject, presetID, locale string) (roleservice.Role, error)
	CreateRole(ctx context.Context, workspaceID, subject string, input roleservice.Mutation) (roleservice.Role, error)
	UpdateRole(ctx context.Context, workspaceID, subject, id string, input roleservice.Mutation) (roleservice.Role, error)
	UpdateRoleVisibility(ctx context.Context, workspaceID, subject, id, visibility string) (roleservice.Role, error)
	DeleteRole(ctx context.Context, workspaceID, subject, id string) error
	CreateRoleFromAgent(ctx context.Context, workspaceID, subject string, input roleservice.AgentDerivedMutation) (roleservice.Role, error)
	UpdateRoleFromAgent(ctx context.Context, workspaceID, subject, id string, input roleservice.AgentDerivedMutation) (roleservice.Role, error)
	AddPresetToMyRoles(ctx context.Context, workspaceID, subject string, input roleservice.AddPresetRequest) (roleservice.Role, error)
	PlanRoleRealizationImpact(ctx context.Context, workspaceID, subject, id string, input roleservice.RealizationRequest) (roleservice.RealizationImpact, error)
	PlanPresetRoleRealizationImpact(ctx context.Context, workspaceID, subject string, input roleservice.AddPresetRequest, realization roleservice.RealizationRequest) (roleservice.RealizationImpact, error)
	RealizeRoleForAgent(ctx context.Context, workspaceID, subject, id string, input roleservice.RealizationRequest) (roleservice.RealizationResult, error)
	RealizePresetRoleForAgent(ctx context.Context, workspaceID, subject string, input roleservice.AddPresetRequest, realization roleservice.RealizationRequest) (roleservice.Role, roleservice.RealizationResult, error)
}

// RolesHandler serves MeshX Role control-plane endpoints.
type RolesHandler struct{ service Service }

type RouteMiddleware func(http.Handler) http.Handler

type routeRegistrar interface {
	Handle(pattern string, handler http.Handler)
}

type DisabledHandler struct{}

func (DisabledHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r != nil && r.Method == http.MethodGet && strings.TrimRight(r.URL.Path, "/") == "/api/v1/roles/capabilities" {
		meshxapi.WriteResponse(w, meshxapi.OK(dto.RoleCapabilities{
			Enabled:        false,
			DisabledReason: "Roles API is not configured",
		}))
		return
	}
	meshxapi.WriteError(w, meshxapi.NewHTTPError(http.StatusServiceUnavailable, "Roles API is not configured"))
}

func NewRolesHandler(service Service) *RolesHandler { return &RolesHandler{service: service} }

func RegisterRoutes(router routeRegistrar, handler *RolesHandler, middleware RouteMiddleware) {
	if router == nil {
		return
	}
	if handler == nil {
		disabled := DisabledHandler{}
		registerRoleRoute(router, "/api/v1/roles", disabled, middleware)
		registerRoleRoute(router, "/api/v1/roles/", disabled, middleware)
		return
	}
	registerRoleRoute(router, "GET /api/v1/roles/capabilities", meshxapi.HandleAuth(handler.Capabilities, roleErrorHandling()), middleware)
	registerRoleRoute(router, "GET /api/v1/roles/presets", meshxapi.HandleAuth(handler.ListPresets, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/internal/roles/presets:realization-impact", meshxapi.HandleAuthJSON[dto.PresetRoleRealizationRequest](handler.PresetRealizationImpact, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/roles/presets:realize", meshxapi.HandleAuthJSON[dto.PresetRoleRealizationRequest](handler.RealizePreset, roleErrorHandling()), middleware)
	registerRoleRoute(router, "GET /api/v1/roles/presets/{preset_id}", meshxapi.HandleAuth(handler.GetPreset, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/roles/presets/{preset_id}/my-roles", meshxapi.HandleAuthJSON[dto.AddPresetRoleRequest](handler.AddPreset, roleErrorHandling()), middleware)
	registerRoleRoute(router, "GET /api/v1/roles", meshxapi.HandleAuth(handler.List, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/roles", meshxapi.HandleAuthJSON[dto.RoleMutationRequest](handler.Create, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/roles:from-agent", meshxapi.HandleAuthJSON[dto.AgentDerivedRoleMutationRequest](handler.CreateFromAgent, roleErrorHandling()), middleware)
	registerRoleRoute(router, "GET /api/v1/roles/{role_id}/history/versions", meshxapi.HandleAuth(handler.ListVersions, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/roles/{role_id}/history/rollback", meshxapi.HandleAuthJSON[dto.RoleVersionRollbackRequest](handler.RollbackVersion, roleErrorHandling()), middleware)
	registerRoleRoute(router, "GET /api/v1/roles/{role_id}", meshxapi.HandleAuth(handler.Get, roleErrorHandling()), middleware)
	registerRoleRoute(router, "PATCH /api/v1/roles/{role_id}/visibility", meshxapi.HandleAuthJSON[dto.RoleVisibilityRequest](handler.UpdateVisibility, roleErrorHandling()), middleware)
	registerRoleRoute(router, "PATCH /api/v1/roles/{role_id}", meshxapi.HandleAuthJSON[dto.RoleMutationRequest](handler.Update, roleErrorHandling()), middleware)
	registerRoleRoute(router, "DELETE /api/v1/roles/{role_id}", meshxapi.HandleAuth(handler.Delete, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/roles/{role_id}/from-agent", meshxapi.HandleAuthJSON[dto.AgentDerivedRoleMutationRequest](handler.UpdateFromAgent, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/internal/roles/{role_id}/realization-impact", meshxapi.HandleAuthJSON[dto.RoleRealizationRequest](handler.RealizationImpact, roleErrorHandling()), middleware)
	registerRoleRoute(router, "POST /api/v1/roles/{role_id}/realize", meshxapi.HandleAuthJSON[dto.RoleRealizationRequest](handler.Realize, roleErrorHandling()), middleware)
}

// RegisterOperationRoutes mounts the three hidden Core-to-MeshX Role
// operation endpoints only when their transport was explicitly composed.
func RegisterOperationRoutes(
	router routeRegistrar,
	handler http.Handler,
	middleware RouteMiddleware,
) {
	if router == nil || handler == nil {
		return
	}
	for _, path := range []string{
		RoleOperationPreflightPath,
		RoleOperationExecutePath,
		RoleOperationRecoveryPath,
	} {
		registerRoleRoute(router, "POST "+path, handler, middleware)
	}
}

func registerRoleRoute(router routeRegistrar, pattern string, handler http.Handler, middleware RouteMiddleware) {
	if middleware != nil {
		handler = middleware(handler)
	}
	router.Handle(pattern, handler)
}

func (h *RolesHandler) ensureService() error {
	if h == nil || h.service == nil {
		return meshxapi.NewHTTPError(http.StatusServiceUnavailable, "Roles API is not configured")
	}
	return nil
}

func (h *RolesHandler) Capabilities(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	capabilities, err := h.service.Capabilities(r.Context(), r.WorkspaceID(), r.Subject())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRoleCapabilities(capabilities)), nil
}

func (h *RolesHandler) List(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	roles, err := h.service.ListRoles(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.URL.Query().Get("view"), r.Request.URL.Query().Get("q"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRoleListResponse(roles)), nil
}

func (h *RolesHandler) ListPresets(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	items, err := h.service.ListPresetRoles(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.URL.Query().Get("locale"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(dto.RoleListResponse{Items: converter.ToRoles(items), Page: 1, PerPage: len(items), Total: int64(len(items)), HasMore: false}), nil
}

func (h *RolesHandler) Get(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	versionID := strings.TrimSpace(r.Request.URL.Query().Get("versionId"))
	var (
		role roleservice.Role
		err  error
	)
	if versionID != "" {
		role, err = h.service.GetRoleVersion(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"), versionID)
	} else {
		role, err = h.service.GetRole(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"))
	}
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRole(role)), nil
}

// ListVersions handles GET /api/v1/roles/{role_id}/history/versions.
// It returns one bounded page of immutable native Role version summaries.
func (h *RolesHandler) ListVersions(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	result, err := h.service.ListRoleVersions(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRoleVersionListResponse(result)), nil
}

// RollbackVersion handles POST /api/v1/roles/{role_id}/history/rollback.
// It republishes one exact historical package behind an active-version fence.
func (h *RolesHandler) RollbackVersion(r meshxapi.AuthenticatedJSON[dto.RoleVersionRollbackRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	role, err := h.service.RollbackRoleVersion(
		r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"),
		r.Body.ExpectedRoleVersionID, r.Body.TargetRoleVersionID,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRole(role)), nil
}

func (h *RolesHandler) GetPreset(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	role, err := h.service.GetPresetRole(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("preset_id"), r.Request.URL.Query().Get("locale"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRole(role)), nil
}

func (h *RolesHandler) Create(r meshxapi.AuthenticatedJSON[dto.RoleMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	role, err := h.service.CreateRole(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToRole(role)), nil
}

func (h *RolesHandler) Update(r meshxapi.AuthenticatedJSON[dto.RoleMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	role, err := h.service.UpdateRole(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRole(role)), nil
}

func (h *RolesHandler) UpdateVisibility(r meshxapi.AuthenticatedJSON[dto.RoleVisibilityRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	role, err := h.service.UpdateRoleVisibility(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"), r.Body.Visibility)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRole(role)), nil
}

func (h *RolesHandler) Delete(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	if err := h.service.DeleteRole(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id")); err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.NoContent(), nil
}

func (h *RolesHandler) CreateFromAgent(r meshxapi.AuthenticatedJSON[dto.AgentDerivedRoleMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	role, err := h.service.CreateRoleFromAgent(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToRole(role)), nil
}

func (h *RolesHandler) UpdateFromAgent(r meshxapi.AuthenticatedJSON[dto.AgentDerivedRoleMutationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	role, err := h.service.UpdateRoleFromAgent(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"), r.Body.Input())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRole(role)), nil
}

func (h *RolesHandler) AddPreset(r meshxapi.AuthenticatedJSON[dto.AddPresetRoleRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	input := r.Body.Input()
	if strings.TrimSpace(input.PresetID) == "" {
		input.PresetID = r.Request.PathValue("preset_id")
	}
	role, err := h.service.AddPresetToMyRoles(r.Context(), r.WorkspaceID(), r.Subject(), input)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToRole(role)), nil
}

func (h *RolesHandler) Realize(r meshxapi.AuthenticatedJSON[dto.RoleRealizationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	input := r.Body.Input()
	input.Actor = repos.ResourceRepositoryActorFromAgentTeam(r.Identity)
	result, err := h.service.RealizeRoleForAgent(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"), input)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRoleRealizationResponse(result)), nil
}

func (h *RolesHandler) RealizationImpact(r meshxapi.AuthenticatedJSON[dto.RoleRealizationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	input := r.Body.Input()
	impact, err := h.service.PlanRoleRealizationImpact(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("role_id"), input)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRoleRealizationImpactResponse(impact)), nil
}

func (h *RolesHandler) RealizePreset(r meshxapi.AuthenticatedJSON[dto.PresetRoleRealizationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	realization := r.Body.RealizationInput()
	realization.Actor = repos.ResourceRepositoryActorFromAgentTeam(r.Identity)
	role, result, err := h.service.RealizePresetRoleForAgent(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.PresetInput(), realization)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(dto.PresetRoleRealizationResponse{Role: converter.ToRole(role), Realization: converter.ToRoleRealizationResponse(result)}), nil
}

func (h *RolesHandler) PresetRealizationImpact(r meshxapi.AuthenticatedJSON[dto.PresetRoleRealizationRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	impact, err := h.service.PlanPresetRoleRealizationImpact(r.Context(), r.WorkspaceID(), r.Subject(), r.Body.PresetInput(), r.Body.RealizationInput())
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToRoleRealizationImpactResponse(impact)), nil
}

func pagination(r *http.Request) (int, int) {
	if r == nil || r.URL == nil {
		return 1, 50
	}
	page := positiveInt(r.URL.Query().Get("page"), 1)
	perPage := positiveInt(firstNonEmpty(r.URL.Query().Get("per_page"), r.URL.Query().Get("perPage")), 50)
	if perPage > maxPerPage {
		perPage = maxPerPage
	}
	return page, perPage
}

func positiveInt(raw string, fallback int) int {
	value, err := strconv.Atoi(raw)
	if err != nil || value <= 0 {
		return fallback
	}
	return value
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}

func roleErrorHandling() meshxapi.HandlerOption {
	return meshxapi.WithErrorClassifier(func(err error) (meshxapi.ErrorResponse, bool) {
		if roleservice.IsRoleDependencyUnavailable(err) {
			return meshxapi.ErrorResponse{Status: http.StatusServiceUnavailable, Message: roleservice.RoleDependencyUnavailableMessage(err), Code: "role_dependency_unavailable"}, true
		}
		if errors.Is(err, roleservice.ErrRoleVersionConflict) {
			return meshxapi.ErrorResponse{Status: http.StatusConflict, Message: err.Error(), Code: "role_version_conflict"}, true
		}
		if errors.Is(err, repos.ErrUnauthorized) || errors.Is(err, repos.ErrInvalidArgument) || errors.Is(err, repos.ErrForbidden) || errors.Is(err, repos.ErrNotFound) || errors.Is(err, repos.ErrConflict) {
			return meshxapi.ErrorResponse{Status: repos.HTTPStatus(err), Message: err.Error(), Code: repos.Code(err), Details: repos.Details(err)}, true
		}
		return meshxapi.ErrorResponse{}, false
	})
}
