// Copyright Contributors to the KubeOpenCode project

package v1alpha1

import (
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope="Namespaced",shortName=ag
// +kubebuilder:printcolumn:JSONPath=`.spec.profile`,name="Profile",type=string,priority=1
// +kubebuilder:printcolumn:JSONPath=`.spec.executorImage`,name="Image",type=string,priority=1
// +kubebuilder:printcolumn:JSONPath=`.spec.serviceAccountName`,name="ServiceAccount",type=string
// +kubebuilder:printcolumn:JSONPath=`.spec.maxConcurrentTasks`,name="MaxTasks",type=integer,priority=1
// +kubebuilder:printcolumn:JSONPath=`.status.ready`,name="Ready",type=boolean
// +kubebuilder:printcolumn:JSONPath=`.metadata.creationTimestamp`,name="Age",type=date

// Agent defines a running AI agent instance.
// When created, the controller provisions a Deployment (running OpenCode server) and a Service.
// Tasks reference Agents via agentRef and connect using `opencode run --attach`.
type Agent struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	// Spec defines the agent configuration
	Spec AgentSpec `json:"spec"`

	// Status represents the current status of the Agent
	// +optional
	Status AgentStatus `json:"status,omitempty"`
}

// QuotaConfig defines rate limiting for Task starts within a sliding time window.
// This is different from maxConcurrentTasks which limits concurrent running Tasks.
// Quota limits the RATE at which new Tasks can start.
type QuotaConfig struct {
	// MaxTaskStarts is the maximum number of Task starts allowed within the window.
	// +kubebuilder:validation:Minimum=1
	// +required
	MaxTaskStarts int32 `json:"maxTaskStarts"`

	// WindowSeconds defines the sliding window duration in seconds.
	// For example, 3600 (1 hour) means "max N tasks per hour".
	// +kubebuilder:validation:Minimum=60
	// +kubebuilder:validation:Maximum=86400
	// +required
	WindowSeconds int32 `json:"windowSeconds"`
}

// TaskStartRecord represents a record of a Task start for quota tracking.
// Stored in AgentStatus to persist across controller restarts.
type TaskStartRecord struct {
	// TaskName is the name of the Task that was started.
	TaskName string `json:"taskName"`

	// TaskNamespace is the namespace of the Task.
	TaskNamespace string `json:"taskNamespace"`

	// StartTime is when the Task transitioned to Running phase.
	StartTime metav1.Time `json:"startTime"`
}

// PersistenceConfig controls persistent storage for Agents.
// Session and workspace persistence are configured independently.
type PersistenceConfig struct {
	// Sessions enables persistent storage for OpenCode session data (SQLite DB).
	// A PVC is created to store the session database, so conversation
	// history survives server pod restarts.
	// +optional
	Sessions *VolumePersistence `json:"sessions,omitempty"`

	// Workspace enables persistent storage for the workspace directory.
	// When enabled, git-cloned repos, AI-modified files, and in-progress work
	// survive server pod restarts. Without this, workspace uses EmptyDir and
	// is re-initialized on every restart (git repos re-cloned by init containers).
	// +optional
	Workspace *VolumePersistence `json:"workspace,omitempty"`
}

// VolumePersistence defines PVC configuration for a persistent volume.
type VolumePersistence struct {
	// StorageClassName for the PVC. If empty, uses cluster default StorageClass.
	// +optional
	StorageClassName *string `json:"storageClassName,omitempty"`

	// Size of the PVC.
	// If not specified, defaults to 1Gi for sessions and 10Gi for workspace.
	// +optional
	Size string `json:"size,omitempty"`
}

// ProxyConfig configures HTTP/HTTPS proxy settings for all containers in generated Pods.
// These environment variables are injected into every init container and worker container.
// The ".svc" and ".cluster.local" suffixes are always appended to NoProxy to prevent
// proxying in-cluster traffic.
type ProxyConfig struct {
	// HttpProxy is the URL of the HTTP proxy server.
	// Sets HTTP_PROXY and http_proxy environment variables.
	// Example: "http://proxy.corp.example.com:8080"
	// +optional
	HttpProxy string `json:"httpProxy,omitempty"` //nolint:staticcheck,revive // JSON API field name convention

	// HttpsProxy is the URL of the HTTPS proxy server.
	// Sets HTTPS_PROXY and https_proxy environment variables.
	// Example: "http://proxy.corp.example.com:8080"
	// +optional
	HttpsProxy string `json:"httpsProxy,omitempty"` //nolint:staticcheck,revive // JSON API field name convention

	// NoProxy is a comma-separated list of hosts that should bypass the proxy.
	// Sets NO_PROXY and no_proxy environment variables.
	// ".svc" and ".cluster.local" are always appended automatically.
	// Example: "localhost,127.0.0.1,10.0.0.0/8,.corp.example.com"
	// +optional
	NoProxy string `json:"noProxy,omitempty"`
}

// PluginTarget specifies which OpenCode plugin runtime to load the plugin into.
// +kubebuilder:validation:Enum=server;tui
type PluginTarget string

