package roles

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strings"

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

const (
	// Role operation paths are hidden Core-to-MeshX transport endpoints.
	RoleOperationPreflightPath = "/api/v1/internal/role-operations:preflight"
	RoleOperationExecutePath   = "/api/v1/internal/role-operations:execute"
	RoleOperationRecoveryPath  = "/api/v1/internal/role-operations:recover"

	// RoleOperationInternalPurpose is issued only by Core's typed transport.
	RoleOperationInternalPurpose = "approved_role_management_operation"

	maxRoleOperationBodyBytes = 1 << 20
)

var (
	roleOperationIDPattern     = regexp.MustCompile(`^[a-z][a-z0-9]*_[a-z0-9]{1,64}$`)
	roleOperationDigestPattern = regexp.MustCompile(`^[0-9a-f]{64}$`)
)

// OperationService is the existing Layer 2 Role authority surface used by the
// hidden transport. It deliberately adds no ledger, verifier, or mutation API.
type OperationService interface {
	PreflightCreateRoleOperation(context.Context, string, string, roleservice.Mutation) error
	PreflightUpdateRoleOperation(context.Context, string, string, string, string, roleservice.RoleUpdatePatch) error
	PreflightDeleteRoleOperation(context.Context, string, string, string, string) error
	PreflightApplyRoleOperation(context.Context, string, string, string, roleservice.RealizationRequest) (roleservice.RealizationImpact, error)
	CreateRoleOperation(context.Context, string, string, roleservice.MutationOperation, roleservice.Mutation) (roleservice.Role, error)
	UpdateRoleOperation(context.Context, string, string, string, roleservice.MutationOperation, string, roleservice.RoleUpdatePatch) (roleservice.Role, error)
	DeleteRoleOperation(context.Context, string, string, string, roleservice.MutationOperation, string) (roleservice.DeleteRoleResult, error)
	ApplyRoleOperation(context.Context, string, string, string, roleservice.MutationOperation, roleservice.RealizationRequest) (roleservice.RealizationResult, error)
	RecoverRoleOperation(context.Context, string, string, roleservice.MutationOperation) (roleservice.RoleOperationRecovery, error)
}

type roleOperationRequest struct {
	OperationKey  string                      `json:"operationKey,omitempty"`
	RequestDigest string                      `json:"requestDigest,omitempty"`
	Create        *roleOperationCreateRequest `json:"create,omitempty"`
	Update        *roleOperationUpdateRequest `json:"update,omitempty"`
	Delete        *roleOperationDeleteRequest `json:"delete,omitempty"`
	Apply         *roleOperationApplyRequest  `json:"apply,omitempty"`
}

type roleOperationCreateRequest struct {
	Name        string `json:"name"`
	Definition  string `json:"definition"`
	Description string `json:"description,omitempty"`
}

type roleOperationUpdateRequest struct {
	RoleID                string  `json:"roleId"`
	ExpectedRoleVersionID string  `json:"expectedRoleVersionId"`
	Name                  *string `json:"name,omitempty"`
	Description           *string `json:"description,omitempty"`
	Definition            *string `json:"definition,omitempty"`
	Visibility            *string `json:"visibility,omitempty"`
}

type roleOperationDeleteRequest struct {
	RoleID                string `json:"roleId"`
	ExpectedRoleVersionID string `json:"expectedRoleVersionId"`
}

type roleOperationApplyRequest struct {
	RoleID        string `json:"roleId"`
	RoleVersionID string `json:"roleVersionId"`
	AgentID       string `json:"agentId"`
}

type roleOperationResponse struct {
	RoleID        string `json:"roleId"`
	RoleVersionID string `json:"roleVersionId"`
}

type roleOperationRecoveryResponse struct {
	Found         bool   `json:"found"`
	RoleID        string `json:"roleId,omitempty"`
	RoleVersionID string `json:"roleVersionId,omitempty"`
}

type roleOperationTransport struct {
	service OperationService
}

// NewRoleOperationTransportHandler constructs the hidden Role transport.
// Route composition remains an explicit caller responsibility.
func NewRoleOperationTransportHandler(service OperationService) http.Handler {
	transport := &roleOperationTransport{service: service}
	return http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
		authenticated, err := meshxapi.NewAuthenticatedRequest(request)
		if err != nil {
			writeRoleOperationError(w, repos.ErrForbidden)
			return
		}
		if err := authorizeRoleOperationRequest(authenticated); err != nil {
			writeRoleOperationError(w, err)
			return
		}
		body, err := decodeRoleOperationRequest(w, request)
		if err != nil {
			writeRoleOperationError(w, invalidRoleOperationRequest("Invalid role operation request"))
			return
		}
		response, err := transport.handle(meshxapi.AuthenticatedJSON[roleOperationRequest]{
			AuthenticatedRequest: authenticated,
			Body:                 body,
		})
		if err != nil {
			writeRoleOperationError(w, err)
			return
		}
		meshxapi.WriteResponse(w, response)
	})
}

