package store

// Registry represents a container image registry.
type Registry struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	URL       string `json:"url"`
	Type      string `json:"type"`
	Token     string `json:"token"`
	Owner     string `json:"owner"`
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

// Settings holds global application configuration (single-row pattern).
type Settings struct {
	Domain                string `json:"domain"`
	ServerIP              string `json:"server_ip"` // Docker host IP (for NPM remote forwarding)
	PublicIP              string `json:"public_ip"` // Public-facing IP for DNS A records (e.g., NPM/proxy host)
	Network               string `json:"network"`
	SubdomainPattern      string `json:"subdomain_pattern"`
	NotificationURL       string `json:"notification_url"`
	NotificationSecret    string `json:"-"` // outgoing-webhook signing secret; never serialized directly
	NpmURL                string `json:"npm_url"`
	NpmEmail              string `json:"npm_email"`
	NpmPassword           string `json:"npm_password"`
	PollingInterval       string `json:"polling_interval"`
	BaseVolumePath        string `json:"base_volume_path"`
	SSLCertificateID      int    `json:"ssl_certificate_id"`
	StaleThresholdDays    int    `json:"stale_threshold_days"`
	AllowedVolumePaths    string `json:"allowed_volume_paths"` // JSON array of allowed absolute paths
	WildcardDNS           bool   `json:"wildcard_dns"`
	DNSProvider           string `json:"dns_provider"`
	CloudflareAPIToken    string `json:"cloudflare_api_token"`
	CloudflareZoneID      string `json:"cloudflare_zone_id"`
	NpmRemote             bool   `json:"npm_remote"`
	NpmAccessListID       int    `json:"npm_access_list_id"`
	ProxyProvider         string `json:"proxy_provider"`
	TraefikEntrypoint     string `json:"traefik_entrypoint"`
	TraefikCertResolver   string `json:"traefik_cert_resolver"`
	TraefikNetwork        string `json:"traefik_network"`
	TraefikAPIURL         string `json:"traefik_api_url"`
	ImagePruneThresholdMB int    `json:"image_prune_threshold_mb"`
	BackupEnabled         bool   `json:"backup_enabled"`
	BackupIntervalHours   int    `json:"backup_interval_hours"`
	BackupRetentionCount  int    `json:"backup_retention_count"`
	// AutoBackupBeforeDeploy creates a "pre-deploy" Tinyforge DB backup
	// at the start of every project deploy. Independent of BackupEnabled
	// (which governs the periodic auto-backup cron).
	AutoBackupBeforeDeploy bool   `json:"auto_backup_before_deploy"`
	StatsIntervalSeconds   int    `json:"stats_interval_seconds"` // 0 disables collection
	StatsRetentionHours    int    `json:"stats_retention_hours"`  // 0 disables collection
	UpdatedAt              string `json:"updated_at"`
}

// ContainerStatsSample is one persisted sample of container resource usage.
// Cumulative counters (network, block I/O) require differencing two samples
// to get rates; CPU is already a percent-since-previous-sample value.
type ContainerStatsSample struct {
	ContainerID     string  `json:"container_id"`
	OwnerType       string  `json:"owner_type"` // "instance" or "site"
	OwnerID         string  `json:"owner_id"`
	TS              int64   `json:"ts"` // Unix seconds UTC
	CPUPercent      float64 `json:"cpu_percent"`
	MemoryUsage     int64   `json:"memory_usage"`
	MemoryLimit     int64   `json:"memory_limit"`
	NetworkRxBytes  int64   `json:"network_rx_bytes"`
	NetworkTxBytes  int64   `json:"network_tx_bytes"`
	BlockReadBytes  int64   `json:"block_read_bytes"`
	BlockWriteBytes int64   `json:"block_write_bytes"`
}

// SystemStatsSample is one persisted host-level snapshot that aggregates
// workload usage across all containers plus daemon capacity + disk totals.
type SystemStatsSample struct {
	TS                 int64   `json:"ts"` // Unix seconds UTC
	NCPU               int     `json:"ncpu"`
	MemoryTotal        int64   `json:"memory_total"`
	WorkloadCPUPercent float64 `json:"workload_cpu_percent"`
	WorkloadMemUsage   int64   `json:"workload_mem_usage"`
	ContainersRunning  int     `json:"containers_running"`
	DiskTotalBytes     int64   `json:"disk_total_bytes"`
}