const (
	// PluginTargetServer loads the plugin into the OpenCode server runtime.
	// Server plugins run in the background within `opencode serve` and provide hooks
	// for tools, auth, events, message interception, etc.
	// Configuration is injected into opencode.json.
	PluginTargetServer PluginTarget = "server"

	// PluginTargetTUI loads the plugin into the OpenCode TUI runtime.
	// TUI plugins run when users interact with the Agent via `kubeoc agent attach`
	// or the web terminal, providing custom commands, themes, and UI extensions.
	// Configuration is injected into tui.json via OPENCODE_TUI_CONFIG.
	PluginTargetTUI PluginTarget = "tui"
)

// PluginSpec defines an OpenCode plugin to install from the npm registry.
// OpenCode plugins are async TypeScript functions that extend Agent capabilities.
//
// OpenCode supports two plugin targets:
//   - server: backend hooks (tools, auth, events, message interception) — runs in `opencode serve`
//   - tui: terminal UI extensions (commands, themes, UI slots) — runs during interactive sessions
//
// The controller creates a plugin-init container that runs `npm install` to download
// the plugin and all its dependencies into a shared /plugins volume. OpenCode loads
// plugins from file:///plugins/node_modules/{package} — the executor container does
// not need npm installed.
type PluginSpec struct {
	// Name is the npm package specifier of the plugin.
	// Supports standard npm specifier formats:
	//   "cc-safety-net"                    — latest version
	//   "cc-safety-net@0.8.2"             — specific version
	//   "@aexol/opencode-tui"             — scoped package
	//   "@aexol/opencode-tui@^1.0.0"      — version range
	//
	// +required
	// +kubebuilder:validation:MinLength=1
	Name string `json:"name"`

	// Target specifies which OpenCode plugin runtime to load this plugin into.
	// - "server" (default): loaded by `opencode serve`, provides backend hooks
	// - "tui": loaded during interactive terminal sessions, provides UI extensions
	//
	// +optional
	// +kubebuilder:default=server
	Target PluginTarget `json:"target,omitempty"`

	// Options is an arbitrary JSON object passed to the plugin function
	// as the second argument (PluginOptions).
	// Each plugin defines its own options schema.
	//
	// Example:
	//   options:
	//     endpoint: "http://otel-collector:4318"
	//     verbose: true
	//
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	// +kubebuilder:validation:Schemaless
	Options *runtime.RawExtension `json:"options,omitempty"`
}

// AgentTemplateReference is a reference to an AgentTemplate in the same namespace.
type AgentTemplateReference struct {
	// Name of the AgentTemplate.
	// +required
	Name string `json:"name"`
}