func (transport *roleOperationTransport) handle(request meshxapi.AuthenticatedJSON[roleOperationRequest]) (meshxapi.Response, error) {
	if transport == nil || transport.service == nil {
		return meshxapi.Response{}, errors.New("role operation service is not configured")
	}
	if request.Request == nil || request.Request.Method != http.MethodPost {
		return meshxapi.Response{}, invalidRoleOperationRequest("role operation requires POST")
	}
	path := request.Request.URL.Path
	if path != RoleOperationPreflightPath &&
		path != RoleOperationExecutePath &&
		path != RoleOperationRecoveryPath {
		return meshxapi.Response{}, invalidRoleOperationRequest("unknown role operation endpoint")
	}
	if err := authorizeRoleOperationRequest(request.AuthenticatedRequest); err != nil {
		return meshxapi.Response{}, err
	}
	workspaceID := request.WorkspaceID()
	subject := strings.TrimSpace(request.Subject())
	action, err := validateRoleOperationRequest(
		request.Body,
		path == RoleOperationExecutePath || path == RoleOperationRecoveryPath,
	)
	if err != nil {
		return meshxapi.Response{}, err
	}
	if action == "apply" && path == RoleOperationPreflightPath {
		if err := (roleservice.ClaimsAgentOwnershipSource{}).RequireOwnedAgent(
			request.Context(), workspaceID, subject, request.Body.Apply.AgentID,
		); err != nil {
			return meshxapi.Response{}, err
		}
	}
	if path == RoleOperationPreflightPath {
		if err := transport.preflight(request.Context(), workspaceID, subject, action, request.Body); err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.NoContent(), nil
	}
	if path == RoleOperationRecoveryPath {
		result, err := transport.service.RecoverRoleOperation(
			request.Context(),
			workspaceID,
			action,
			roleservice.MutationOperation{
				OperationKey:  request.Body.OperationKey,
				RequestDigest: request.Body.RequestDigest,
			},
		)
		if err != nil {
			return meshxapi.Response{}, err
		}
		return meshxapi.OK(roleOperationRecoveryResponse{
			Found:         result.Found,
			RoleID:        result.RoleID,
			RoleVersionID: result.RoleVersionID,
		}), nil
	}
	result, err := transport.execute(request.Context(), workspaceID, subject, action, request.Body)
	if err != nil {
		return meshxapi.Response{}, err
	}
	if strings.TrimSpace(result.RoleID) == "" || strings.TrimSpace(result.RoleVersionID) == "" {
		return meshxapi.Response{}, errors.New("role operation authority returned incomplete result")
	}
	return meshxapi.OK(result), nil
}

func authorizeRoleOperationRequest(request meshxapi.AuthenticatedRequest) error {
	workspaceID := request.WorkspaceID()
	subject := strings.TrimSpace(request.Subject())
	if workspaceID == "" || subject == "" {
		return repos.ErrForbidden
	}
	identity, ok := trusted.IdentityFromContext(request.Context())
	if !ok || strings.TrimSpace(identity.Subject) != subject ||
		claimText(identity.RawClaims, "agentteam_internal_purpose") != RoleOperationInternalPurpose ||
		claimText(identity.RawClaims, "agent_ownership_workspace_id") != workspaceID {
		return repos.ErrForbidden
	}
	return nil
}

func decodeRoleOperationRequest(w http.ResponseWriter, request *http.Request) (roleOperationRequest, error) {
	var body roleOperationRequest
	if request == nil || request.Body == nil {
		return body, io.EOF
	}
	request.Body = http.MaxBytesReader(w, request.Body, maxRoleOperationBodyBytes)
	defer request.Body.Close()
	payload, err := io.ReadAll(request.Body)
	if err != nil {
		return body, err
	}
	if err := rejectDuplicateRoleOperationJSONKeys(payload); err != nil {
		return body, err
	}
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&body); err != nil {
		return body, err
	}
	var trailing any
	if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
		if err == nil {
			return body, errors.New("multiple JSON values")
		}
		return body, err
	}
	return body, nil
}