// Backup represents a backup metadata record.
type Backup struct {
	ID         string `json:"id"`
	Filename   string `json:"filename"`
	SizeBytes  int64  `json:"size_bytes"`
	BackupType string `json:"backup_type"` // "manual" or "auto"
	CreatedAt  string `json:"created_at"`
}

// VolumeSnapshot is one captured archive of a workload's host-bind data
// volumes. Unlike Backup (global, SQLite-specific) it is per-workload and the
// archive is a tar.gz of the resolved volume directories. Manifest is a
// JSON-encoded []SnapshotVolume describing what the archive covers, so a
// future restore can re-resolve each target even if volume settings drift.
type VolumeSnapshot struct {
	ID         string `json:"id"`
	WorkloadID string `json:"workload_id"`
	Label      string `json:"label"`
	Filename   string `json:"filename"`
	SizeBytes  int64  `json:"size_bytes"`
	Manifest   string `json:"manifest"` // JSON []SnapshotVolume
	CreatedAt  string `json:"created_at"`
}

// DNSRecord tracks a DNS record managed by the application.
type DNSRecord struct {
	ID               string `json:"id"`
	FQDN             string `json:"fqdn"`
	ProviderRecordID string `json:"provider_record_id"`
	ConsumerType     string `json:"consumer_type"` // "instance" or "standalone"
	ConsumerID       string `json:"consumer_id"`
	CreatedAt        string `json:"created_at"`
	UpdatedAt        string `json:"updated_at"`
}

// ProxyRoute shapes one proxy-enabled container row for the Proxies
// page. The legacy field names (ProjectID, ProjectName, StageID,
// StageName, InstanceID) are retained verbatim for the existing
// frontend contract — after the workload-first cutover they map to:
//
//	ProjectID/Name → workload id / workload name
//	StageID/Name   → containers.stage_id / containers.role
//	InstanceID     → container row id
//	Source         → "instance" for image/compose, "static_site" for static
//
// Renaming would require a coordinated frontend change; deferred.
type ProxyRoute struct {
	Source       string `json:"source"`
	InstanceID   string `json:"instance_id"`
	ProjectID    string `json:"project_id"`
	ProjectName  string `json:"project_name"`
	StageID      string `json:"stage_id"`
	StageName    string `json:"stage_name"`
	ImageTag     string `json:"image_tag"`
	Subdomain    string `json:"subdomain"`
	Domain       string `json:"domain"`
	ContainerID  string `json:"container_id"`
	Port         int    `json:"port"`
	ProxyRouteID string `json:"proxy_route_id"`
	NpmProxyID   int    `json:"npm_proxy_id"`
	Status       string `json:"status"`
	CreatedAt    string `json:"created_at"`
}

// WorkloadVolume is the plugin-shape equivalent of legacy Volume: a
// per-workload mount declaration. The Scope enum matches the existing
// VolumeScope contract so the legacy resolver can be reused once its
// project_id assumption is loosened.
type WorkloadVolume struct {
	ID         string `json:"id"`
	WorkloadID string `json:"workload_id"`
	Source     string `json:"source"`
	Target     string `json:"target"`
	Scope      string `json:"scope"`
	Name       string `json:"name"`
	CreatedAt  string `json:"created_at"`
	UpdatedAt  string `json:"updated_at"`
}

// WorkloadEnv is the plugin-shape equivalent of StageEnv: per-workload
// environment variable overrides, optionally encrypted at rest. Read by
// the Source plugin at deploy time, merged on top of source_config.env.
type WorkloadEnv struct {
	ID         string `json:"id"`
	WorkloadID string `json:"workload_id"`
	Key        string `json:"key"`
	Value      string `json:"value"`
	Encrypted  bool   `json:"encrypted"`
	CreatedAt  string `json:"created_at"`
	UpdatedAt  string `json:"updated_at"`
}