// AgentSpec defines agent configuration
type AgentSpec struct {
	// TemplateRef references an AgentTemplate in the same namespace.
	// When set, the Agent inherits configuration from the template.
	// Agent-level fields override template values (scalar: non-zero wins; lists: replace).
	// +optional
	TemplateRef *AgentTemplateReference `json:"templateRef,omitempty"`

	// Profile is a brief, human-readable summary of the Agent's purpose and capabilities.
	// This is for documentation and discovery only — it has no functional effect on execution.
	// Visible via `kubectl get agents -o wide` for quick identification.
	//
	// Example:
	//   profile: "Full-stack development agent with GitHub and AWS access"
	// +optional
	Profile string `json:"profile,omitempty"`

	// AgentImage specifies the OpenCode init container image.
	// This image contains the OpenCode binary that gets copied to /tools volume.
	// The init container runs this image and copies the opencode binary to /tools/opencode.
	// If not specified, defaults to "ghcr.io/kubeopencode/kubeopencode-agent-opencode:latest".
	// +optional
	AgentImage string `json:"agentImage,omitempty"`

	// ExecutorImage specifies the main worker container image for task execution.
	// This is the development environment where tasks actually run.
	// The container uses /tools/opencode (provided by agentImage init container) to execute AI tasks.
	// If not specified, defaults to "ghcr.io/kubeopencode/kubeopencode-agent-devbox:latest".
	// +optional
	ExecutorImage string `json:"executorImage,omitempty"`

	// AttachImage specifies the lightweight image used for --attach Pods.
	// Tasks using agentRef create Pods that run `opencode run --attach <server-url>`.
	// These Pods only need the OpenCode binary and network access, not the full development
	// environment. Using a minimal image (~25MB) instead of devbox (~1GB) significantly
	// reduces image pull time and resource usage.
	//
	// If not specified, defaults to "ghcr.io/kubeopencode/kubeopencode-agent-attach:latest".
	// +optional
	AttachImage string `json:"attachImage,omitempty"`

	// WorkspaceDir specifies the working directory inside the agent container.
	// This is where task.md and context files are mounted.
	// The agent image must support the WORKSPACE_DIR environment variable.
	// When templateRef is set, this field is inherited from the template if not specified.
	// +optional
	// +kubebuilder:validation:Pattern=`^/.*`
	WorkspaceDir string `json:"workspaceDir,omitempty"`

	// Command specifies the entrypoint command for the agent container.
	// This is optional and overrides the default ENTRYPOINT of the container image.
	//
	// If not specified, defaults to:
	//   ["sh", "-c", "/tools/opencode run \"$(cat ${WORKSPACE_DIR}/task.md)\""]
	//
	// The command defines HOW the agent executes tasks. Most users should not
	// need to customize this. Override only if you need custom execution behavior.
	//
	// ## Example
	//
	//   command: ["sh", "-c", "/tools/opencode run --format json \"$(cat /workspace/task.md)\""]
	//
	// +optional
	Command []string `json:"command,omitempty"`

	// Contexts provides default contexts for all tasks using this Agent.
	// These have the lowest priority in context merging.
	//
	// Context priority (lowest to highest):
	//   1. Agent.contexts (Agent-level defaults)
	//   2. Task.contexts (Task-specific contexts)
	//   3. Task.description (highest, becomes ${WORKSPACE_DIR}/task.md)
	//
	// Use this for organization-wide defaults like coding standards, security policies,
	// or common tool configurations that should apply to all tasks.
	// +optional
	Contexts []ContextItem `json:"contexts,omitempty"`

	// Skills defines external skill sources for the Agent.
	// Skills are SKILL.md files organized as one-folder-per-skill in Git repositories.
	// The controller clones skill repositories and auto-injects skills.paths into
	// the OpenCode configuration so skills are discovered automatically.
	//
	// Skills are semantically different from Contexts:
	//   - Contexts provide knowledge ("what the agent knows")
	//   - Skills provide capabilities ("what the agent can do")
	//
	// When templateRef is set, Agent skills replace template skills (same as contexts).
	// +optional
	Skills []SkillSource `json:"skills,omitempty"`

	// Plugins declares OpenCode plugins to load for this Agent.
	// The controller merges these into the OpenCode configuration's plugin array.
	// Plugin credentials should be provided via spec.credentials (as env vars).
	//
	// Plugins extend Agent capabilities through OpenCode's hook system:
	// custom tools, auth providers, message interception, event handling, etc.
	//
	// When templateRef is set, Agent plugins replace template plugins (same merge
	// strategy as contexts, skills, and credentials).
	//
	// Example:
	//   plugins:
	//     - name: "@kubeopencode/opencode-slack-plugin"
	//     - name: "opencode-plugin-otel"
	//       options:
	//         endpoint: "http://otel-collector:4318"
	// +optional
	Plugins []PluginSpec `json:"plugins,omitempty"`

	// Config provides OpenCode configuration as an inline object.
	// This configuration is written to /tools/opencode.json and the OPENCODE_CONFIG
	// environment variable is set to point to this file.
	//
	// The config should be a valid JSON object compatible with OpenCode's config schema.
	// See: https://opencode.ai/config.json for the schema.
	//
	// Example:
	//   config:
	//     model: opencode/big-pickle
	//     small_model: opencode/big-pickle
	//
	// +optional
	// +kubebuilder:pruning:PreserveUnknownFields
	// +kubebuilder:validation:Schemaless
	Config *runtime.RawExtension `json:"config,omitempty"`

	// Credentials defines secrets that should be available to the agent.
	// Similar to GitHub Actions secrets, these can be mounted as files or
	// exposed as environment variables.
	//
	// Example use cases:
	//   - GitHub token for repository access (env: GITHUB_TOKEN)
	//   - SSH keys for git operations (file: ~/.ssh/id_rsa)
	//   - API keys for external services (env: ANTHROPIC_API_KEY)
	//   - Cloud credentials (file: ~/.config/gcloud/credentials.json)
	// +optional
	Credentials []Credential `json:"credentials,omitempty"`

	// PodSpec defines advanced Pod configuration for agent pods.
	// This includes labels, scheduling, runtime class, and other Pod-level settings.
	// Use this for fine-grained control over how agent pods are created.
	// +optional
	PodSpec *AgentPodSpec `json:"podSpec,omitempty"`

	// ServiceAccountName specifies the Kubernetes ServiceAccount to use for agent pods.
	// This controls what cluster resources the agent can access via RBAC.
	//
	// The ServiceAccount must exist in the Agent's namespace (where Pods run).
	// Users are responsible for creating the ServiceAccount and appropriate RBAC bindings
	// based on what permissions their agent needs.
	// When templateRef is set, this field is inherited from the template if not specified.
	//
	// +optional
	ServiceAccountName string `json:"serviceAccountName,omitempty"`

	// MaxConcurrentTasks limits the number of Tasks that can run concurrently
	// using this Agent. When the limit is reached, new Tasks will enter Queued
	// phase until capacity becomes available.
	//
	// This is useful when the Agent uses backend AI services with rate limits
	// (e.g., Claude, Gemini API quotas) to prevent overwhelming the service.
	//
	// - nil or 0: unlimited (default behavior, no concurrency limit)
	// - positive number: maximum number of Tasks that can be in Running phase
	//
	// Example:
	//   maxConcurrentTasks: 3  # Only 3 Tasks can run at once
	// +optional
	MaxConcurrentTasks *int32 `json:"maxConcurrentTasks,omitempty"`

	// Quota defines rate limiting for Task starts within a sliding time window.
	// When configured, Tasks will be queued if the quota is exceeded.
	// This is complementary to maxConcurrentTasks:
	//   - maxConcurrentTasks: limits how many Tasks run at once
	//   - quota: limits how quickly new Tasks can start
	//
	// Example:
	//   quota:
	//     maxTaskStarts: 10
	//     windowSeconds: 3600  # 10 tasks per hour
	// +optional
	Quota *QuotaConfig `json:"quota,omitempty"`

	// CABundle configures custom CA certificates for TLS verification.
	// The CA bundle is mounted into all init containers (git-init, url-fetch, context-init)
	// and the worker container, enabling HTTPS access to servers using private/self-signed CAs.
	//
	// Compatible with cert-manager trust-manager Bundle resources (ConfigMap with "ca-bundle.crt" key).
	//
	// Example:
	//   caBundle:
	//     configMapRef:
	//       name: custom-ca-bundle
	//       key: ca-bundle.crt
	// +optional
	CABundle *CABundleConfig `json:"caBundle,omitempty"`

	// Proxy configures HTTP/HTTPS proxy settings for all containers in generated Pods.
	// When set, HTTP_PROXY, HTTPS_PROXY, NO_PROXY (and lowercase variants) environment
	// variables are injected into all init containers and the worker container.
	//
	// This is useful in enterprise environments where all outbound traffic must go
	// through a corporate proxy server.
	//
	// Agent-level proxy overrides cluster-level proxy from KubeOpenCodeConfig.
	//
	// Example:
	//   proxy:
	//     httpProxy: "http://proxy.corp.example.com:8080"
	//     httpsProxy: "http://proxy.corp.example.com:8080"
	//     noProxy: "localhost,127.0.0.1,10.0.0.0/8"
	// +optional
	Proxy *ProxyConfig `json:"proxy,omitempty"`

	// ImagePullSecrets is a list of references to secrets for pulling container images
	// from private registries. These are added to the Pod spec's imagePullSecrets field.
	//
	// This is useful when agentImage, executorImage, or attachImage are hosted in
	// private registries that require authentication (e.g., Harbor, AWS ECR, GCR).
	//
	// The referenced Secrets must exist in the same namespace as the Agent and be
	// of type kubernetes.io/dockerconfigjson.
	//
	// Example:
	//   imagePullSecrets:
	//     - name: harbor-registry-secret
	//     - name: gcr-secret
	// +optional
	ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`

	// Port is the port OpenCode server listens on inside the Agent's Deployment.
	// Tasks connect to the Agent via this port using `opencode run --attach`.
	// Defaults to 4096 if not specified.
	// +optional
	// +kubebuilder:default=4096
	// +kubebuilder:validation:Minimum=1
	// +kubebuilder:validation:Maximum=65535
	Port int32 `json:"port,omitempty"`

	// ExtraPorts defines additional ports to expose on the Agent's Service and Deployment.
	// This is useful for DinD scenarios where containers running inside the agent
	// need to be accessible from outside (e.g., web application UIs, VS Code server,
	// database ports for debugging).
	//
	// Each extra port is added to both the Deployment's container ports and the
	// Agent's Service. Users can then access them via kubectl port-forward or Ingress.
	//
	// Example:
	//   extraPorts:
	//     - name: webapp
	//       port: 3000
	//     - name: vscode
	//       port: 8080
	// +optional
	ExtraPorts []ExtraPort `json:"extraPorts,omitempty"`

	// Persistence configures persistent storage for the Agent.
	// When set, session data (and optionally workspace files) survive pod restarts.
	// +optional
	Persistence *PersistenceConfig `json:"persistence,omitempty"`

	// Suspend scales the Agent's Deployment to 0 replicas when true.
	// The Agent is stopped but PVCs and Service are retained, so it
	// can be resumed without data loss. Tasks targeting a suspended Agent
	// enter Queued phase until the Agent is resumed.
	//
	// This field can be set by users (via kubectl/UI) or by the controller
	// when standby is configured (automatic lifecycle management).
	//
	// Similar to Kubernetes CronJob's spec.suspend field.
	// +optional
	Suspend bool `json:"suspend,omitempty"`

	// Standby configures automatic suspend/resume lifecycle management.
	// When configured, the controller automatically:
	//   - Suspends the Agent (sets spec.suspend=true) after idleTimeout with no active Tasks
	//   - Resumes the Agent (sets spec.suspend=false) when a new Task arrives
	//
	// Without standby, spec.suspend is controlled only by the user.
	//
	// Example:
	//   standby:
	//     idleTimeout: "30m"   # Auto-suspend after 30 minutes idle
	// +optional
	Standby *StandbyConfig `json:"standby,omitempty"`

	// Share configures a shareable terminal link for this Agent.
	// When enabled, the controller generates a cryptographic share token and stores it
	// in a Secret. The KubeOpenCode server exposes a standalone terminal page at
	// /s/{token} that requires no Kubernetes credentials.
	//
	// This enables Agent developers to share terminal access with "Agent Consumers"
	// (e.g., QA engineers, stakeholders) who don't have cluster access.
	//
	// Example:
	//   share:
	//     enabled: true
	//     expiresAt: "2026-05-01T00:00:00Z"
	// +optional
	Share *ShareConfig `json:"share,omitempty"`
}

// AgentStatus defines the observed state of Agent
type AgentStatus struct {
	// ObservedGeneration is the most recent generation observed by the controller.
	// +optional
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`

	// Conditions represent the latest available observations of the Agent's state.
	// +optional
	// +listType=map
	// +listMapKey=type
	Conditions []metav1.Condition `json:"conditions,omitempty"`

	// TaskStartHistory tracks recent Task starts for quota enforcement.
	// The controller prunes entries older than the quota window automatically.
	// This is only populated when quota is configured on the Agent.
	// +optional
	// +listType=atomic
	TaskStartHistory []TaskStartRecord `json:"taskStartHistory,omitempty"`

	// DeploymentName is the name of the Kubernetes Deployment running the Agent.
	// Format: "{agent-name}-server"
	// +optional
	DeploymentName string `json:"deploymentName,omitempty"`

	// ServiceName is the name of the Kubernetes Service exposing the Agent.
	// Format: "{agent-name}"
	// +optional
	ServiceName string `json:"serviceName,omitempty"`

	// URL is the in-cluster URL to reach the Agent's OpenCode server.
	// Format: "http://{service-name}.{namespace}.svc.cluster.local:{port}"
	// Tasks use this URL with `opencode run --attach` to connect to the Agent.
	// +optional
	URL string `json:"url,omitempty"`

	// Ready indicates whether the Agent's Deployment is ready to accept tasks.
	// +optional
	Ready bool `json:"ready,omitempty"`

	// Suspended mirrors spec.suspend for observability.
	// When true, Ready is always false. Use this to distinguish "suspended"
	// from "not ready due to an issue".
	// +optional
	Suspended bool `json:"suspended,omitempty"`

	// IdleSince records when the Agent became idle (no running or queued Tasks).
	// Nil when Tasks are active. Used with spec.standby to determine
	// when to auto-suspend.
	// +optional
	IdleSince *metav1.Time `json:"idleSince,omitempty"`

	// GitSyncStatuses tracks the sync state of each Git context with sync enabled.
	// +optional
	// +listType=atomic
	GitSyncStatuses []GitSyncStatus `json:"gitSyncStatuses,omitempty"`

	// Share contains the status of the share link feature.
	// +optional
	Share *ShareStatus `json:"share,omitempty"`
}

