package store

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

// User mirrors the users table.
type User struct {
	ID           string
	Username     string
	PasswordHash string
	Role         Role
	CreatedAt    time.Time
	LastLoginAt  *time.Time
}

// Role enumerates the access tiers from spec.md §7.2.
type Role string

// Defined Role values, in descending order of privilege.
const (
	RoleAdmin    Role = "admin"
	RoleOperator Role = "operator"
	RoleViewer   Role = "viewer"
)

// Session mirrors the sessions table. The ID is the (raw) session
// token; the DB stores its hash. Callers that hold a *Session have
// already authenticated.
type Session struct {
	ID        string
	UserID    string
	CreatedAt time.Time
	ExpiresAt time.Time
	IP        string
	UA        string
}

// Host mirrors the hosts table. The P2 redesign moved repo-related
// flags out (auto-init replaces RepoInitialisedAt; bandwidth lives
// here as a host-wide cap; "what to back up" lives on source_groups).
type Host struct {
	ID                     string
	Name                   string
	OS                     string
	Arch                   string
	AgentVersion           string
	ResticVersion          string
	ProtocolVersion        int
	EnrolledAt             time.Time
	LastSeenAt             *time.Time
	Status                 string
	RepoID                 *string
	Tags                   []string
	CurrentJobID           *string
	LastBackupAt           *time.Time
	LastBackupStatus       *string
	RepoSizeBytes          int64
	SnapshotCount          int
	OpenAlertCount         int
	AppliedScheduleVersion int64
	// Host-wide bandwidth caps applied to every restic invocation
	// (backup, restore, prune). nil = no cap.
	BandwidthUpKBps   *int
	BandwidthDownKBps *int

	// PreHookDefault / PostHookDefault are AEAD ciphertext (string
	// blob produced by crypto.AEAD.Encrypt). Per source group hooks
	// (SourceGroup.PreHook / PostHook) override these when set.
	// Empty = no default configured.
	PreHookDefault  string
	PostHookDefault string
}

// Schedule is now intentionally slim: cron + which groups + enabled.
// The "what" lives on SourceGroup (paths, excludes, retention, retry).
// The "kind" of operation a schedule can drive is implicit — backup
// only. forget/prune/check are repo-level cadences on
// HostRepoMaintenance, not schedule kinds.
type Schedule struct {
	ID        string
	HostID    string
	CronExpr  string
	Enabled   bool
	CreatedAt time.Time
	UpdatedAt time.Time
	// SourceGroupIDs is populated by ListSchedulesByHost (joins
	// schedule_source_groups) and accepted on Create / Update so the
	// caller passes the desired junction state in one shape.
	SourceGroupIDs []string
}

// SourceGroup is the new home for "what gets backed up." A named
// bundle of include + exclude paths plus a retention policy. Group
// name doubles as the snapshot tag (restic --tag <name>) so retention
// can target it via `restic forget --tag`.
type SourceGroup struct {
	ID                  string
	HostID              string
	Name                string
	Includes            []string
	Excludes            []string
	RetentionPolicy     RetentionPolicy
	RetryMax            int
	RetryBackoffSeconds int
	// ConflictDimension is the cached name of the failing keep-* on
	// a granularity↔cadence mismatch (e.g. "hourly" when keep-hourly
	// is set but no schedule pointing at this group fires sub-daily).
	// Empty means no conflict. Refreshed on every schedule + group CRUD.
	ConflictDimension string
	CreatedAt         time.Time
	UpdatedAt         time.Time

	// PreHook / PostHook are AEAD ciphertext (string blob produced by
	// crypto.AEAD.Encrypt). Empty means "no hook configured."
	// Encryption/decryption happens at the HTTP layer (where AEAD
	// lives); the store layer just persists the bytes verbatim.
	PreHook  string
	PostHook string
}

// RetentionPolicy is the typed view of `restic forget --keep-*`.
// All fields nullable; empty struct = "keep everything for this group."
type RetentionPolicy struct {
	KeepLast    *int `json:"keep_last,omitempty"`
	KeepHourly  *int `json:"keep_hourly,omitempty"`
	KeepDaily   *int `json:"keep_daily,omitempty"`
	KeepWeekly  *int `json:"keep_weekly,omitempty"`
	KeepMonthly *int `json:"keep_monthly,omitempty"`
	KeepYearly  *int `json:"keep_yearly,omitempty"`
}

// Summary renders a compact human view — "last=7, d=14, w=4" or
// "—" when nothing is set. Used by templates and logs.
func (p RetentionPolicy) Summary() string {
	parts := []string{}
	for _, kv := range []struct {
		k string
		v *int
	}{
		{"last", p.KeepLast},
		{"h", p.KeepHourly},
		{"d", p.KeepDaily},
		{"w", p.KeepWeekly},
		{"m", p.KeepMonthly},
		{"y", p.KeepYearly},
	} {
		if kv.v != nil {
			parts = append(parts, fmt.Sprintf("%s=%d", kv.k, *kv.v))
		}
	}
	if len(parts) == 0 {
		return "—"
	}
	return strings.Join(parts, ", ")
}

// HostRepoMaintenance carries the host-level cron cadences for the
// three repo-wide verbs (forget / prune / check). 1:1 with hosts;
// row is auto-created at host enrolment with sensible defaults.
type HostRepoMaintenance struct {
	HostID         string
	ForgetCron     string
	ForgetEnabled  bool
	PruneCron      string
	PruneEnabled   bool
	CheckCron      string
	CheckEnabled   bool
	CheckSubsetPct int
}

// PendingRun queues a missed cron tick (agent was offline) for the
// server-side retry ticker to dispatch later.
type PendingRun struct {
	ID            string
	ScheduleID    string
	SourceGroupID string
	HostID        string
	Attempt       int
	NextAttemptAt time.Time
	ScheduledAt   time.Time // original cron tick — forensic / audit
	LastError     string
}

// EnrollmentToken is the issuer's view of a one-time token.
type EnrollmentToken struct {
	Raw       string
	TokenHash string
	CreatedAt time.Time
	ExpiresAt time.Time
}

// AuditEntry mirrors the audit_log table.
type AuditEntry struct {
	ID         string
	UserID     *string
	Actor      string
	Action     string
	TargetKind *string
	TargetID   *string
	TS         time.Time
	Payload    json.RawMessage
}
