package forwarder

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

	"github.com/tidbcloud/AgentTeam/internal/service/role/execution"
)

const (
	RoleOperationPreflightPath   = "/api/v1/internal/role-operations:preflight"
	RoleOperationExecutePath     = "/api/v1/internal/role-operations:execute"
	RoleOperationRecoveryPath    = "/api/v1/internal/role-operations:recover"
	RoleOperationInternalPurpose = "approved_role_management_operation"
)

// RoleOperationGateway is a transport-only Core client for the hidden MeshX
// Role authority. Production wiring is intentionally outside Layer 3.
type RoleOperationGateway struct {
	upstream *Handler
}

// RoleOperationPrincipal is only a typed claim projection for the MeshX
// protected-listener boundary. It is not an ownership authority: the Phase B
// caller must derive OwnedAgentIDs from current Core facts before invoking this
// gateway. The gateway exposes no arbitrary headers.
type RoleOperationPrincipal struct {
	WorkspaceID   string
	Subject       string
	OwnedAgentIDs []string
}

type RoleOperationRequest struct {
	Create *RoleOperationCreateRequest `json:"create,omitempty"`
	Update *RoleOperationUpdateRequest `json:"update,omitempty"`
	Delete *RoleOperationDeleteRequest `json:"delete,omitempty"`
	Apply  *RoleOperationApplyRequest  `json:"apply,omitempty"`
}

type RoleOperationExecuteRequest struct {
	OperationKey  string `json:"operationKey"`
	RequestDigest string `json:"requestDigest"`
	RoleOperationRequest
}

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 RoleOperationResult struct {
	RoleID        string `json:"roleId"`
	RoleVersionID string `json:"roleVersionId"`
}

type RoleOperationRecoveryResult struct {
	Found         bool
	RoleID        string
	RoleVersionID string
}

// DeterministicRoleOperationError is returned only for an explicit MeshX
// stable-code whitelist. Status alone never makes a failure deterministic.
type DeterministicRoleOperationError struct {
	Code    string
	Message string
}

func (err *DeterministicRoleOperationError) Error() string {
	if err == nil {
		return ""
	}
	if strings.TrimSpace(err.Message) != "" {
		return err.Message
	}
	return err.Code
}

func NewRoleOperationGateway(deps Dependencies) (*RoleOperationGateway, error) {
	upstream, err := NewHandler(deps)
	if err != nil {
		return nil, err
	}
	return &RoleOperationGateway{upstream: upstream}, nil
}

func (gateway *RoleOperationGateway) Preflight(
	ctx context.Context,
	principal RoleOperationPrincipal,
	request RoleOperationRequest,
) error {
	if gateway == nil || gateway.upstream == nil {
		return roleOperationResponseLost(errors.New("role operation gateway is not configured"))
	}
	if err := validateRoleOperationPrincipal(principal); err != nil {
		return err
	}
	err := gateway.upstream.ForwardJSON(ctx, JSONRequest{
		Method:                    http.MethodPost,
		Path:                      RoleOperationPreflightPath,
		WorkspaceID:               strings.TrimSpace(principal.WorkspaceID),
		Identity:                  roleOperationIdentity(principal),
		AgentOwnershipWorkspaceID: strings.TrimSpace(principal.WorkspaceID),
		OwnedAgents:               roleOperationOwnedAgents(principal.OwnedAgentIDs),
		Body:                      request,
		ExpectedStatus:            http.StatusNoContent,
		InternalPurpose:           RoleOperationInternalPurpose,
	}, nil)
	return classifyRoleOperationGatewayError(err)
}