// GitSyncStatus tracks the observed sync state of a single Git context.
type GitSyncStatus struct {
	// Name is the context name.
	Name string `json:"name"`

	// CommitHash is the last observed remote commit hash that has been
	// successfully applied (either via sidecar pull or deployment rollout).
	CommitHash string `json:"commitHash,omitempty"`

	// LastSynced is the timestamp of the last successful sync check.
	LastSynced *metav1.Time `json:"lastSynced,omitempty"`
}

// StandbyConfig configures automatic suspend/resume lifecycle management for an Agent.
// When configured on an Agent, the controller manages spec.suspend automatically:
// it suspends the Agent after idleTimeout with no active Tasks, and resumes it
// when a new Task arrives.
type StandbyConfig struct {
	// IdleTimeout is the duration after which the Agent is automatically suspended
	// when there are no running or queued Tasks.
	//
	// Example: "30m", "1h"
	IdleTimeout metav1.Duration `json:"idleTimeout"`
}

// ShareConfig configures a shareable terminal link for an Agent.
// When enabled, the controller generates a cryptographic token stored in a Secret,
// and the KubeOpenCode server exposes a standalone terminal page at /s/{token}
// that requires no Kubernetes credentials.
type ShareConfig struct {
	// Enabled enables the share link feature.
	// When set to true, the controller generates a share token and stores it in a Secret.
	// When set to false (or the field is removed), the controller deletes the share Secret.
	Enabled bool `json:"enabled"`

	// ExpiresAt is the optional expiry time for the share link.
	// After this time, the link becomes invalid and the controller marks status.share.active as false.
	// The token Secret is retained (not deleted) so re-enabling with a new expiresAt reuses the same token.
	// +optional
	ExpiresAt *metav1.Time `json:"expiresAt,omitempty"`

	// AllowedIPs is an optional list of CIDR ranges allowed to access the share link.
	// When set, the server validates the client IP against these ranges.
	// Empty means all IPs are allowed.
	//
	// Example:
	//   allowedIPs: ["10.0.0.0/8", "192.168.1.0/24"]
	// +optional
	AllowedIPs []string `json:"allowedIPs,omitempty"`
}