// SharedSecret is an env var shared across workloads by scope. Resolved
// into a workload's container env as a low-precedence default (overridden
// by image cfg.Env and workload_env).
type SharedSecret struct {
	ID          string `json:"id"`
	Name        string `json:"name"`  // the env KEY
	Value       string `json:"value"` // ciphertext when Encrypted; never returned decrypted by the API
	Encrypted   bool   `json:"encrypted"`
	Scope       string `json:"scope"`  // global | app
	AppID       string `json:"app_id"` // set when scope == app; "" for global
	Description string `json:"description"`
	Enabled     bool   `json:"enabled"`
	CreatedAt   string `json:"created_at"`
	UpdatedAt   string `json:"updated_at"`
}

// Shared-secret scope enum: a secret is either applied to every workload
// (global) or only to workloads whose app_id matches (app).
const (
	SharedSecretScopeGlobal = "global"
	SharedSecretScopeApp    = "app"
)

// VolumeScope defines the sharing scope for a volume mount.
// Valid scopes: instance, stage, project, project_named, named, ephemeral.
type VolumeScope string

const (
	VolumeScopeInstance     VolumeScope = "instance"
	VolumeScopeStage        VolumeScope = "stage"
	VolumeScopeProject      VolumeScope = "project"
	VolumeScopeProjectNamed VolumeScope = "project_named"
	VolumeScopeNamed        VolumeScope = "named"
	VolumeScopeEphemeral    VolumeScope = "ephemeral"
	VolumeScopeAbsolute     VolumeScope = "absolute"
)

// ValidVolumeScopes contains all valid scope values for validation.
var ValidVolumeScopes = []VolumeScope{
	VolumeScopeInstance, VolumeScopeStage, VolumeScopeProject,
	VolumeScopeProjectNamed, VolumeScopeNamed, VolumeScopeEphemeral,
	VolumeScopeAbsolute,
}

// IsValidVolumeScope returns true if the given string is a valid scope.
func IsValidVolumeScope(s string) bool {
	for _, v := range ValidVolumeScopes {
		if string(v) == s {
			return true
		}
	}
	return false
}

// EventLog represents a persistent event log entry.
type EventLog struct {
	ID         int64  `json:"id"`
	Source     string `json:"source"`
	WorkloadID string `json:"workload_id"` // "" = unscoped (non-deploy events)
	Severity   string `json:"severity"`    // info, warn, error
	Message    string `json:"message"`
	Metadata   string `json:"metadata"` // JSON-encoded structured data
	CreatedAt  string `json:"created_at"`
}

// EventTrigger is a filter+action rule evaluated against EventLog
// entries published on the bus. When all non-empty filters match, the
// trigger fires its configured action (webhook today, additional action
// types extensible via the ActionType enum).
//
// Filter fields use a comma-separated list shape for multi-value
// filters (severity, source) to keep the schema flat — empty string
// means "no filter on this dimension." FilterMessageRegex is a single
// regex evaluated against EventLog.Message.
//
// Loop-prevention: deliveries are recorded in webhook_deliveries (the
// existing audit trail). The dispatcher MUST NOT write to event_log
// or it will recurse.
type EventTrigger struct {
	ID                 int64  `json:"id"`
	Name               string `json:"name"`
	FilterSeverity     string `json:"filter_severity"`      // comma list: "warn,error"; "" = any
	FilterSource       string `json:"filter_source"`        // comma list: "logscan,deploy"; "" = any
	FilterMessageRegex string `json:"filter_message_regex"` // "" = any
	ActionType         string `json:"action_type"`          // "webhook" today
	ActionTarget       string `json:"action_target"`        // URL for webhook
	ActionSecret       string `json:"action_secret"`        // optional HMAC secret for signed delivery
	Enabled            bool   `json:"enabled"`
	CreatedAt          string `json:"created_at"`
	UpdatedAt          string `json:"updated_at"`
}

// EventTriggerActionType enumerates the supported action_type values.
// Adding a new action is additive — old triggers keep working, the
// dispatcher just learns a new branch.
const (
	EventTriggerActionWebhook = "webhook"
)