func (gateway *RoleOperationGateway) Execute(
	ctx context.Context,
	principal RoleOperationPrincipal,
	request RoleOperationExecuteRequest,
) (RoleOperationResult, error) {
	if gateway == nil || gateway.upstream == nil {
		return RoleOperationResult{}, roleOperationResponseLost(errors.New("role operation gateway is not configured"))
	}
	if err := validateRoleOperationPrincipal(principal); err != nil {
		return RoleOperationResult{}, err
	}
	var response RoleOperationResult
	err := gateway.upstream.ForwardJSON(ctx, JSONRequest{
		Method:                    http.MethodPost,
		Path:                      RoleOperationExecutePath,
		WorkspaceID:               strings.TrimSpace(principal.WorkspaceID),
		Identity:                  roleOperationIdentity(principal),
		AgentOwnershipWorkspaceID: strings.TrimSpace(principal.WorkspaceID),
		OwnedAgents:               roleOperationOwnedAgents(principal.OwnedAgentIDs),
		Body:                      request,
		ExpectedStatus:            http.StatusOK,
		InternalPurpose:           RoleOperationInternalPurpose,
	}, &response)
	if err != nil {
		return RoleOperationResult{}, classifyRoleOperationGatewayError(err)
	}
	if strings.TrimSpace(response.RoleID) == "" || strings.TrimSpace(response.RoleVersionID) == "" {
		return RoleOperationResult{}, roleOperationResponseLost(errors.New("role operation response is incomplete"))
	}
	return response, nil
}

// Recover reads only an immutable MeshX operation receipt. A miss carries no
// authority to mutate and must continue through Core's normal authority path.
func (gateway *RoleOperationGateway) Recover(
	ctx context.Context,
	principal RoleOperationPrincipal,
	request RoleOperationExecuteRequest,
) (RoleOperationRecoveryResult, error) {
	if gateway == nil || gateway.upstream == nil {
		return RoleOperationRecoveryResult{}, roleOperationResponseLost(
			errors.New("role operation gateway is not configured"),
		)
	}
	if err := validateRoleOperationPrincipal(principal); err != nil {
		return RoleOperationRecoveryResult{}, err
	}
	var response RoleOperationRecoveryResult
	err := gateway.upstream.ForwardJSON(ctx, JSONRequest{
		Method:                    http.MethodPost,
		Path:                      RoleOperationRecoveryPath,
		WorkspaceID:               strings.TrimSpace(principal.WorkspaceID),
		Identity:                  roleOperationIdentity(principal),
		AgentOwnershipWorkspaceID: strings.TrimSpace(principal.WorkspaceID),
		Body:                      request,
		ExpectedStatus:            http.StatusOK,
		InternalPurpose:           RoleOperationInternalPurpose,
	}, &response)
	if err != nil {
		return RoleOperationRecoveryResult{}, classifyRoleOperationRecoveryError(err)
	}
	return response, nil
}

// UnmarshalJSON enforces the authority's exact successful wire contract.
func (result *RoleOperationResult) UnmarshalJSON(data []byte) error {
	type wire RoleOperationResult
	var decoded wire
	if err := rejectDuplicateRoleOperationJSONKeys(data); err != nil {
		return err
	}
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&decoded); err != nil {
		return err
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return errors.New("multiple JSON values")
		}
		return err
	}
	*result = RoleOperationResult(decoded)
	return nil
}

// UnmarshalJSON enforces the recovery endpoint's exact found/miss union.
func (result *RoleOperationRecoveryResult) UnmarshalJSON(data []byte) error {
	var decoded struct {
		Found         *bool   `json:"found"`
		RoleID        *string `json:"roleId"`
		RoleVersionID *string `json:"roleVersionId"`
	}
	if err := rejectDuplicateRoleOperationJSONKeys(data); err != nil {
		return err
	}
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&decoded); err != nil {
		return err
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return errors.New("multiple JSON values")
		}
		return err
	}
	if decoded.Found == nil {
		return errors.New("recovery response is missing found")
	}
	if !*decoded.Found {
		if decoded.RoleID != nil || decoded.RoleVersionID != nil {
			return errors.New("recovery miss contains stable ids")
		}
		*result = RoleOperationRecoveryResult{}
		return nil
	}
	if decoded.RoleID == nil || decoded.RoleVersionID == nil ||
		!validTypedRoleOperationID(*decoded.RoleID, "role") ||
		!validTypedRoleOperationID(*decoded.RoleVersionID, "rlv") {
		return errors.New("recovery result contains incomplete stable ids")
	}
	*result = RoleOperationRecoveryResult{
		Found: true, RoleID: *decoded.RoleID, RoleVersionID: *decoded.RoleVersionID,
	}
	return nil
}