// ShareStatus represents the observed state of the share link feature.
type ShareStatus struct {
	// SecretName is the name of the Secret containing the share token.
	// Format: "{agent-name}-share"
	// +optional
	SecretName string `json:"secretName,omitempty"`

	// URL is the full shareable URL.
	// Only set when the server's external URL is configured in KubeOpenCodeConfig.
	// +optional
	URL string `json:"url,omitempty"`

	// Active indicates whether the share link is currently valid.
	// False when: share is disabled, expired, or Agent is not ready.
	// +optional
	Active bool `json:"active,omitempty"`
}

// ExtraPort defines an additional port to expose on the Agent's Service and Deployment.
// This enables access to services running inside the agent container, such as
// web applications started via Docker-in-Docker, VS Code server, or database ports.
type ExtraPort struct {
	// Name is the name of this port within the Service.
	// Must be unique among all ports (including the main OpenCode server port).
	// +required
	// +kubebuilder:validation:MinLength=1
	// +kubebuilder:validation:MaxLength=15
	Name string `json:"name"`

	// Port is the port number to expose on both the container and the Service.
	// +required
	// +kubebuilder:validation:Minimum=1
	// +kubebuilder:validation:Maximum=65535
	Port int32 `json:"port"`

	// Protocol defaults to TCP.
	// +optional
	// +kubebuilder:default=TCP
	// +kubebuilder:validation:Enum=TCP;UDP
	Protocol corev1.Protocol `json:"protocol,omitempty"`
}