// LogScanRule is one regex-based pattern the log scanner evaluates
// against container log lines. The (workload_id, overrides_id) pair
// implements the "global rule with optional per-workload override"
// pattern documented in docs/LOGSCAN_AND_TRIGGERS_TODO.md:
//
//   - WorkloadID == "" && OverridesID == 0 → global rule, applies to
//     every workload unless overridden.
//   - WorkloadID != "" && OverridesID == 0 → workload-only addition.
//   - WorkloadID != "" && OverridesID != 0 → override of the named
//     global rule for one workload (Enabled=false to disable globally
//     for this workload).
type LogScanRule struct {
	ID              int64  `json:"id"`
	WorkloadID      string `json:"workload_id"`  // "" = global
	OverridesID     int64  `json:"overrides_id"` // 0 = not an override
	Name            string `json:"name"`
	Pattern         string `json:"pattern"`  // regex, compiled at load
	Severity        string `json:"severity"` // info|warn|error
	Streams         string `json:"streams"`  // all|stdout|stderr
	CooldownSeconds int    `json:"cooldown_seconds"`
	Enabled         bool   `json:"enabled"`
	CreatedAt       string `json:"created_at"`
	UpdatedAt       string `json:"updated_at"`
}

// Log scan stream filter values. "all" reads both streams; "stdout"
// or "stderr" filter to one. Used both for store validation and at
// docker-side log read time.
const (
	LogScanStreamAll    = "all"
	LogScanStreamStdout = "stdout"
	LogScanStreamStderr = "stderr"
)

// Log scan severity values mirror the event_log enum so a matched
// rule lands as an event_log row with the rule's severity verbatim.
const (
	LogScanSeverityInfo  = "info"
	LogScanSeverityWarn  = "warn"
	LogScanSeverityError = "error"
)

// MetricAlertRule fires an event when a container metric breaches a
// threshold. Mirrors LogScanRule but evaluated against stats_samples
// instead of log lines.
type MetricAlertRule struct {
	ID              int64   `json:"id"`
	WorkloadID      string  `json:"workload_id"` // "" = applies to all workloads
	Name            string  `json:"name"`
	Metric          string  `json:"metric"`     // cpu_percent | memory_percent | memory_bytes
	Comparator      string  `json:"comparator"` // gt | lt
	Threshold       float64 `json:"threshold"`
	Severity        string  `json:"severity"`         // info | warn | error
	CooldownSeconds int     `json:"cooldown_seconds"` // min seconds between fires per (rule,workload)
	Enabled         bool    `json:"enabled"`
	CreatedAt       string  `json:"created_at"`
	UpdatedAt       string  `json:"updated_at"`
}

// Metric-alert metric identifiers. cpu_percent + memory_percent are
// 0–100 ratios; memory_bytes is an absolute usage figure. Validated in
// the store on create/update.
const (
	MetricCPUPercent    = "cpu_percent"
	MetricMemoryPercent = "memory_percent"
	MetricMemoryBytes   = "memory_bytes"
)

// Metric-alert comparators. gt fires when the value exceeds the
// threshold; lt when it falls below.
const (
	MetricComparatorGT = "gt"
	MetricComparatorLT = "lt"
)

// WorkloadKind enumerates the legacy discriminator values written into
// containers.workload_kind and workloads.kind. After the hard cutover the
// backing project / stack / static_site tables are gone — these constants
// are just strings used to filter the unified containers index in the UI.
//
// `build` is the dockerfile-source kind: a container built from a
// Dockerfile in a Git repo. Operationally it looks like a site (one
// container, one optional public face) but its origin is the build
// pipeline, not a static-asset extract. Dashboard filters that need to
// distinguish "I built this from source" from "I served files from a
// repo" should key on this value.
type WorkloadKind string

const (
	WorkloadKindProject WorkloadKind = "project"
	WorkloadKindStack   WorkloadKind = "stack"
	WorkloadKindSite    WorkloadKind = "site"
	WorkloadKindBuild   WorkloadKind = "build"
)