func validTypedRoleOperationID(value, prefix string) bool {
	suffix, found := strings.CutPrefix(value, prefix+"_")
	if !found || len(suffix) == 0 || len(suffix) > 64 {
		return false
	}
	for _, character := range suffix {
		if (character < 'a' || character > 'z') &&
			(character < '0' || character > '9') {
			return false
		}
	}
	return true
}

func validateRoleOperationPrincipal(principal RoleOperationPrincipal) error {
	if strings.TrimSpace(principal.WorkspaceID) == "" || strings.TrimSpace(principal.Subject) == "" {
		return &DeterministicRoleOperationError{
			Code: "authority_denied", Message: "role operation principal is incomplete",
		}
	}
	return nil
}

func roleOperationIdentity(principal RoleOperationPrincipal) TrustedIdentity {
	subject := strings.TrimSpace(principal.Subject)
	return TrustedIdentity{
		UserID: subject, AgentTeamUserID: subject, PreferredUsername: normalizePreferredUsernameCandidate(subject),
	}
}

func roleOperationOwnedAgents(ids []string) []TrustedOwnedAgent {
	agents := make([]TrustedOwnedAgent, 0, len(ids))
	for _, id := range ids {
		if id = strings.TrimSpace(id); id != "" {
			agents = append(agents, TrustedOwnedAgent{ID: id})
		}
	}
	return agents
}

func classifyRoleOperationGatewayError(err error) error {
	if err == nil {
		return nil
	}
	var upstream UpstreamHTTPError
	if errors.As(err, &upstream) {
		code, message, ok := strictRoleOperationErrorEnvelope(upstream.rawBody)
		if !ok {
			return roleOperationResponseLost(err)
		}
		switch code {
		case "invalid_request", "authority_denied", "not_found",
			"role_version_conflict", "role_operation_conflict":
			return &DeterministicRoleOperationError{Code: code, Message: message}
		default:
			return roleOperationResponseLost(err)
		}
	}
	return roleOperationResponseLost(err)
}

func classifyRoleOperationRecoveryError(err error) error {
	if err == nil {
		return nil
	}
	var upstream UpstreamHTTPError
	if errors.As(err, &upstream) {
		code, message, ok := strictRoleOperationErrorEnvelope(upstream.rawBody)
		if ok && code == "role_operation_conflict" {
			return &DeterministicRoleOperationError{Code: code, Message: message}
		}
	}
	return roleOperationResponseLost(err)
}

func strictRoleOperationErrorEnvelope(body []byte) (string, string, bool) {
	var envelope struct {
		Error   string `json:"error"`
		Message string `json:"message"`
	}
	if err := rejectDuplicateRoleOperationJSONKeys(body); err != nil {
		return "", "", false
	}
	decoder := json.NewDecoder(bytes.NewReader(body))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&envelope); err != nil {
		return "", "", false
	}
	var trailing any
	if err := decoder.Decode(&trailing); err != io.EOF {
		return "", "", false
	}
	code := strings.TrimSpace(envelope.Error)
	message := strings.TrimSpace(envelope.Message)
	if code == "" || message == "" {
		return "", "", false
	}
	return code, message, true
}

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 roleOperationResponseLost(cause error) error {
	responseLost := &execution.ApplyError{Outcome: execution.ApplyResponseLost}
	return fmt.Errorf("%w: %v", responseLost, cause)
}