// InitContainerOverrides allows injecting extra environment variables and
// volume mounts into a specific KubeOpenCode-managed init or sidecar container.
// This is useful for enterprise environments (e.g., OpenShift with SCC) that
// require custom env vars such as HOME, or need to mount additional secrets
// into specific system containers.
type InitContainerOverrides struct {
	// ExtraEnv defines additional environment variables for this container.
	// These are appended after the controller-managed env vars, so they can
	// override controller defaults (e.g., HOME, SHELL) if needed.
	//
	// Supports all Kubernetes EnvVar sources: raw values, SecretKeyRef, ConfigMapKeyRef,
	// and fieldRef.
	//
	// Example — fix HOME for OpenShift SCC compatibility:
	//   extraEnv:
	//     - name: HOME
	//       value: /tmp
	//
	// Example — inject a secret value:
	//   extraEnv:
	//     - name: GIT_TOKEN
	//       valueFrom:
	//         secretKeyRef:
	//           name: my-git-secret
	//           key: token
	//
	// +optional
	ExtraEnv []corev1.EnvVar `json:"extraEnv,omitempty"`

	// ExtraVolumeMounts defines additional volume mounts for this container.
	// Referenced volumes must be declared in podSpec.extraVolumes.
	//
	// Example — mount a corporate CA bundle into git-init:
	//   extraVolumeMounts:
	//     - name: corp-ca
	//       mountPath: /etc/ssl/corp
	//       readOnly: true
	//
	// +optional
	ExtraVolumeMounts []corev1.VolumeMount `json:"extraVolumeMounts,omitempty"`
}

// SystemContainerOverrides configures per-container-type overrides for
// KubeOpenCode-managed system containers (init containers and git-sync sidecars).
// Use this when you need different env vars or volume mounts on specific
// container types rather than all containers.
//
// Container types and their roles:
//   - openCodeInit: copies the OpenCode binary from agentImage to /tools
//   - contextInit: copies ConfigMap content into the writable workspace
//   - gitInit: clones Git repositories for Git contexts (all git-init-* containers)
//   - gitSync: periodically syncs Git repositories (HotReload policy sidecars)
//   - pluginInit: installs OpenCode plugins via npm
type SystemContainerOverrides struct {
	// OpenCodeInit configures overrides for the opencode-init init container.
	// This container copies the OpenCode binary from agentImage to /tools volume.
	// +optional
	OpenCodeInit *InitContainerOverrides `json:"openCodeInit,omitempty"`

	// ContextInit configures overrides for the context-init init container.
	// This container copies ConfigMap content into the writable workspace directory.
	// +optional
	ContextInit *InitContainerOverrides `json:"contextInit,omitempty"`

	// GitInit configures overrides applied to ALL git-init-* init containers.
	// These containers clone Git repositories for Git-type contexts.
	//
	// This is the recommended place to fix HOME for OpenShift SCC environments,
	// as git-init containers need a writable home directory for git credential helpers.
	//
	// Example:
	//   gitInit:
	//     extraEnv:
	//       - name: HOME
	//         value: /tmp
	//
	// +optional
	GitInit *InitContainerOverrides `json:"gitInit,omitempty"`

	// GitSync configures overrides applied to ALL git-sync-* sidecar containers.
	// These containers periodically pull Git repositories when sync.policy is HotReload.
	// +optional
	GitSync *InitContainerOverrides `json:"gitSync,omitempty"`

	// PluginInit configures overrides for the plugin-init init container.
	// This container installs OpenCode plugins via npm into the shared /plugins volume.
	// +optional
	PluginInit *InitContainerOverrides `json:"pluginInit,omitempty"`
}

