use secrecy::SecretString;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug)]
pub struct AgentConfig {
    pub mongodb_uri: String,
    pub mongodb_database: String,
    pub litellm_url: String,
    pub litellm_api_key: SecretString,
    pub litellm_model: String,
    pub litellm_embed_model: String,
    pub github_token: Option<SecretString>,
    pub github_webhook_secret: Option<SecretString>,
    pub gitlab_url: Option<String>,
    pub gitlab_token: Option<SecretString>,
    pub gitlab_webhook_secret: Option<SecretString>,
    pub jira_url: Option<String>,
    pub jira_email: Option<String>,
    pub jira_api_token: Option<SecretString>,
    pub jira_project_key: Option<String>,
    pub searxng_url: Option<String>,
    pub nvd_api_key: Option<SecretString>,
    pub agent_port: u16,
    pub scan_schedule: String,
    pub cve_monitor_schedule: String,
    pub git_clone_base_path: String,
    /// Base directory for content-addressed artifact blobs and per-run working
    /// dirs (`<base>/blobs/<sha[0:2]>/<sha>`, `<base>/work/<target>/<artifact>/`).
    pub artifact_store_base_path: String,
    pub ssh_key_path: String,
    pub keycloak_url: Option<String>,
    pub keycloak_realm: Option<String>,
    pub keycloak_admin_username: Option<String>,
    pub keycloak_admin_password: Option<SecretString>,
    // Pentest defaults
    pub pentest_verification_email: Option<String>,
    pub pentest_imap_host: Option<String>,
    pub pentest_imap_port: Option<u16>,
    /// Use implicit TLS (IMAPS, port 993) instead of plain IMAP.
    pub pentest_imap_tls: bool,
    pub pentest_imap_username: Option<String>,
    pub pentest_imap_password: Option<SecretString>,
    /// Static bearer for the cross-tenant admin endpoints under
    /// `/api/v1/admin/*`. When `None`, those endpoints are not
    /// mounted at all (defense-in-depth: ops endpoints never reach
    /// any auth path if no operator has explicitly opted in).
    pub admin_api_token: Option<SecretString>,
    /// Live tenant-registry URL the scheduler consults for the list
    /// of tenants to iterate. When `None` or unreachable, scheduler
    /// falls back to `SCHEDULER_TENANT_IDS` env (M7.2-C).
    pub tenant_registry_url: Option<String>,
    /// Ephemeral soft-PLC provisioning for dynamic PLC testing (#183). Off by
    /// default: it needs Docker access in the agent's runtime, which is a
    /// deployment opt-in.
    pub plc_runtime: PlcRuntimeConfig,
    /// Static bearer for the Werkbank runner endpoints
    /// (`/api/v1/werkbank/jobs/*`). Machine auth for runners leasing/completing
    /// jobs — NOT a Keycloak JWT, since a runner acts across tenants. When
    /// `None`, those endpoints are not mounted at all.
    pub werkbank_runner_token: Option<SecretString>,
    /// Source for the OSCAL control catalog pulled from breakpilot-compliance
    /// (drives the [`crate::traits::ControlsProvider`]). Disabled when
    /// `base_url` is `None`.
    pub breakpilot: BreakpilotConfig,
}

/// Where to pull the OSCAL control catalog from breakpilot-compliance, and where
/// to snapshot it for deterministic / offline reuse.
#[derive(Debug, Clone)]
pub struct BreakpilotConfig {
    /// Backend base URL (e.g. `http://backend-compliance:8002`). `None` disables
    /// the OSCAL controls provider.
    pub base_url: Option<String>,
    /// Optional bearer token for the catalog endpoint.
    pub token: Option<SecretString>,
    /// Directory for catalog snapshots.
    pub snapshot_dir: String,
    /// Enable the master-controls **semantic** mapping pass (embed regions,
    /// Enable the master-controls **semantic** mapping pass (embed regions,
    /// retrieve nearest controls, grounded-judge). On by default — validated live
    /// against the deployed master-controls catalog. Still a no-op unless
    /// `base_url` is set and the catalog is reachable.
    pub semantic_mapping: bool,
    /// Enable the **grounded surface** pass for absence-based controls (retrieve
    /// the code surface a control governs, judge whether it holds). On by default
    /// — validated live; it covers the 8 absence-based CRA controls that no
    /// syntactic rule can.
    pub grounded_control_checks: bool,
}

impl Default for BreakpilotConfig {
    fn default() -> Self {
        Self {
            base_url: None,
            token: None,
            snapshot_dir: "/data/compliance-scanner/oscal".to_string(),
            semantic_mapping: true,
            grounded_control_checks: true,
        }
    }
}

/// Configuration for the ephemeral soft-PLC "provision-and-test" path (#183).
///
/// When a PLC/SPS target ships control logic but no reachable live device, the
/// agent can instantiate that logic itself: spin up a throwaway soft-PLC
/// (OpenPLC) container in-cluster, load the program, start the runtime, probe it
/// over industrial protocols, then tear it down. This struct carries the knobs
/// for that container's lifecycle and the OpenPLC web-UI credentials used to
/// upload the program.
#[derive(Clone, Debug)]
pub struct PlcRuntimeConfig {
    /// Master switch. Provision-and-test does nothing unless this is set — it
    /// shells out to `docker`, which requires the agent container to have Docker
    /// access (socket mount), an explicit deployment decision.
    pub enabled: bool,
    /// Container image for the ephemeral soft-PLC (OpenPLC).
    pub image: String,
    /// Docker network the instance joins. Must be the agent's own network so it
    /// is reachable in-cluster by container name and never published to the host.
    pub network: String,
    /// Memory cap passed to `docker run --memory` (e.g. `512m`).
    pub memory: String,
    /// CPU cap passed to `docker run --cpus` (e.g. `0.5`).
    pub cpus: String,
    /// Hard ceiling on a provisioned instance's lifetime. Teardown is guaranteed
    /// no later than this even if a load/probe step hangs.
    pub max_lifetime_secs: u64,
    /// OpenPLC web-UI username for the program upload (image default `openplc`).
    pub openplc_user: String,
    /// OpenPLC web-UI password (image default `openplc`).
    pub openplc_password: SecretString,
}

impl Default for PlcRuntimeConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            image: "registry.meghsakha.com/openplc:latest".to_string(),
            network: "certifai".to_string(),
            memory: "512m".to_string(),
            cpus: "0.5".to_string(),
            max_lifetime_secs: 180,
            openplc_user: "openplc".to_string(),
            openplc_password: SecretString::from("openplc".to_string()),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DashboardConfig {
    pub mongodb_uri: String,
    pub mongodb_database: String,
    pub agent_api_url: String,
    pub dashboard_port: u16,
    pub mcp_endpoint_url: Option<String>,
}
