//! Component-level policy: `BackupPolicy`, `Destination`, `RetentionPolicy`,
//! `Schedule`, `BackupProviderRef`, plus the surrounding `ServiceConcerns`
//! umbrella.

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use super::scope::BackupScope;

// ============================================================================
// ServiceConcerns umbrella (ADR-008)
// ============================================================================

/// One of `enabled`, `disabled`, `pending`, `inherited`.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum ConcernStateKind {
    /// Concern is implemented; the impl payload field is populated.
    Enabled,
    /// Concern is explicitly opted out.
    Disabled,
    /// Concern is deferred; `backlog_ref` points to the tracking item.
    Pending,
    /// Concern is inherited from a parent component.
    Inherited,
}

/// State of one concern within [`ServiceConcerns`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConcernState {
    /// Discriminator.
    pub kind: ConcernStateKind,

    /// `disabled` reason.
    #[serde(default)]
    pub reason: Option<String>,

    /// `disabled` since (ISO date).
    #[serde(default)]
    pub since: Option<String>,

    /// `pending` backlog reference.
    #[serde(default)]
    pub backlog_ref: Option<String>,

    /// `pending` target iteration.
    #[serde(default)]
    pub target_iteration: Option<String>,

    /// `inherited` parent component.
    #[serde(default)]
    pub from: Option<String>,

    /// `enabled` payload — only one of these is populated based on which
    /// concern this state belongs to.
    #[serde(default)]
    pub backup_impl: Option<BackupPolicy>,

    /// `enabled` payload for non-backup concerns kept opaque at this layer
    /// (TLS/DNS/certs/observability/security details are not consumed by the
    /// backup-manager binary).
    #[serde(default, flatten)]
    pub other_impl: BTreeMap<String, serde_json::Value>,
}

/// Mandatory umbrella in `ComponentDef.concerns`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceConcerns {
    /// TLS concern state (opaque to the backup binary).
    pub tls: ConcernState,
    /// DNS concern state.
    pub dns: ConcernState,
    /// Certificates concern state.
    pub certs: ConcernState,
    /// Backup concern state — when `kind = Enabled`, `backup_impl` is set.
    pub backup: ConcernState,
    /// Observability concern state.
    pub observability: ConcernState,
    /// Security concern state.
    pub security: ConcernState,
}

// ============================================================================
// BackupPolicy
// ============================================================================

/// Reference to a [`BackupProvider`] declared under
/// `provisioning/extensions/providers/backup/<name>/`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupProviderRef {
    /// Provider directory name (e.g. `"restic"`, `"kopia"`).
    pub name: String,

    /// Pinned CLI version. The daemon emits a warning if the installed
    /// binary is older.
    #[serde(default)]
    pub version: Option<String>,
}

/// Schedule discriminated union (cron / interval / NATS-event).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScheduleSpec {
    /// `cron`, `interval`, or `on_event`.
    pub kind: String,

    /// Cron expression when `kind = "cron"`.
    #[serde(default)]
    pub cron_expr: Option<String>,

    /// Jitter seconds applied to cron firing.
    #[serde(default)]
    pub jitter_sec: Option<u64>,

    /// Period when `kind = "interval"`.
    #[serde(default)]
    pub every: Option<String>,

    /// Random jitter for interval scheduling.
    #[serde(default)]
    pub jitter: Option<String>,

    /// NATS subject when `kind = "on_event"`.
    #[serde(default)]
    pub subject: Option<String>,

    /// Debounce duration for event-driven schedules.
    #[serde(default)]
    pub debounce: Option<String>,
}

/// Retention preset. Mirrors restic's `--keep-*` flags.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RetentionPolicy {
    /// `--keep-last`.
    #[serde(default)]
    pub keep_last: u32,
    /// `--keep-daily`.
    #[serde(default)]
    pub keep_daily: u32,
    /// `--keep-weekly`.
    #[serde(default)]
    pub keep_weekly: u32,
    /// `--keep-monthly`.
    #[serde(default)]
    pub keep_monthly: u32,
    /// `--keep-yearly`.
    #[serde(default)]
    pub keep_yearly: u32,
    /// Hard upper bound on snapshot age (deleted regardless of keep_*).
    #[serde(default)]
    pub prune_after: Option<String>,
}

/// Destination protocol kind.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DestinationKind {
    /// S3-compatible object storage (Hetzner, MinIO, AWS S3, etc.).
    S3,
    /// Backblaze B2.
    B2,
    /// Local filesystem (only valid for archive role).
    Local,
    /// SFTP target.
    Sftp,
    /// restic REST server.
    RestServer,
}