// AgentPodSpec defines advanced Pod configuration for agent pods.
// This groups all Pod-level settings that control how the agent container runs.
// These settings apply to the Agent's Deployment and to Task Pods.
type AgentPodSpec struct {
	// Labels defines additional labels to add to the agent pod.
	// These labels are applied to the Pod template and enable integration with:
	//   - NetworkPolicy podSelector for network isolation
	//   - Service selector for service discovery
	//   - PodMonitor/ServiceMonitor for Prometheus monitoring
	//   - Any other label-based pod selection
	//
	// Example: To make pods match a NetworkPolicy with podSelector:
	//   labels:
	//     network-policy: agent-restricted
	// +optional
	Labels map[string]string `json:"labels,omitempty"`

	// Scheduling defines pod scheduling configuration for agent pods.
	// This includes node selection, tolerations, and affinity rules.
	// +optional
	Scheduling *PodScheduling `json:"scheduling,omitempty"`

	// RuntimeClassName specifies the RuntimeClass to use for agent pods.
	// RuntimeClass provides a way to select container runtime configurations
	// such as gVisor (runsc) or Kata Containers for enhanced isolation.
	//
	// This is useful when running untrusted AI agent code that may generate
	// and execute arbitrary commands. Using gVisor or Kata provides an
	// additional layer of security beyond standard container isolation.
	//
	// The RuntimeClass must exist in the cluster before use.
	// Common values: "gvisor", "kata", "runc" (default if not specified)
	//
	// Example:
	//   runtimeClassName: gvisor
	//
	// See: https://kubernetes.io/docs/concepts/containers/runtime-class/
	// +optional
	RuntimeClassName *string `json:"runtimeClassName,omitempty"`

	// Resources specifies the compute resources (CPU, memory) for the agent container.
	// This applies to both the Agent's Deployment and Task Pods.
	// If not specified, uses the cluster's default resource limits.
	//
	// Example:
	//   resources:
	//     requests:
	//       memory: "512Mi"
	//       cpu: "500m"
	//     limits:
	//       memory: "2Gi"
	//       cpu: "2"
	// +optional
	Resources *corev1.ResourceRequirements `json:"resources,omitempty"`

	// SecurityContext defines the security options for the agent container.
	// This is applied to the worker container (and init containers where applicable).
	//
	// If not specified, a restricted default is applied:
	//   - allowPrivilegeEscalation: false
	//   - capabilities: drop ALL
	//   - seccompProfile: RuntimeDefault
	//
	// Enterprise users can further tighten this with:
	//   - runAsNonRoot: true
	//   - readOnlyRootFilesystem: true (requires emptyDir volumes for writable paths)
	//
	// Example:
	//   securityContext:
	//     runAsNonRoot: true
	//     allowPrivilegeEscalation: false
	// +optional
	SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`

	// PodSecurityContext defines pod-level security attributes and common container settings.
	// Applied to the Pod spec directly (affects all containers).
	//
	// Example:
	//   podSecurityContext:
	//     runAsUser: 1000
	//     runAsGroup: 1000
	//     fsGroup: 1000
	// +optional
	PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`

	// Lifecycle describes actions that the management system should take in response
	// to container lifecycle events. This is applied to the executor (worker) container.
	//
	// Common use case: start a sidecar process (e.g., code-server, language server)
	// alongside the main OpenCode process using a postStart hook.
	//
	// Note: postStart runs in parallel with the container's entrypoint (not sequentially).
	// If the postStart handler fails, the container is killed and restarted.
	// The handler should complete quickly — use background processes (&) for long-running services.
	//
	// Example — start code-server alongside OpenCode:
	//   lifecycle:
	//     postStart:
	//       exec:
	//         command: ["/usr/local/bin/start-code-server.sh"]
	//
	// See: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/
	// +optional
	Lifecycle *corev1.Lifecycle `json:"lifecycle,omitempty"`

	// ExtraVolumes defines additional volumes to add to the agent pod.
	// These volumes are appended to the pod's volume list alongside
	// the controller-managed volumes (tools, workspace, etc.).
	//
	// Use this to mount ConfigMaps, Secrets, PVCs, NFS shares, or any other
	// Kubernetes volume types into agent pods. Combined with ExtraVolumeMounts,
	// this enables scenarios like:
	//   - Mounting shared skills from a PVC or NFS share
	//   - Providing configuration files from ConfigMaps
	//   - Attaching external storage for large datasets
	//
	// Example — mount shared skills from NFS:
	//   extraVolumes:
	//     - name: shared-skills
	//       nfs:
	//         server: nfs.example.com
	//         path: /exports/skills
	//
	// +optional
	ExtraVolumes []corev1.Volume `json:"extraVolumes,omitempty"`

	// ExtraVolumeMounts defines additional volume mounts for the executor (worker) container.
	// Each mount must reference a volume defined in ExtraVolumes by name.
	//
	// Mounts are applied to the main executor container only (not init containers).
	// To mount volumes into init containers, use podSpec.systemContainers.*.extraVolumeMounts.
	//
	// Example — mount shared skills into the OpenCode skills directory:
	//   extraVolumeMounts:
	//     - name: shared-skills
	//       mountPath: /workspace/.opencode/skills
	//       readOnly: true
	//
	// +optional
	ExtraVolumeMounts []corev1.VolumeMount `json:"extraVolumeMounts,omitempty"`

	// ExtraEnv defines additional environment variables injected into ALL containers
	// in the agent pod — every init container and the executor container.
	// These are appended after controller-managed env vars, so they can override
	// controller defaults (e.g., HOME, SHELL) when necessary.
	//
	// Supports all Kubernetes EnvVar sources: raw values, SecretKeyRef, ConfigMapKeyRef,
	// and fieldRef.
	//
	// For per-container-type targeting, use podSpec.systemContainers instead.
	//
	// Example — inject a corporate registry URL into all containers:
	//   extraEnv:
	//     - name: CORPORATE_REGISTRY
	//       value: registry.corp.example.com
	//     - name: MY_API_KEY
	//       valueFrom:
	//         secretKeyRef:
	//           name: api-keys
	//           key: my-api-key
	//
	// +optional
	ExtraEnv []corev1.EnvVar `json:"extraEnv,omitempty"`

	// SystemContainers configures per-container-type overrides for KubeOpenCode-managed
	// system containers (init containers and git-sync sidecars).
	// Use this for fine-grained control when you need different env vars or volume mounts
	// on specific container types rather than all containers.
	//
	// This is particularly useful for OpenShift SCC environments where git-init and
	// git-sync containers need HOME=/tmp set explicitly:
	//
	//   systemContainers:
	//     gitInit:
	//       extraEnv:
	//         - name: HOME
	//           value: /tmp
	//     gitSync:
	//       extraEnv:
	//         - name: HOME
	//           value: /tmp
	//
	// +optional
	SystemContainers *SystemContainerOverrides `json:"systemContainers,omitempty"`
}