// Workload is the unifying primitive that abstracts Project, Stack, and StaticSite.
// Each row is paired with exactly one project/stack/site via (Kind, RefID).
// Notification + webhook config moves here so it lives in one place across kinds.
//
// SourceKind / SourceConfig / TriggerKind / TriggerConfig / PublicFaces /
// ParentWorkloadID populate the unified plugin model from the Workload-first
// refactor. Existing rows keep these empty until they are explicitly migrated
// or replaced — the legacy Kind/RefID columns continue to point at
// project/stack/site rows in parallel during the cutover.
type Workload struct {
	ID                      string `json:"id"`
	Kind                    string `json:"kind"` // project | stack | site (legacy discriminator)
	RefID                   string `json:"ref_id"`
	Name                    string `json:"name"`
	AppID                   string `json:"app_id"`        // nullable; "" = unassigned (a.k.a. GroupID after rename)
	SourceKind              string `json:"source_kind"`   // "" until plugin-mode populated
	SourceConfig            string `json:"source_config"` // JSON-encoded, decoded by the matching Source
	TriggerKind             string `json:"trigger_kind"`
	TriggerConfig           string `json:"trigger_config"`     // JSON-encoded, decoded by the matching Trigger
	PublicFaces             string `json:"public_faces"`       // JSON-encoded []PublicFace
	ParentWorkloadID        string `json:"parent_workload_id"` // "" = root; non-empty = stage chain
	NotificationURL         string `json:"notification_url"`
	NotificationSecret      string `json:"-"` // never serialized
	WebhookSecret           string `json:"-"` // URL-identifier secret; never serialized
	WebhookSigningSecret    string `json:"-"` // HMAC key; never serialized
	WebhookRequireSignature bool   `json:"webhook_require_signature"`
	// GitOps config-as-code (dockerfile/static only). Opt-in: when enabled,
	// the workload reads its deploy config from GitOpsPath in its own repo.
	GitOpsEnabled    bool   `json:"gitops_enabled"`
	GitOpsPath       string `json:"gitops_path"`         // repo-relative; default ".tinyforge.yml"
	GitOpsLastSyncAt string `json:"gitops_last_sync_at"` // "" = never synced
	GitOpsCommitSHA  string `json:"gitops_commit_sha"`   // sha applied at last sync
	CreatedAt        string `json:"created_at"`
	UpdatedAt        string `json:"updated_at"`
}

// WorkloadNotification is one configured outbound notification route for
// a workload. Multiple rows per workload model the "one Slack channel
// for failures, one Discord webhook for successes" routing the legacy
// single notification_url column could not express.
//
// EventTypes is a comma-separated allow-list (e.g. "build_failure" or
// "deploy_success,deploy_failure"). An empty EventTypes means the row
// fires for every event type — the cheapest way to keep the existing
// single-destination behaviour expressible in the new shape.
//
// Secret round-trips through the same crypto envelope as other stored
// secrets; the API layer strips it from responses.
type WorkloadNotification struct {
	ID         string `json:"id"`
	WorkloadID string `json:"workload_id"`
	Name       string `json:"name"`
	URL        string `json:"url"`
	Secret     string `json:"-"`
	EventTypes string `json:"event_types"`
	Enabled    bool   `json:"enabled"`
	SortOrder  int    `json:"sort_order"`
	CreatedAt  string `json:"created_at"`
	UpdatedAt  string `json:"updated_at"`
}

