// Package repotokens owns the MeshX Repository Access Tokens HTTP wrapper.
package repotokens

import (
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	"github.com/tidbcloud/AgentTeam/internal/service/repotokens"
)

type Handler struct{ service *repotokens.Service }
type RouteMiddleware func(http.Handler) http.Handler
type routeRegistrar interface {
	Handle(pattern string, handler http.Handler)
}

type CreateRequest struct {
	DisplayName     string                     `json:"displayName"`
	ProviderHost    string                     `json:"providerHost"`
	RepositoryScope repotokens.RepositoryScope `json:"repositoryScope"`
	Capabilities    []string                   `json:"capabilities"`
	ExpiresAt       *time.Time                 `json:"expiresAt"`
}

type RevokeRequest struct {
	Reason string `json:"reason"`
}

type ReconcileAgentRequest struct {
	ProviderHost    string                     `json:"providerHost"`
	RepositoryScope repotokens.RepositoryScope `json:"repositoryScope"`
	PermissionMode  string                     `json:"permissionMode"`
	Enabled         *bool                      `json:"enabled,omitempty"`
	Reason          string                     `json:"reason"`
}

type RevokeAgentRequest struct {
	Reason string `json:"reason"`
	Force  bool   `json:"force"`
}

func NewHandler(svc *repotokens.Service) *Handler { return &Handler{service: svc} }

func RegisterRoutes(router routeRegistrar, handler *Handler, middleware RouteMiddleware) {
	if router == nil {
		return
	}
	if handler == nil {
		handler = NewHandler(nil)
	}
	register(router, "GET /api/v1/repository-access-tokens", meshxapi.HandleAuth(handler.List, errorHandling()), middleware)
	register(router, "POST /api/v1/repository-access-tokens", meshxapi.HandleAuthJSON[CreateRequest](handler.Create, errorHandling()), middleware)
	register(router, "GET /api/v1/repository-access-tokens/{token_id}", meshxapi.HandleAuth(handler.Get, errorHandling()), middleware)
	register(router, "GET /api/v1/repository-access-tokens/{token_id}/events", meshxapi.HandleAuth(handler.Events, errorHandling()), middleware)
	register(router, "POST /api/v1/repository-access-tokens/{token_id}/revoke", meshxapi.HandleAuthJSON[RevokeRequest](handler.Revoke, errorHandling()), middleware)
	register(router, "POST /api/v1/internal/repository-access-tokens/agents/{agent_id}/reconcile", meshxapi.HandleAuthJSON[ReconcileAgentRequest](handler.ReconcileAgent, errorHandling()), middleware)
	register(router, "POST /api/v1/internal/repository-access-tokens/agents/{agent_id}/revoke", meshxapi.HandleAuthJSON[RevokeAgentRequest](handler.RevokeAgent, errorHandling()), middleware)
}

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

func (h *Handler) svc() *repotokens.Service {
	if h == nil || h.service == nil {
		return repotokens.NewService(nil, nil)
	}
	return h.service
}

func (h *Handler) List(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := requireHumanTokenManage(r); err != nil {
		return meshxapi.Response{}, err
	}
	limit, _ := strconv.Atoi(strings.TrimSpace(r.Request.URL.Query().Get("limit")))
	result, err := h.svc().List(r.Context(), repotokens.ListRequest{
		WorkspaceID:      r.WorkspaceID(),
		ActorPrincipalID: r.Subject(),
		Type:             strings.TrimSpace(r.Request.URL.Query().Get("type")),
		Status:           strings.TrimSpace(r.Request.URL.Query().Get("status")),
		ProviderHost:     strings.TrimSpace(r.Request.URL.Query().Get("providerHost")),
		OwnerPrincipalID: strings.TrimSpace(r.Request.URL.Query().Get("ownerPrincipalId")),
		AgentID:          strings.TrimSpace(r.Request.URL.Query().Get("agentId")),
		Cursor:           strings.TrimSpace(r.Request.URL.Query().Get("cursor")),
		Limit:            limit,
	})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(result), nil
}

func (h *Handler) Create(r meshxapi.AuthenticatedJSON[CreateRequest]) (meshxapi.Response, error) {
	if err := requireHumanTokenManage(r.AuthenticatedRequest); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.svc().CreateHuman(r.Context(), repotokens.CreateHumanRequest{
		WorkspaceID:      r.WorkspaceID(),
		ActorPrincipalID: r.Subject(),
		DisplayName:      r.Body.DisplayName,
		ProviderHost:     r.Body.ProviderHost,
		RepositoryScope:  r.Body.RepositoryScope,
		Capabilities:     r.Body.Capabilities,
		ExpiresAt:        r.Body.ExpiresAt,
		IdempotencyKey:   r.Request.Header.Get("Idempotency-Key"),
	})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(result), nil
}

func (h *Handler) Get(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := requireHumanTokenManage(r); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.svc().Get(r.Context(), repotokens.GetRequest{WorkspaceID: r.WorkspaceID(), ActorPrincipalID: r.Subject(), ID: r.Request.PathValue("token_id")})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(result), nil
}

func (h *Handler) Events(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := requireHumanTokenManage(r); err != nil {
		return meshxapi.Response{}, err
	}
	limit, _ := strconv.Atoi(strings.TrimSpace(r.Request.URL.Query().Get("limit")))
	result, err := h.svc().Events(r.Context(), repotokens.EventListRequest{
		WorkspaceID:      r.WorkspaceID(),
		ActorPrincipalID: r.Subject(),
		ID:               r.Request.PathValue("token_id"),
		Cursor:           strings.TrimSpace(r.Request.URL.Query().Get("cursor")),
		Limit:            limit,
	})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(result), nil
}

