use crate::Error;
use chrono::Utc;
use pod_store::PodMetadata;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sha2::{Digest, Sha256};
use std::{
    collections::BTreeSet,
    fs,
    path::{Path, PathBuf},
    sync::Arc,
};

const LOCAL_RUNTIME_ID: &str = "local-pod-runtime";
const LOCAL_HOST_KIND: &str = "local-pod-host";
const MAX_DIAGNOSTICS: usize = 16;
const MAX_HOST_SCAN: usize = 256;
const MAX_IDENTIFIER_LEN: usize = 120;
const ID_DIGEST_HEX_LEN: usize = 16;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeDiagnostic {
    pub code: String,
    pub severity: DiagnosticSeverity,
    pub message: String,
}

impl RuntimeDiagnostic {
    pub fn new(code: impl Into<String>, severity: &str, message: impl Into<String>) -> Self {
        let severity = match severity {
            "error" => DiagnosticSeverity::Error,
            "warning" => DiagnosticSeverity::Warning,
            _ => DiagnosticSeverity::Info,
        };
        diagnostic(code, severity, message)
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum DiagnosticSeverity {
    Info,
    Warning,
    Error,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeCapabilitySummary {
    pub can_list_hosts: bool,
    pub can_list_workers: bool,
    pub can_get_worker: bool,
    pub can_spawn_worker: bool,
    pub can_stop_worker: bool,
    pub can_accept_input: bool,
    pub can_stream_events: bool,
    pub can_read_bounded_transcript: bool,
    pub has_workspace_fs: bool,
    pub has_shell: bool,
    pub has_git: bool,
    pub supports_worktrees: bool,
    pub supports_backend_internal_tools: bool,
    pub local_pod_inspection: String,
    pub workspace_scope: String,
    pub max_workers: usize,
    pub os: String,
    pub arch: String,
}

pub type HostCapabilitySummary = RuntimeCapabilitySummary;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeSummary {
    pub runtime_id: String,
    pub label: String,
    pub kind: String,
    pub status: String,
    pub host_ids: Vec<String>,
    pub capabilities: RuntimeCapabilitySummary,
    pub diagnostics: Vec<RuntimeDiagnostic>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HostSummary {
    pub runtime_id: String,
    pub host_id: String,
    pub label: String,
    pub kind: String,
    pub status: String,
    pub observed_at: String,
    pub last_seen_at: Option<String>,
    pub capabilities: HostCapabilitySummary,
    pub diagnostics: Vec<RuntimeDiagnostic>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerWorkspaceSummary {
    pub visibility: String,
    pub identity: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerImplementationSummary {
    pub kind: String,
    pub display_hint: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerCapabilitySummary {
    pub can_accept_input: bool,
    pub can_stream_events: bool,
    pub can_stop: bool,
    pub can_spawn_followup: bool,
    pub can_read_bounded_transcript: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerSummary {
    pub runtime_id: String,
    pub worker_id: String,
    pub host_id: String,
    pub label: String,
    pub role: Option<String>,
    pub profile: Option<String>,
    pub workspace: WorkerWorkspaceSummary,
    pub state: String,
    pub status: String,
    pub last_seen_at: Option<String>,
    pub implementation: WorkerImplementationSummary,
    pub capabilities: WorkerCapabilitySummary,
    pub diagnostics: Vec<RuntimeDiagnostic>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RuntimeList<T> {
    pub items: Vec<T>,
    pub diagnostics: Vec<RuntimeDiagnostic>,
}

impl<T> RuntimeList<T> {
    fn new(items: Vec<T>, diagnostics: Vec<RuntimeDiagnostic>) -> Self {
        Self { items, diagnostics }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerLookupResult {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub worker: Option<WorkerSummary>,
    pub diagnostics: Vec<RuntimeDiagnostic>,
}

/// Browser-safe worker spawn request shape.
///
/// The request intentionally carries only workspace policy intents and stable
/// worker identifiers. Raw workspace roots, child cwd, executable path, and raw
/// profile selectors are resolved by the runtime service and never accepted from
/// Workspace API callers.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerSpawnRequest {
    pub intent: WorkerSpawnIntent,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub requested_worker_name: Option<String>,
    pub acceptance: WorkerSpawnAcceptanceRequirement,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum WorkerSpawnIntent {
    WorkspaceCompanion,
    WorkspaceOrchestrator,
    TicketRole {
        ticket_id: String,
        role: TicketWorkerRole,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TicketWorkerRole {
    Intake,
    Orchestrator,
    Coder,
    Reviewer,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum WorkerSpawnAcceptanceRequirement {
    SocketReady,
    RunAccepted { expected_segments: usize },
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerSpawnResult {
    pub state: WorkerOperationState,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub worker: Option<WorkerSummary>,
    pub acceptance_evidence: Vec<WorkerSpawnAcceptanceEvidence>,
    pub diagnostics: Vec<RuntimeDiagnostic>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WorkerOperationState {
    Accepted,
    Unsupported,
    Rejected,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerSpawnAcceptanceEvidence {
    pub kind: String,
    pub detail: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerStopRequest {
    pub worker_id: String,
    pub mode: WorkerStopMode,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WorkerStopMode {
    Graceful,
    Force,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerStopResult {
    pub state: WorkerOperationState,
    pub diagnostics: Vec<RuntimeDiagnostic>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct WorkerProxyConnectPoint {
    pub kind: String,
    pub status: String,
    pub diagnostics: Vec<RuntimeDiagnostic>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RuntimeRegistryError {
    InvalidIdentifier { kind: &'static str, value: String },
    UnknownHost(String),
    UnknownWorker(String),
}

impl RuntimeRegistryError {
    pub fn into_error(self) -> Error {
        match self {
            Self::InvalidIdentifier { kind, value } => Error::InvalidRuntimeIdentifier {
                kind: kind.to_string(),
                value,
            },
            Self::UnknownHost(host_id) => Error::UnknownHost(host_id),
            Self::UnknownWorker(worker_id) => Error::UnknownWorker(worker_id),
        }
    }
}

pub trait WorkspaceWorkerRuntime: Send + Sync {
    fn runtime_id(&self) -> &str;

    fn runtime_summary(&self, limit: usize) -> RuntimeSummary;

    fn list_hosts(&self, limit: usize) -> RuntimeList<HostSummary>;

    fn list_workers(&self, limit: usize) -> RuntimeList<WorkerSummary>;

    fn worker(&self, worker_id: &str) -> WorkerLookupResult;

    fn spawn_worker(&self, request: WorkerSpawnRequest) -> WorkerSpawnResult {
        WorkerSpawnResult {
            state: WorkerOperationState::Unsupported,
            worker: None,
            acceptance_evidence: Vec::new(),
            diagnostics: vec![diagnostic(
                "worker_spawn_resolver_pending",
                DiagnosticSeverity::Info,
                format!(
                    "worker spawn intent '{}' was accepted as a typed request shape, but launch resolution is not implemented by this registry surface",
                    worker_spawn_intent_label(&request.intent)
                ),
            )],
        }
    }

    fn stop_worker(&self, request: WorkerStopRequest) -> WorkerStopResult {
        WorkerStopResult {
            state: WorkerOperationState::Unsupported,
            diagnostics: vec![diagnostic(
                "worker_stop_pending",
                DiagnosticSeverity::Info,
                format!(
                    "worker stop for '{}' is reserved for the runtime service boundary and is not implemented by this registry surface",
                    request.worker_id
                ),
            )],
        }
    }

    fn proxy_connect_points(&self, worker_id: &str) -> Vec<WorkerProxyConnectPoint> {
        vec![WorkerProxyConnectPoint {
            kind: "stream_proxy".to_string(),
            status: "not_implemented".to_string(),
            diagnostics: vec![diagnostic(
                "worker_proxy_pending",
                DiagnosticSeverity::Info,
                format!(
                    "worker proxy connect points for '{}' are not implemented by this overview-only registry surface",
                    worker_id
                ),
            )],
        }]
    }
}

#[derive(Clone)]
pub struct WorkerRuntimeRegistry {
    runtimes: Vec<Arc<dyn WorkspaceWorkerRuntime>>,
}

impl WorkerRuntimeRegistry {
    pub fn new(runtimes: Vec<Arc<dyn WorkspaceWorkerRuntime>>) -> Self {
        Self { runtimes }
    }

    pub fn for_local_pods(runtime: LocalPodRuntime) -> Self {
        Self::new(vec![Arc::new(runtime)])
    }

    pub fn list_runtimes(&self, limit: usize) -> RuntimeList<RuntimeSummary> {
        let mut diagnostics = Vec::new();
        let mut items = Vec::new();
        for runtime in self.runtimes.iter().take(limit) {
            let summary = runtime.runtime_summary(limit);
            diagnostics.extend(summary.diagnostics.iter().cloned());
            items.push(summary);
        }
        diagnostics.truncate(MAX_DIAGNOSTICS);
        RuntimeList::new(items, diagnostics)
    }

    pub fn list_hosts(&self, limit: usize) -> RuntimeList<HostSummary> {
        let mut items = Vec::new();
        let mut diagnostics = Vec::new();
        for runtime in &self.runtimes {
            if items.len() >= limit {
                break;
            }
            let mut list = runtime.list_hosts(limit.saturating_sub(items.len()));
            diagnostics.append(&mut list.diagnostics);
            items.append(&mut list.items);
        }
        diagnostics.truncate(MAX_DIAGNOSTICS);
        RuntimeList::new(items, diagnostics)
    }

    pub fn list_workers(&self, limit: usize) -> RuntimeList<WorkerSummary> {
        let mut items = Vec::new();
        let mut diagnostics = Vec::new();
        for runtime in &self.runtimes {
            if items.len() >= limit {
                break;
            }
            let mut list = runtime.list_workers(limit.saturating_sub(items.len()));
            diagnostics.append(&mut list.diagnostics);
            items.append(&mut list.items);
        }
        diagnostics.truncate(MAX_DIAGNOSTICS);
        RuntimeList::new(items, diagnostics)
    }

    pub fn list_workers_for_host(
        &self,
        host_id: &str,
        limit: usize,
    ) -> Result<RuntimeList<WorkerSummary>, RuntimeRegistryError> {
        validate_backend_identifier("host_id", host_id)?;

        let mut host_found = false;
        let mut diagnostics = Vec::new();
        let mut items = Vec::new();
        for runtime in &self.runtimes {
            let host_list = runtime.list_hosts(MAX_HOST_SCAN);
            diagnostics.extend(host_list.diagnostics);
            if !host_list.items.iter().any(|host| host.host_id == host_id) {
                continue;
            }
            host_found = true;
            let worker_list = runtime.list_workers(limit);
            diagnostics.extend(worker_list.diagnostics);
            items.extend(
                worker_list
                    .items
                    .into_iter()
                    .filter(|worker| worker.host_id == host_id)
                    .take(limit.saturating_sub(items.len())),
            );
            if items.len() >= limit {
                break;
            }
        }
        diagnostics.truncate(MAX_DIAGNOSTICS);
        if host_found {
            Ok(RuntimeList::new(items, diagnostics))
        } else {
            Err(RuntimeRegistryError::UnknownHost(host_id.to_string()))
        }
    }

    pub fn worker(&self, worker_id: &str) -> Result<WorkerSummary, RuntimeRegistryError> {
        validate_backend_identifier("worker_id", worker_id)?;
        for runtime in &self.runtimes {
            let lookup = runtime.worker(worker_id);
            if let Some(worker) = lookup.worker {
                return Ok(worker);
            }
        }
        Err(RuntimeRegistryError::UnknownWorker(worker_id.to_string()))
    }
}

#[derive(Clone)]
pub struct LocalPodRuntime {
    runtime_id: String,
    host_id: String,
    workspace_root: PathBuf,
    data_dir: Option<PathBuf>,
}

pub type LocalRuntimeBridge = LocalPodRuntime;

impl LocalPodRuntime {
    pub fn new(
        workspace_id: impl AsRef<str>,
        workspace_root: impl Into<PathBuf>,
        data_dir: Option<PathBuf>,
    ) -> Self {
        Self {
            runtime_id: LOCAL_RUNTIME_ID.to_string(),
            host_id: host_id_for_workspace(workspace_id.as_ref()),
            workspace_root: workspace_root.into(),
            data_dir,
        }
    }

    fn pod_root(&self) -> Option<PathBuf> {
        self.data_dir.as_ref().map(|dir| dir.join("pods"))
    }

    fn pod_names(&self, pod_root: &Path) -> Result<BTreeSet<String>, RuntimeDiagnostic> {
        let entries = fs::read_dir(pod_root).map_err(|err| {
            diagnostic(
                "local_pod_registry_unreadable",
                DiagnosticSeverity::Warning,
                format!("local Pod registry could not be read: {err}"),
            )
        })?;

        let mut pod_names = BTreeSet::new();
        for entry in entries.flatten() {
            let Ok(file_type) = entry.file_type() else {
                continue;
            };
            if !file_type.is_dir() {
                continue;
            }
            let Some(name) = entry.file_name().to_str().map(|name| name.to_string()) else {
                continue;
            };
            pod_names.insert(name);
        }
        Ok(pod_names)
    }

    fn read_worker(&self, pod_root: &Path, pod_name: &str) -> WorkerReadOutcome {
        let metadata_path = pod_root.join(pod_name).join("metadata.json");
        let data = match fs::read_to_string(metadata_path) {
            Ok(data) => data,
            Err(err) => {
                return WorkerReadOutcome::Diagnostic(diagnostic(
                    "local_pod_metadata_unreadable",
                    DiagnosticSeverity::Warning,
                    format!("local Pod metadata could not be read: {err}"),
                ));
            }
        };
        let metadata: PodMetadata = match serde_json::from_str(&data) {
            Ok(metadata) => metadata,
            Err(err) => {
                return WorkerReadOutcome::Diagnostic(diagnostic(
                    "local_pod_metadata_invalid",
                    DiagnosticSeverity::Warning,
                    format!("local Pod metadata could not be parsed: {err}"),
                ));
            }
        };

        let Some(metadata_workspace_root) = metadata.workspace_root.as_ref() else {
            return WorkerReadOutcome::Diagnostic(diagnostic(
                "local_pod_workspace_root_missing",
                DiagnosticSeverity::Warning,
                "local Pod metadata did not include a workspace identity and was not included"
                    .to_string(),
            ));
        };
        if !same_workspace_root(metadata_workspace_root, &self.workspace_root) {
            return WorkerReadOutcome::Diagnostic(diagnostic(
                "local_pod_workspace_not_visible",
                DiagnosticSeverity::Info,
                "local Pod metadata belongs to another workspace and was not included".to_string(),
            ));
        }

        let label = safe_display_hint(&metadata.pod_name);
        let role = manifest_hint_string(&metadata.resolved_manifest_snapshot, "role");
        let profile =
            manifest_hint_string(&metadata.resolved_manifest_snapshot, "profile_selector");
        let worker_id = worker_id_for_pod(pod_name);
        let status = match metadata
            .active
            .as_ref()
            .and_then(|active| active.segment_id.as_ref())
        {
            Some(_) => "active:segment".to_string(),
            None if metadata.active.is_some() => "active:pending_segment".to_string(),
            None => "metadata_only".to_string(),
        };
        let state = if metadata.active.is_some() {
            "active".to_string()
        } else {
            "metadata_only".to_string()
        };
        let last_seen_at = None;
        WorkerReadOutcome::Worker(WorkerSummary {
            runtime_id: self.runtime_id.clone(),
            worker_id,
            host_id: self.host_id.clone(),
            label,
            role,
            profile,
            workspace: WorkerWorkspaceSummary {
                visibility: "current_workspace".to_string(),
                identity: "runtime_workspace".to_string(),
            },
            state,
            status,
            last_seen_at,
            implementation: WorkerImplementationSummary {
                kind: "local_pod".to_string(),
                display_hint: safe_display_hint(pod_name),
            },
            capabilities: WorkerCapabilitySummary {
                can_accept_input: true,
                can_stream_events: false,
                can_stop: false,
                can_spawn_followup: false,
                can_read_bounded_transcript: false,
            },
            diagnostics: vec![diagnostic(
                "worker_actions_not_implemented",
                DiagnosticSeverity::Info,
                "runtime overview is available; worker control and stream/proxy operations are not yet implemented"
                    .to_string(),
            )],
        })
    }
}

impl WorkspaceWorkerRuntime for LocalPodRuntime {
    fn runtime_id(&self) -> &str {
        &self.runtime_id
    }

    fn runtime_summary(&self, limit: usize) -> RuntimeSummary {
        let host_list = self.list_hosts(1);
        RuntimeSummary {
            runtime_id: self.runtime_id.clone(),
            label: "Local Pod runtime".to_string(),
            kind: "local_pod".to_string(),
            status: "available".to_string(),
            host_ids: host_list
                .items
                .iter()
                .take(limit)
                .map(|host| host.host_id.clone())
                .collect(),
            capabilities: local_runtime_capabilities(limit, self.pod_root().is_some()),
            diagnostics: host_list.diagnostics,
        }
    }

    fn list_hosts(&self, limit: usize) -> RuntimeList<HostSummary> {
        if limit == 0 {
            return RuntimeList::new(Vec::new(), Vec::new());
        }

        let mut diagnostics = Vec::new();
        let inspection_available = self.pod_root().is_some();
        if !inspection_available {
            diagnostics.push(diagnostic(
                "local_pod_registry_unavailable",
                DiagnosticSeverity::Warning,
                "local Pod data directory is not configured; worker discovery is unavailable"
                    .to_string(),
            ));
        }

        let item = HostSummary {
            runtime_id: self.runtime_id.clone(),
            host_id: self.host_id.clone(),
            label: label_for_workspace(&self.workspace_root),
            kind: LOCAL_HOST_KIND.to_string(),
            status: if inspection_available {
                "available"
            } else {
                "degraded"
            }
            .to_string(),
            observed_at: Utc::now().to_rfc3339(),
            last_seen_at: None,
            capabilities: local_runtime_capabilities(limit, inspection_available),
            diagnostics: diagnostics.clone(),
        };
        RuntimeList::new(vec![item], diagnostics)
    }

    fn list_workers(&self, limit: usize) -> RuntimeList<WorkerSummary> {
        let Some(pod_root) = self.pod_root() else {
            return RuntimeList::new(
                Vec::new(),
                vec![diagnostic(
                    "local_pod_registry_unavailable",
                    DiagnosticSeverity::Warning,
                    "local Pod data directory is not configured; worker discovery is unavailable"
                        .to_string(),
                )],
            );
        };
        if limit == 0 {
            return RuntimeList::new(Vec::new(), Vec::new());
        }

        let mut workers = Vec::new();
        let mut diagnostics = Vec::new();
        let pod_names = match self.pod_names(&pod_root) {
            Ok(pod_names) => pod_names,
            Err(diag) => return RuntimeList::new(Vec::new(), vec![diag]),
        };

        for pod_name in pod_names {
            if workers.len() >= limit {
                break;
            }
            match self.read_worker(&pod_root, &pod_name) {
                WorkerReadOutcome::Worker(worker) => workers.push(worker),
                WorkerReadOutcome::Diagnostic(diag) => {
                    if diagnostics.len() < MAX_DIAGNOSTICS {
                        diagnostics.push(diag);
                    }
                }
            }
        }
        RuntimeList::new(workers, diagnostics)
    }

    fn worker(&self, worker_id: &str) -> WorkerLookupResult {
        let Some(pod_root) = self.pod_root() else {
            return WorkerLookupResult {
                worker: None,
                diagnostics: vec![diagnostic(
                    "local_pod_registry_unavailable",
                    DiagnosticSeverity::Warning,
                    "local Pod data directory is not configured; worker discovery is unavailable"
                        .to_string(),
                )],
            };
        };
        let pod_names = match self.pod_names(&pod_root) {
            Ok(pod_names) => pod_names,
            Err(diag) => {
                return WorkerLookupResult {
                    worker: None,
                    diagnostics: vec![diag],
                };
            }
        };
        for pod_name in pod_names {
            if worker_id_for_pod(&pod_name) != worker_id {
                continue;
            }
            return match self.read_worker(&pod_root, &pod_name) {
                WorkerReadOutcome::Worker(worker) => WorkerLookupResult {
                    worker: Some(worker),
                    diagnostics: Vec::new(),
                },
                WorkerReadOutcome::Diagnostic(diag) => WorkerLookupResult {
                    worker: None,
                    diagnostics: vec![diag],
                },
            };
        }
        WorkerLookupResult {
            worker: None,
            diagnostics: Vec::new(),
        }
    }
}

enum WorkerReadOutcome {
    Worker(WorkerSummary),
    Diagnostic(RuntimeDiagnostic),
}

fn local_runtime_capabilities(
    limit: usize,
    inspection_available: bool,
) -> RuntimeCapabilitySummary {
    RuntimeCapabilitySummary {
        can_list_hosts: true,
        can_list_workers: inspection_available,
        can_get_worker: inspection_available,
        can_spawn_worker: false,
        can_stop_worker: false,
        can_accept_input: false,
        can_stream_events: false,
        can_read_bounded_transcript: false,
        has_workspace_fs: false,
        has_shell: false,
        has_git: false,
        supports_worktrees: false,
        supports_backend_internal_tools: false,
        local_pod_inspection: if inspection_available {
            "available"
        } else {
            "unavailable"
        }
        .to_string(),
        workspace_scope: "current_workspace".to_string(),
        max_workers: limit,
        os: std::env::consts::OS.to_string(),
        arch: std::env::consts::ARCH.to_string(),
    }
}

fn diagnostic(
    code: impl Into<String>,
    severity: DiagnosticSeverity,
    message: impl Into<String>,
) -> RuntimeDiagnostic {
    RuntimeDiagnostic {
        code: code.into(),
        severity,
        message: message.into(),
    }
}

fn host_id_for_workspace(workspace_id: &str) -> String {
    bounded_backend_identifier("local-", workspace_id)
}

fn worker_id_for_pod(pod_name: &str) -> String {
    bounded_backend_identifier("local-pod-", pod_name)
}

fn bounded_backend_identifier(prefix: &str, value: &str) -> String {
    let digest = digest_hex(value.as_bytes(), ID_DIGEST_HEX_LEN);
    let mut body = sanitize_identifier_body(value);
    if body.is_empty() {
        body = "id".to_string();
    }

    let suffix_len = 1 + ID_DIGEST_HEX_LEN;
    let body_budget = MAX_IDENTIFIER_LEN
        .saturating_sub(prefix.len())
        .saturating_sub(suffix_len)
        .max(1);
    if body.len() > body_budget {
        body.truncate(body_budget);
        body = body.trim_matches('-').to_string();
        if body.is_empty() {
            body = "id".to_string();
        }
    }

    let mut id = format!("{prefix}{body}-{digest}");
    if id.len() > MAX_IDENTIFIER_LEN {
        let digest_suffix = format!("-{digest}");
        let prefix_budget = MAX_IDENTIFIER_LEN.saturating_sub(digest_suffix.len());
        id = format!(
            "{}{}",
            prefix.chars().take(prefix_budget).collect::<String>(),
            digest_suffix
        );
    }
    id
}

fn sanitize_identifier_body(value: &str) -> String {
    let mut out = String::with_capacity(value.len());
    for ch in value.chars() {
        if ch.is_ascii_alphanumeric() {
            out.push(ch.to_ascii_lowercase());
        } else if ch == '-' || ch == '_' {
            out.push(ch);
        } else {
            out.push('-');
        }
    }
    out.trim_matches('-').to_string()
}

fn digest_hex(bytes: &[u8], hex_len: usize) -> String {
    let digest = Sha256::digest(bytes);
    let mut out = String::with_capacity(hex_len);
    for byte in digest {
        if out.len() >= hex_len {
            break;
        }
        out.push_str(&format!("{byte:02x}"));
    }
    out.truncate(hex_len);
    out
}

fn validate_backend_identifier(
    kind: &'static str,
    value: &str,
) -> Result<(), RuntimeRegistryError> {
    if value.is_empty()
        || value.len() > MAX_IDENTIFIER_LEN
        || value
            .chars()
            .any(|ch| !(ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' || ch == ':'))
    {
        return Err(RuntimeRegistryError::InvalidIdentifier {
            kind,
            value: value.chars().take(MAX_IDENTIFIER_LEN).collect(),
        });
    }
    Ok(())
}

fn label_for_workspace(workspace_root: &Path) -> String {
    workspace_root
        .file_name()
        .and_then(|name| name.to_str())
        .filter(|name| !name.is_empty())
        .unwrap_or("workspace")
        .to_string()
}

fn same_workspace_root(left: &Path, right: &Path) -> bool {
    lexical_path_string(left) == lexical_path_string(right)
}

fn lexical_path_string(path: &Path) -> String {
    let mut value = path.to_string_lossy().replace('\\', "/");
    while value.len() > 1 && value.ends_with('/') {
        value.pop();
    }
    value
}

fn safe_display_hint(value: &str) -> String {
    value
        .chars()
        .filter(|ch| !ch.is_control() && *ch != '/' && *ch != '\\')
        .take(80)
        .collect()
}

fn worker_spawn_intent_label(intent: &WorkerSpawnIntent) -> &'static str {
    match intent {
        WorkerSpawnIntent::WorkspaceCompanion => "workspace_companion",
        WorkerSpawnIntent::WorkspaceOrchestrator => "workspace_orchestrator",
        WorkerSpawnIntent::TicketRole { role, .. } => match role {
            TicketWorkerRole::Intake => "ticket_intake",
            TicketWorkerRole::Orchestrator => "ticket_orchestrator",
            TicketWorkerRole::Coder => "ticket_coder",
            TicketWorkerRole::Reviewer => "ticket_reviewer",
        },
    }
}

fn manifest_hint_string(snapshot: &Option<Value>, key: &str) -> Option<String> {
    let value = snapshot.as_ref()?;
    let candidate = match key {
        "role" => value
            .get("role")
            .or_else(|| value.get("role_claim").and_then(|role| role.get("role"))),
        "profile_selector" => value
            .get("profile_selector")
            .or_else(|| value.get("profile")),
        _ => value.get(key),
    }?;
    let text = candidate.as_str().map(ToOwned::to_owned).or_else(|| {
        candidate
            .get("name")
            .and_then(|name| name.as_str())
            .map(ToOwned::to_owned)
    })?;
    let safe = safe_display_hint(&text);
    (!safe.is_empty()).then_some(safe)
}

pub fn placeholder_worker(host_id: impl Into<String>) -> WorkerSummary {
    let host_id = host_id.into();
    WorkerSummary {
        runtime_id: "placeholder".to_string(),
        worker_id: "worker-placeholder".to_string(),
        host_id,
        label: "Worker runtime actions are not implemented".to_string(),
        role: None,
        profile: None,
        workspace: WorkerWorkspaceSummary {
            visibility: "none".to_string(),
            identity: "unsupported".to_string(),
        },
        state: "unsupported".to_string(),
        status: "Worker runtime control is not wired yet".to_string(),
        last_seen_at: None,
        implementation: WorkerImplementationSummary {
            kind: "placeholder".to_string(),
            display_hint: "unsupported".to_string(),
        },
        capabilities: WorkerCapabilitySummary {
            can_accept_input: false,
            can_stream_events: false,
            can_stop: false,
            can_spawn_followup: false,
            can_read_bounded_transcript: false,
        },
        diagnostics: vec![diagnostic(
            "runtime_capability_unsupported",
            DiagnosticSeverity::Info,
            "worker control is outside this overview-only registry surface".to_string(),
        )],
    }
}

pub fn placeholder_spawn_response(host_id: impl Into<String>) -> WorkerSpawnResult {
    WorkerSpawnResult {
        state: WorkerOperationState::Unsupported,
        worker: Some(placeholder_worker(host_id)),
        acceptance_evidence: Vec::new(),
        diagnostics: vec![diagnostic(
            "worker_spawn_unsupported",
            DiagnosticSeverity::Info,
            "Workspace worker runtime control is not implemented yet".to_string(),
        )],
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;
    use std::fs;
    use tempfile::TempDir;

    fn write_metadata(dir: &Path, pod_name: &str, metadata: &PodMetadata) {
        let pod_dir = dir.join("pods").join(pod_name);
        fs::create_dir_all(&pod_dir).unwrap();
        fs::write(
            pod_dir.join("metadata.json"),
            serde_json::to_vec(metadata).unwrap(),
        )
        .unwrap();
    }

    fn metadata(workspace_root: Option<&str>) -> PodMetadata {
        let mut metadata = PodMetadata::new("coder", None);
        metadata.workspace_root = workspace_root.map(PathBuf::from);
        metadata.resolved_manifest_snapshot = Some(json!({
            "role": "coder",
            "profile_selector": "builtin:coder"
        }));
        metadata
    }

    fn assert_valid_generated_id(id: &str) {
        assert!(id.len() <= MAX_IDENTIFIER_LEN, "id too long: {id}");
        validate_backend_identifier("test_id", id).unwrap();
    }

    fn host_id() -> String {
        host_id_for_workspace("local:test")
    }

    #[test]
    fn local_runtime_reports_host_without_private_paths() {
        let bridge = LocalPodRuntime::new("local:test", "/workspace/project", None);
        let hosts = bridge.list_hosts(10);
        assert_eq!(hosts.items.len(), 1);
        let host = &hosts.items[0];
        assert_eq!(host.runtime_id, LOCAL_RUNTIME_ID);
        assert_eq!(host.host_id, host_id());
        assert_valid_generated_id(&host.host_id);
        assert_eq!(host.capabilities.local_pod_inspection, "unavailable");
        assert_eq!(host.capabilities.workspace_scope, "current_workspace");
        let json = serde_json::to_string(host).unwrap();
        assert!(!json.contains("/workspace/project"));
        assert!(!json.contains("metadata.json"));
    }

    #[test]
    fn registry_lists_runtimes_hosts_and_workers() {
        let temp = TempDir::new().unwrap();
        write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project")));
        let registry = WorkerRuntimeRegistry::for_local_pods(LocalPodRuntime::new(
            "local:test",
            "/workspace/project",
            Some(temp.path().to_path_buf()),
        ));

        let runtimes = registry.list_runtimes(10);
        assert_eq!(runtimes.items[0].runtime_id, LOCAL_RUNTIME_ID);
        assert_eq!(runtimes.items[0].host_ids, vec![host_id()]);

        let hosts = registry.list_hosts(10);
        assert_eq!(hosts.items[0].host_id, host_id());
        assert_valid_generated_id(&hosts.items[0].host_id);

        let workers = registry.list_workers(10);
        assert_eq!(workers.items.len(), 1);
        let worker = &workers.items[0];
        assert_eq!(worker.runtime_id, LOCAL_RUNTIME_ID);
        assert_eq!(worker.worker_id, worker_id_for_pod("coder"));
        assert_valid_generated_id(&worker.worker_id);
        assert_eq!(worker.host_id, host_id());
        assert_eq!(worker.workspace.visibility, "current_workspace");
        assert_eq!(worker.implementation.display_hint, "coder");
        let json = serde_json::to_string(worker).unwrap();
        assert!(!json.contains("/workspace/project"));
        assert!(!json.contains("metadata.json"));
    }

    #[test]
    fn registry_resolves_backend_validated_host_ids() {
        let temp = TempDir::new().unwrap();
        write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project")));
        let registry = WorkerRuntimeRegistry::for_local_pods(LocalPodRuntime::new(
            "local:test",
            "/workspace/project",
            Some(temp.path().to_path_buf()),
        ));

        let workers = registry.list_workers_for_host(&host_id(), 10).unwrap();
        assert_eq!(workers.items.len(), 1);
        assert!(matches!(
            registry.list_workers_for_host("../secret", 10),
            Err(RuntimeRegistryError::InvalidIdentifier { .. })
        ));
        assert!(matches!(
            registry.list_workers_for_host("local-missing", 10),
            Err(RuntimeRegistryError::UnknownHost(_))
        ));
    }

    #[test]
    fn local_runtime_excludes_other_workspace_metadata() {
        let temp = TempDir::new().unwrap();
        write_metadata(temp.path(), "other", &metadata(Some("/workspace/other")));
        write_metadata(temp.path(), "missing", &metadata(None));
        let bridge = LocalPodRuntime::new(
            "local:test",
            "/workspace/project",
            Some(temp.path().to_path_buf()),
        );
        let workers = bridge.list_workers(10);
        assert!(workers.items.is_empty());
        assert!(
            workers
                .diagnostics
                .iter()
                .any(|diag| diag.code == "local_pod_workspace_not_visible")
        );
        assert!(
            workers
                .diagnostics
                .iter()
                .any(|diag| diag.code == "local_pod_workspace_root_missing")
        );
    }

    #[test]
    fn local_runtime_worker_detail_is_safe_and_bounded() {
        let temp = TempDir::new().unwrap();
        write_metadata(temp.path(), "coder", &metadata(Some("/workspace/project")));
        let bridge = LocalPodRuntime::new(
            "local:test",
            "/workspace/project",
            Some(temp.path().to_path_buf()),
        );
        let worker_id = worker_id_for_pod("coder");
        let worker = bridge.worker(&worker_id).worker.unwrap();
        assert_eq!(worker.label, "coder");
        assert_eq!(worker.workspace.identity, "runtime_workspace");
        assert!(
            bridge
                .worker(&worker_id_for_pod("missing"))
                .worker
                .is_none()
        );
    }

    #[test]
    fn generated_worker_ids_are_opaque_bounded_unique_and_resolvable() {
        let temp = TempDir::new().unwrap();
        let long_a = format!("{}-A", "TicketWorkerWithVeryLongName".repeat(8));
        let long_b = format!("{}-B", "TicketWorkerWithVeryLongName".repeat(8));
        let pod_names = vec![
            "00001KVWECEQG-Coder.Pod".to_string(),
            "foo.bar".to_string(),
            "foo-bar".to_string(),
            "Ticket#Worker@Reviewer".to_string(),
            long_a,
            long_b,
        ];
        for pod_name in &pod_names {
            write_metadata(temp.path(), pod_name, &metadata(Some("/workspace/project")));
        }
        let bridge = LocalPodRuntime::new(
            "local:test",
            "/workspace/project",
            Some(temp.path().to_path_buf()),
        );
        let registry = WorkerRuntimeRegistry::for_local_pods(bridge.clone());

        let listed = registry.list_workers(100);
        assert_eq!(listed.items.len(), pod_names.len());
        let mut ids = BTreeSet::new();
        for worker in listed.items {
            assert_valid_generated_id(&worker.worker_id);
            assert!(
                ids.insert(worker.worker_id.clone()),
                "duplicate id: {}",
                worker.worker_id
            );
            assert!(!worker.worker_id.contains('.'));
            assert!(!worker.worker_id.contains('@'));
            assert!(!worker.worker_id.contains('#'));

            let from_registry = registry.worker(&worker.worker_id).unwrap();
            assert_eq!(from_registry.worker_id, worker.worker_id);
            let from_runtime = bridge.worker(&worker.worker_id).worker.unwrap();
            assert_eq!(from_runtime.worker_id, worker.worker_id);
        }

        let dotted = worker_id_for_pod("foo.bar");
        let dashed = worker_id_for_pod("foo-bar");
        assert_ne!(dotted, dashed);
        assert!(ids.contains(&dotted));
        assert!(ids.contains(&dashed));
        assert!(ids.iter().any(|id| id.len() == MAX_IDENTIFIER_LEN));
    }
}