func rejectDuplicateRoleOperationJSONKeys(payload []byte) error {
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.UseNumber()
	first, err := decoder.Token()
	if err != nil {
		return err
	}
	if err := walkRoleOperationJSONToken(decoder, first); err != nil {
		return err
	}
	if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
		if err == nil {
			return errors.New("multiple JSON values")
		}
		return err
	}
	return nil
}

func walkRoleOperationJSONToken(decoder *json.Decoder, token json.Token) error {
	delimiter, composite := token.(json.Delim)
	if !composite {
		return nil
	}
	switch delimiter {
	case '{':
		var keys []string
		for decoder.More() {
			keyToken, err := decoder.Token()
			if err != nil {
				return err
			}
			key, ok := keyToken.(string)
			if !ok {
				return errors.New("JSON object key is not a string")
			}
			for _, existingKey := range keys {
				if strings.EqualFold(existingKey, key) {
					return fmt.Errorf("duplicate JSON field %q", key)
				}
			}
			keys = append(keys, key)
			value, err := decoder.Token()
			if err != nil {
				return err
			}
			if err := walkRoleOperationJSONToken(decoder, value); err != nil {
				return err
			}
		}
		closing, err := decoder.Token()
		if err != nil {
			return err
		}
		if closing != json.Delim('}') {
			return errors.New("JSON object is not closed")
		}
	case '[':
		for decoder.More() {
			value, err := decoder.Token()
			if err != nil {
				return err
			}
			if err := walkRoleOperationJSONToken(decoder, value); err != nil {
				return err
			}
		}
		closing, err := decoder.Token()
		if err != nil {
			return err
		}
		if closing != json.Delim(']') {
			return errors.New("JSON array is not closed")
		}
	default:
		return fmt.Errorf("unexpected JSON delimiter %q", delimiter)
	}
	return nil
}

func (transport *roleOperationTransport) preflight(
	ctx context.Context,
	workspaceID, subject, action string,
	request roleOperationRequest,
) error {
	switch action {
	case "create":
		return transport.service.PreflightCreateRoleOperation(ctx, workspaceID, subject, request.Create.input())
	case "update":
		return transport.service.PreflightUpdateRoleOperation(
			ctx, workspaceID, subject, request.Update.RoleID,
			request.Update.ExpectedRoleVersionID, request.Update.patch(),
		)
	case "delete":
		return transport.service.PreflightDeleteRoleOperation(
			ctx, workspaceID, subject, request.Delete.RoleID, request.Delete.ExpectedRoleVersionID,
		)
	case "apply":
		_, err := transport.service.PreflightApplyRoleOperation(
			ctx, workspaceID, subject, request.Apply.RoleID, request.Apply.input(),
		)
		return err
	default:
		return invalidRoleOperationRequest("role operation action is invalid")
	}
}

func (transport *roleOperationTransport) execute(
	ctx context.Context,
	workspaceID, subject, action string,
	request roleOperationRequest,
) (roleOperationResponse, error) {
	operation := roleservice.MutationOperation{
		OperationKey: request.OperationKey, RequestDigest: request.RequestDigest,
	}
	switch action {
	case "create":
		result, err := transport.service.CreateRoleOperation(ctx, workspaceID, subject, operation, request.Create.input())
		return roleOperationResponse{RoleID: result.ID, RoleVersionID: result.RoleVersionID}, err
	case "update":
		result, err := transport.service.UpdateRoleOperation(
			ctx, workspaceID, subject, request.Update.RoleID, operation,
			request.Update.ExpectedRoleVersionID, request.Update.patch(),
		)
		return roleOperationResponse{RoleID: result.ID, RoleVersionID: result.RoleVersionID}, err
	case "delete":
		result, err := transport.service.DeleteRoleOperation(
			ctx, workspaceID, subject, request.Delete.RoleID, operation, request.Delete.ExpectedRoleVersionID,
		)
		return roleOperationResponse{RoleID: result.RoleID, RoleVersionID: result.RoleVersionID}, err
	case "apply":
		result, err := transport.service.ApplyRoleOperation(
			ctx, workspaceID, subject, request.Apply.RoleID, operation, request.Apply.input(),
		)
		return roleOperationResponse{RoleID: result.RoleID, RoleVersionID: result.RoleVersionID()}, err
	default:
		return roleOperationResponse{}, invalidRoleOperationRequest("role operation action is invalid")
	}
}