// Container is the normalized index of every Tinyforge-managed container.
// Replaces the project-specific Instance table after migration. Subdomain/
// proxy fields are hoisted as first-class columns because ListProxyRoutes,
// stale detection, and dashboard queries filter on them frequently.
//
// StageID is populated by the deployer for project containers so ListProxyRoutes
// survives stage renames; it stays empty for stack and site rows.
type Container struct {
	ID           string `json:"id"`
	WorkloadID   string `json:"workload_id"`
	WorkloadKind string `json:"workload_kind"` // denormalized for filtered queries
	Role         string `json:"role"`          // stage name (project), service name (stack), '' (site)
	StageID      string `json:"stage_id"`      // project containers only; '' otherwise
	ContainerID  string `json:"container_id"`  // Docker container ID; '' between create+start
	ImageRef     string `json:"image_ref"`     // "image:tag" as scheduled
	ImageTag     string `json:"image_tag"`     // just the tag, for ListProxyRoutes
	Host         string `json:"host"`
	State        string `json:"state"` // running | stopped | failed | removing | missing
	Port         int    `json:"port"`
	Subdomain    string `json:"subdomain"`
	ProxyRouteID string `json:"proxy_route_id"`
	NpmProxyID   int    `json:"npm_proxy_id"`
	LastSeenAt   string `json:"last_seen_at"`
	// ExtraJSON carries source-specific metadata that isn't promoted to a
	// first-class column — currently per-face proxy route IDs for
	// multi-face image deploys. Stored as a JSON object; '{}' on empty
	// rows. Sources own the shape; consumers should tolerate unknown
	// keys.
	ExtraJSON string `json:"extra_json"`
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

// Trigger is a first-class redeploy signal source. Triggers were embedded
// in workload rows (workload.trigger_kind / trigger_config) until the
// trigger-split refactor; they are now standalone records bound to
// workloads via WorkloadTriggerBinding so a single trigger (a webhook,
// registry watcher, schedule, git push) can fan out to many workloads.
//
// Webhook secrets live here, not on the workload — the inbound webhook
// URL identifies a trigger, which then resolves its bindings to decide
// which workloads to fire.
type Trigger struct {
	ID                      string `json:"id"`
	Kind                    string `json:"kind"`   // registry | git | manual | schedule | log_scan | ...
	Name                    string `json:"name"`   // human-readable, unique
	Config                  string `json:"config"` // JSON-encoded, decoded by the matching plugin
	WebhookSecret           string `json:"-"`      // URL-identifier secret; never serialized
	WebhookSigningSecret    string `json:"-"`      // HMAC key; never serialized
	WebhookRequireSignature bool   `json:"webhook_require_signature"`
	// LastFiredAt is the RFC3339 wall-clock the scheduler last dispatched
	// this trigger. Empty for never-fired or non-schedule triggers. The
	// scheduler reads + writes this column to decide next-fire windows
	// and to surface "last fired" on the trigger detail page.
	LastFiredAt string `json:"last_fired_at,omitempty"`
	CreatedAt   string `json:"created_at"`
	UpdatedAt   string `json:"updated_at"`
}

// WorkloadTriggerBinding joins a Workload to a Trigger. BindingConfig is
// the per-binding override applied on top of Trigger.Config (top-level
// JSON merge: binding fields win). Empty BindingConfig means "use the
// trigger's config verbatim". Enabled false skips the binding without
// deleting it (useful for paused stages).
type WorkloadTriggerBinding struct {
	ID            string `json:"id"`
	WorkloadID    string `json:"workload_id"`
	TriggerID     string `json:"trigger_id"`
	BindingConfig string `json:"binding_config"` // JSON-encoded; "{}" = none
	Enabled       bool   `json:"enabled"`
	SortOrder     int    `json:"sort_order"`
	CreatedAt     string `json:"created_at"`
	UpdatedAt     string `json:"updated_at"`
}

// App is an optional grouping of workloads (e.g., "my-saas" = web project + worker stack + redis stack).
// Schema lives here from day one so future UI work is unblocked, but no UI is wired in v1.
type App struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
	CreatedAt   string `json:"created_at"`
	UpdatedAt   string `json:"updated_at"`
}

// DeployHistoryEntry is one row in the per-workload deploy ledger. Unlike
// event_log (free-text human timeline), this is the structured, version-
// pinned record the rollback action replays from. Reference is the
// effective deployed artifact handle (image tag for image sources, commit
// sha for git-built sources, "" when none applies). Error is NEVER the raw
// source error — that can carry registry-auth bytes or compose stdout; it
// holds only a fixed, secret-free marker. Raw detail goes to slog.
type DeployHistoryEntry struct {
	ID          int64  `json:"id"`
	WorkloadID  string `json:"workload_id"`
	SourceKind  string `json:"source_kind"`
	Reference   string `json:"reference"` // effective tag | commit sha | ""
	Reason      string `json:"reason"`    // manual|registry-push|git-push|cron|rollback|promote
	TriggeredBy string `json:"triggered_by"`
	Note        string `json:"note"`
	Outcome     string `json:"outcome"` // success | failure
	Error       string `json:"error"`   // generic, secret-free marker on failure
	StartedAt   string `json:"started_at"`
	FinishedAt  string `json:"finished_at"`
	// Rollbackable is computed at the API layer (not persisted): a row is
	// rollbackable when it succeeded, has a non-empty Reference, and its
	// source kind supports reference-pinned redeploy.
	Rollbackable bool `json:"rollbackable"`
}