func (h *Handler) Revoke(r meshxapi.AuthenticatedJSON[RevokeRequest]) (meshxapi.Response, error) {
	if err := requireHumanTokenManage(r.AuthenticatedRequest); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.svc().Revoke(r.Context(), repotokens.RevokeRequest{
		WorkspaceID:      r.WorkspaceID(),
		ActorPrincipalID: r.Subject(),
		ID:               r.Request.PathValue("token_id"),
		Reason:           r.Body.Reason,
	})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(map[string]repotokens.TokenRef{"token": result}), nil
}

func requireHumanTokenManage(r meshxapi.AuthenticatedRequest) error {
	workspaceID := r.WorkspaceID()
	if strings.TrimSpace(workspaceID) == "" {
		return repotokens.E(repotokens.ErrPermissionDenied, "workspace context is required")
	}
	if !stringClaimContainsAny(r.Identity.RawClaims, "workspace_ids", workspaceID, "*", "all") {
		return repotokens.E(repotokens.ErrPermissionDenied, "repository access token management requires workspace-scoped authority")
	}
	if !stringClaimContainsAny(r.Identity.RawClaims, "automation_permissions", "automation:*") &&
		!stringClaimContainsAny(r.Identity.RawClaims, "repository_access_permissions", "repository_access_tokens:manage", "repository_access_tokens:human:manage") {
		return repotokens.E(repotokens.ErrPermissionDenied, "repository access token management requires workspace manage authority")
	}
	return nil
}

func (h *Handler) ReconcileAgent(r meshxapi.AuthenticatedJSON[ReconcileAgentRequest]) (meshxapi.Response, error) {
	if err := requireAgentLifecycleManage(r.AuthenticatedRequest); err != nil {
		return meshxapi.Response{}, err
	}
	agentID := r.Request.PathValue("agent_id")
	state := repotokens.DesiredStateFromAgentRepositoryAccess(r.WorkspaceID(), r.Subject(), agentID, r.Body.ProviderHost, r.Body.RepositoryScope, r.Body.PermissionMode)
	if r.Body.Enabled != nil && !*r.Body.Enabled {
		state.Enabled = false
	}
	result, err := h.svc().ReconcileAgentDesiredState(r.Context(), state, r.Body.Reason)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(map[string]repotokens.TokenRef{"token": result}), nil
}

func (h *Handler) RevokeAgent(r meshxapi.AuthenticatedJSON[RevokeAgentRequest]) (meshxapi.Response, error) {
	if err := requireAgentLifecycleManage(r.AuthenticatedRequest); err != nil {
		return meshxapi.Response{}, err
	}
	result, err := h.svc().RevokeAgent(r.Context(), repotokens.RevokeAgentRequest{
		WorkspaceID:      r.WorkspaceID(),
		ActorPrincipalID: r.Subject(),
		AgentID:          r.Request.PathValue("agent_id"),
		Reason:           r.Body.Reason,
		Force:            r.Body.Force,
	})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(map[string]repotokens.TokenRef{"token": result}), nil
}

func requireAgentLifecycleManage(r meshxapi.AuthenticatedRequest) error {
	workspaceID := r.WorkspaceID()
	if strings.TrimSpace(workspaceID) == "" {
		return repotokens.E(repotokens.ErrPermissionDenied, "workspace context is required")
	}
	if !stringClaimContainsAny(r.Identity.RawClaims, "workspace_ids", workspaceID, "*", "all") {
		return repotokens.E(repotokens.ErrPermissionDenied, "agent repository access lifecycle requires workspace-scoped internal authority")
	}
	if !stringClaimContainsAny(r.Identity.RawClaims, "automation_permissions", "automation:runner:manage", "automation:*") &&
		!stringClaimContains(r.Identity.RawClaims, "repository_access_permissions", "repository_access_tokens:agent:manage") {
		return repotokens.E(repotokens.ErrPermissionDenied, "agent repository access lifecycle requires workspace manage authority")
	}
	return nil
}

func stringClaimContainsAny(claims map[string]any, key string, wants ...string) bool {
	for _, want := range wants {
		if stringClaimContains(claims, key, want) {
			return true
		}
	}
	return false
}

func stringClaimContains(claims map[string]any, key string, want string) bool {
	want = strings.TrimSpace(want)
	if want == "" {
		return false
	}
	raw, ok := claims[key]
	if !ok {
		return false
	}
	switch values := raw.(type) {
	case []any:
		for _, value := range values {
			if s, ok := value.(string); ok && strings.TrimSpace(s) == want {
				return true
			}
		}
	case []string:
		for _, value := range values {
			if strings.TrimSpace(value) == want {
				return true
			}
		}
	case string:
		for _, value := range strings.Split(values, ",") {
			if strings.TrimSpace(value) == want {
				return true
			}
		}
	}
	return false
}

func errorHandling() meshxapi.HandlerOption {
	return meshxapi.WithErrorClassifier(func(err error) (meshxapi.ErrorResponse, bool) {
		status := repotokens.HTTPStatus(err)
		if status == http.StatusInternalServerError {
			return meshxapi.ErrorResponse{}, false
		}
		return meshxapi.ErrorResponse{Status: status, Message: err.Error(), Code: repotokens.Code(err)}, true
	})
}