func validateRoleOperationRequest(request roleOperationRequest, execute bool) (string, error) {
	selected := 0
	action := ""
	for name, present := range map[string]bool{
		"create": request.Create != nil,
		"update": request.Update != nil,
		"delete": request.Delete != nil,
		"apply":  request.Apply != nil,
	} {
		if present {
			selected++
			action = name
		}
	}
	if selected != 1 {
		return "", invalidRoleOperationRequest("exactly one role operation action is required")
	}
	if execute && (!validRoleOperationID(request.OperationKey) || !roleOperationDigestPattern.MatchString(strings.TrimSpace(request.RequestDigest))) {
		return "", invalidRoleOperationRequest("valid operationKey and requestDigest are required")
	}
	switch action {
	case "create":
		if strings.TrimSpace(request.Create.Name) == "" || strings.TrimSpace(request.Create.Definition) == "" {
			return "", invalidRoleOperationRequest("create name and definition are required")
		}
	case "update":
		if !validRoleOperationID(request.Update.RoleID) || !validRoleOperationID(request.Update.ExpectedRoleVersionID) {
			return "", invalidRoleOperationRequest("update role identifiers are invalid")
		}
		if request.Update.Name == nil && request.Update.Description == nil &&
			request.Update.Definition == nil && request.Update.Visibility == nil {
			return "", invalidRoleOperationRequest("update requires at least one field")
		}
	case "delete":
		if !validRoleOperationID(request.Delete.RoleID) || !validRoleOperationID(request.Delete.ExpectedRoleVersionID) {
			return "", invalidRoleOperationRequest("delete role identifiers are invalid")
		}
	case "apply":
		if !validRoleOperationID(request.Apply.RoleID) || !validRoleOperationID(request.Apply.RoleVersionID) ||
			!validRoleOperationID(request.Apply.AgentID) {
			return "", invalidRoleOperationRequest("apply identifiers are invalid")
		}
	}
	return action, nil
}

func (request roleOperationCreateRequest) input() roleservice.Mutation {
	return roleservice.Mutation{
		Name: request.Name, Definition: request.Definition, Description: request.Description,
	}
}

func (request roleOperationUpdateRequest) patch() roleservice.RoleUpdatePatch {
	return roleservice.RoleUpdatePatch{
		Name: request.Name, Description: request.Description,
		Definition: request.Definition, Visibility: request.Visibility,
	}
}

func (request roleOperationApplyRequest) input() roleservice.RealizationRequest {
	return roleservice.RealizationRequest{
		AgentID: request.AgentID, RoleVersionID: request.RoleVersionID, Apply: true,
	}
}

func validRoleOperationID(value string) bool {
	value = strings.TrimSpace(value)
	return roleOperationIDPattern.MatchString(value)
}

func claimText(claims map[string]any, name string) string {
	value, _ := claims[name].(string)
	return strings.TrimSpace(value)
}

func invalidRoleOperationRequest(message string) error {
	return meshxapi.HTTPError{
		Status: http.StatusBadRequest, Code: "invalid_request", Message: message,
	}
}

func classifyRoleOperationError(err error) (meshxapi.ErrorResponse, bool) {
	response := meshxapi.ErrorResponse{Message: err.Error()}
	switch {
	case errors.Is(err, roleservice.ErrInvalidRoleMutationOperation),
		errors.Is(err, repos.ErrInvalidArgument):
		response.Status, response.Code = http.StatusBadRequest, "invalid_request"
	case errors.Is(err, repos.ErrUnauthorized),
		errors.Is(err, repos.ErrForbidden):
		response.Status, response.Code = http.StatusForbidden, "authority_denied"
	case errors.Is(err, repos.ErrNotFound):
		response.Status, response.Code = http.StatusNotFound, "not_found"
	case errors.Is(err, roleservice.ErrRoleVersionConflict):
		response.Status, response.Code = http.StatusConflict, "role_version_conflict"
	case errors.Is(err, roleservice.ErrRoleMutationConflict),
		errors.Is(err, repos.ErrConflict):
		response.Status, response.Code = http.StatusConflict, "role_operation_conflict"
	case roleservice.IsRoleDependencyUnavailable(err):
		response.Status, response.Code = http.StatusServiceUnavailable, "role_dependency_unavailable"
		response.Message = roleservice.RoleDependencyUnavailableMessage(err)
	default:
		return meshxapi.ErrorResponse{}, false
	}
	return response, true
}

func writeRoleOperationError(w http.ResponseWriter, err error) {
	var explicit meshxapi.HTTPError
	if errors.As(err, &explicit) {
		meshxapi.WriteError(w, explicit)
		return
	}
	if response, ok := classifyRoleOperationError(err); ok {
		meshxapi.WriteError(w, meshxapi.HTTPError{
			Status: response.Status, Code: response.Code, Message: response.Message, Cause: err,
		})
		return
	}
	meshxapi.WriteError(w, err)
}