/// Role of a destination within a policy. The `MultiDestinationRequired`
/// Nickel contract requires at least one `Primary` and a total of ≥2 entries.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum DestinationRole {
    /// First-write target. Backups land here directly.
    Primary,
    /// Replicated copy of `Primary` for off-site durability.
    Replica,
    /// Cold archive (typically slower or cheaper storage).
    Archive,
}

/// Backup destination specification.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Destination {
    /// Stable identifier used in metric labels and tags.
    pub name: String,
    /// Protocol kind.
    pub kind: DestinationKind,
    /// Provider URI (e.g. `s3:fsn1.hetzner/libre-wuji-backups`).
    pub uri: String,
    /// Reference to credentials in `secretumvault`.
    pub cred_ref: VaultCredRef,
    /// Role within the policy.
    pub role: DestinationRole,
    /// Region (for cloud providers that support it).
    #[serde(default)]
    pub region: Option<String>,
}

/// Reference to a credentials entry in vault.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VaultCredRef {
    /// Path under vault (e.g. `backup-manager/destinations/hetzner-primary`).
    pub path: String,
    /// Type of credential payload at the path.
    pub kind: String,
}

/// Reference to an encryption key in vault.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VaultKeyRef {
    /// Path under vault.
    pub path: String,
    /// Algorithm (`age_x25519`, `aes_gcm_256`, etc.).
    #[serde(default)]
    pub algorithm: String,
    /// HKDF derivation parameters when applicable.
    #[serde(default)]
    pub derivation: Option<HkdfDerivation>,
}

/// HKDF derivation parameters.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HkdfDerivation {
    /// Method (`none` or `hkdf_sha256`).
    pub method: String,
    /// HKDF info parameter.
    pub info: String,
}

/// Tag generation strategy for snapshots.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TagStrategy {
    /// Used as the `component=<value>` tag.
    pub component_label: String,
    /// Additional static tags as `k=v` strings.
    #[serde(default)]
    pub extra: Vec<String>,
}

/// Pre/post hooks executed by the manager around the backup run.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Hooks {
    /// Commands run before backup; non-zero exit aborts when `abort_on_failure = true`.
    #[serde(default)]
    pub pre: Vec<String>,
    /// Commands run after backup, regardless of outcome.
    #[serde(default)]
    pub post: Vec<String>,
    /// Hook timeout (e.g. `"5m"`).
    #[serde(default = "default_hook_timeout")]
    pub timeout: String,
    /// Whether a non-zero pre hook aborts the backup.
    #[serde(default = "default_abort_on_failure")]
    pub abort_on_failure: bool,
}

fn default_hook_timeout() -> String { "5m".to_string() }
fn default_abort_on_failure() -> bool { true }

/// Bandwidth throttle (passed to provider as `--limit-upload`/`--limit-download`).
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Throttle {
    /// Upload limit in KB/s.
    #[serde(default)]
    pub upload_kbps: Option<u32>,
    /// Download limit in KB/s.
    #[serde(default)]
    pub download_kbps: Option<u32>,
}

/// Reference to a verify policy (drill spec lives separately to keep policies
/// focused).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyPolicyRef {
    /// Verify schedule.
    #[serde(default)]
    pub schedule: Option<ScheduleSpec>,
    /// Level: `quick`, `deep`, `restore_drill`, `full_dr`.
    #[serde(default = "default_verify_level")]
    pub level: String,
    /// Reference to a `DrillSpec` by name.
    #[serde(default)]
    pub drill_ref: Option<String>,
}

fn default_verify_level() -> String { "quick".to_string() }

/// Component-level backup policy.
///
/// The Nickel contracts `EncryptionRequired`, `MultiDestinationRequired`, and
/// `NonEmptyScopes` are enforced at policy export time, so values reaching
/// this struct already satisfy the invariants.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupPolicy {
    /// Provider reference.
    pub provider: BackupProviderRef,
    /// ≥2 destinations, at least one with `role = Primary`.
    pub destinations: Vec<Destination>,
    /// Encryption key reference.
    pub encryption: VaultKeyRef,
    /// Schedule.
    pub schedule: ScheduleSpec,
    /// Retention policy.
    pub retention: RetentionPolicy,
    /// Scopes (≥1).
    pub scopes: Vec<BackupScope>,
    /// Tag strategy.
    pub tag_strategy: TagStrategy,
    /// Optional hooks.
    #[serde(default)]
    pub hooks: Option<Hooks>,
    /// Optional verify reference.
    #[serde(default)]
    pub verify: Option<VerifyPolicyRef>,
    /// Optional throttle.
    #[serde(default)]
    pub throttle: Option<Throttle>,
    /// Optional consistency-group name (this policy participates in a `BackupGroup`).
    #[serde(default)]
    pub consistency_group: Option<String>,
}
