// Package api contains internal APIs that are shared by agent and server code.
package api

import (
	"fmt"
	"log/slog"

	"go.opentelemetry.io/otel/attribute"
)

const (
	// TraceAgentIDAttr is tracing attribute that holds an agent id.
	TraceAgentIDAttr attribute.Key = "agent_id"
	// TraceAgentTypeAttr is tracing attribute that holds an agent type.
	TraceAgentTypeAttr attribute.Key = "agent_type"

	// AgentKeyFieldName is the field name to log the agent key.
	AgentKeyFieldName = "agent_key"

	JWTAgentk = "gitlab-agent"
	JWTKAS    = "gitlab-kas"

	// FieldManager name for the ones agentk or kas own, see
	// https://kubernetes.io/docs/reference/using-api/server-side-apply/#field-management
	FieldManager = "agentk"

	// AgentKeyPrefix is the prefix to use for labels and annotations owned by kas/agentk.
	// See https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set.
	// See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set.
	AgentKeyPrefix     = "agent.gitlab.com"
	AgentIDKey         = AgentKeyPrefix + "/id"
	ConfigProjectIDKey = AgentKeyPrefix + "/config_project_id"
	ProjectIDKey       = AgentKeyPrefix + "/project_id"
	// ProjectKey is the annotation/label key that has a GitLab project full path as its value, e.g. `gitlab-org/gitlab`.
	ProjectKey         = AgentKeyPrefix + "/project"
	CIPipelineIDKey    = AgentKeyPrefix + "/ci_pipeline_id"
	CIJobIDKey         = AgentKeyPrefix + "/ci_job_id"
	UsernameKey        = AgentKeyPrefix + "/username"
	EnvironmentSlugKey = AgentKeyPrefix + "/environment_slug"
	EnvironmentTierKey = AgentKeyPrefix + "/environment_tier"

	// WebSocketMaxMessageSize is an arbitrary historical limit we used from the early days.
	WebSocketMaxMessageSize = 10 * 1024 * 1024
	// GRPCMaxMessageSize is a limit that is under WebSocketMaxMessageSize.
	// Historically we didn't set gRPC limits:
	// - On the server the default value was 4MB.
	// - On the client the default value was 2GB.
	// We use 4MB explicitly to match the historical value.
	GRPCMaxMessageSize = 4 * 1024 * 1024

	// Compile time test to ensure GRPCMaxMessageSize is smaller than WebSocketMaxMessageSize.
	_ uint = WebSocketMaxMessageSize - GRPCMaxMessageSize
)

// AgentType is agent's type.
type AgentType byte

// NOTE: AgentTypeKubernetes should always be at the top to ensure backwards compatibility.
const (
	AgentTypeKubernetes AgentType = iota
	AgentTypeWorkspace
	AgentTypeRunner
	AgentTypeRunnerController
	AgentTypeUnknown
)

// String returns the string representation of the agent type
func (at AgentType) String() string {
	//nolint: exhaustive
	switch at {
	case AgentTypeKubernetes:
		return "agentk"
	case AgentTypeWorkspace:
		return "agentw"
	case AgentTypeRunner:
		return "runner"
	case AgentTypeRunnerController:
		return "runnerc"
	default:
		return "unknown"
	}
}

// ParseAgentType converts a string to an AgentType
// When parsing an agent type, we only expect real values like `agentk`, `agentw`.
// If we encounter any other value, we treat it as `AgentTypeUnknown` and raise an error.
func ParseAgentType(s string) (AgentType, error) {
	switch s {
	case "agentk":
		return AgentTypeKubernetes, nil
	case "agentw":
		return AgentTypeWorkspace, nil
	case "runner":
		return AgentTypeRunner, nil
	case "runnerc":
		return AgentTypeRunnerController, nil
	default:
		return AgentTypeUnknown, fmt.Errorf("unrecognized agent type: %s", s)
	}
}

// AgentKey is agent's key consisting of type and id.
type AgentKey struct {
	ID   int64
	Type AgentType
}

// String returns the string representation of the agent key
func (a AgentKey) String() string {
	return fmt.Sprintf("%s:%d", a.Type, a.ID)
}

func (a AgentKey) LogValue() slog.Value {
	return slog.StringValue(a.String())
}

func (a AgentKey) LogAttr() slog.Attr {
	return slog.Any(AgentKeyFieldName, a)
}

// AgentToken is agent's bearer access token.
type AgentToken string

// AgentTokenWithType is agent's token and type
type AgentTokenWithType struct {
	Token AgentToken
	Type  AgentType
}