// PodScheduling defines scheduling configuration for agent pods.
// All fields are applied directly to the Pod template.
type PodScheduling struct {
	// NodeSelector specifies a selector for scheduling pods to specific nodes.
	// The pod will only be scheduled to nodes that have all the specified labels.
	//
	// Example:
	//   nodeSelector:
	//     kubernetes.io/os: linux
	//     node-type: gpu
	// +optional
	NodeSelector map[string]string `json:"nodeSelector,omitempty"`

	// Tolerations allows pods to be scheduled on nodes with matching taints.
	//
	// Example:
	//   tolerations:
	//     - key: "dedicated"
	//       operator: "Equal"
	//       value: "ai-workload"
	//       effect: "NoSchedule"
	// +optional
	Tolerations []corev1.Toleration `json:"tolerations,omitempty"`

	// Affinity specifies affinity and anti-affinity rules for pods.
	// This enables advanced scheduling based on node attributes, pod co-location,
	// or pod anti-affinity for high availability.
	//
	// Example:
	//   affinity:
	//     nodeAffinity:
	//       requiredDuringSchedulingIgnoredDuringExecution:
	//         nodeSelectorTerms:
	//           - matchExpressions:
	//               - key: topology.kubernetes.io/zone
	//                 operator: In
	//                 values: ["us-west-2a", "us-west-2b"]
	// +optional
	Affinity *corev1.Affinity `json:"affinity,omitempty"`
}

// Credential represents a secret that should be available to the agent.
// Each credential references a Kubernetes Secret and specifies how to expose it.
//
// Mounting behavior depends on whether SecretRef.Key is specified:
//
// 1. No Key specified + No MountPath: entire Secret as environment variables
// 2. No Key specified + MountPath: entire Secret as directory (each key becomes a file)
// 3. Key specified + Env: single key as environment variable
// 4. Key specified + MountPath: single key as file
// +kubebuilder:validation:XValidation:rule="!has(self.env) || has(self.secretRef.key)",message="env can only be set when secretRef.key is specified"
type Credential struct {
	// Name is a descriptive name for this credential (for documentation purposes).
	// +required
	Name string `json:"name"`

	// SecretRef references the Kubernetes Secret containing the credential.
	// +required
	SecretRef SecretReference `json:"secretRef"`

	// MountPath specifies where to mount the secret.
	// - If SecretRef.Key is specified: mounts the single key's value as a file at this path.
	//   Example: "/home/agent/.ssh/id_rsa" for SSH keys
	// - If SecretRef.Key is not specified: mounts the entire Secret as a directory,
	//   where each key in the Secret becomes a file in the directory.
	//   Example: "/etc/ssl/certs" for a Secret containing ca.crt, client.crt, client.key
	// +optional
	MountPath *string `json:"mountPath,omitempty"`

	// Env specifies the environment variable name to expose the secret value.
	// Only applicable when SecretRef.Key is specified.
	// If specified, the secret key's value is set as this environment variable.
	// Example: "GITHUB_TOKEN" for GitHub API access
	// +optional
	Env *string `json:"env,omitempty"`

	// FileMode specifies the permission mode for mounted files.
	// Only applicable when MountPath is specified.
	// Defaults to 0600 (read/write for owner only) for security.
	// Use 0400 for read-only files like SSH keys.
	// +optional
	FileMode *int32 `json:"fileMode,omitempty"`
}

// SecretReference references a Kubernetes Secret.
// When Key is specified, only that specific key is used.
// When Key is omitted, the entire Secret is used (behavior depends on Credential.MountPath).
type SecretReference struct {
	// Name of the Secret.
	// +required
	Name string `json:"name"`

	// Key of the Secret to select.
	// If not specified, the entire Secret is used:
	// - With MountPath: mounted as a directory (each key becomes a file)
	// - Without MountPath: all keys become environment variables
	// When Key is omitted, the Env field on the Credential is ignored.
	// +optional
	Key *string `json:"key,omitempty"`
}

// CABundleConfig configures custom CA certificates for TLS verification.
// The CA bundle is mounted into all init containers and the worker container,
// enabling access to HTTPS services that use private or self-signed certificates.
//
// Exactly one of ConfigMapRef or SecretRef must be specified.
// +kubebuilder:validation:XValidation:rule="has(self.configMapRef) || has(self.secretRef)",message="either configMapRef or secretRef must be specified"
// +kubebuilder:validation:XValidation:rule="!(has(self.configMapRef) && has(self.secretRef))",message="only one of configMapRef or secretRef can be specified"
type CABundleConfig struct {
	// ConfigMapRef references a ConfigMap containing PEM-encoded CA certificates.
	// Compatible with cert-manager trust-manager Bundle resources.
	// +optional
	ConfigMapRef *CABundleReference `json:"configMapRef,omitempty"`

	// SecretRef references a Secret containing PEM-encoded CA certificates.
	// +optional
	SecretRef *CABundleReference `json:"secretRef,omitempty"`
}

// CABundleReference references a ConfigMap or Secret containing a CA bundle.
type CABundleReference struct {
	// Name of the ConfigMap or Secret.
	// +required
	Name string `json:"name"`

	// Key containing the PEM-encoded CA bundle.
	// Defaults to "ca-bundle.crt" for ConfigMapRef, "ca.crt" for SecretRef.
	// +optional
	Key string `json:"key,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// AgentList contains a list of Agent
type AgentList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []Agent `json:"items"`
}
