use crate::backend::conch::{
    exec::ConchExec, filesystem::ConchFileSystem, path::ConchPathResolver, search::ConchSearch,
};
use agent_contracts::backend::{
    capability::{
        OperationExec, OperationExport, OperationFileSystem, OperationPathResolver, OperationSearch,
    },
    BackendPath, OperationBackend, OperationBackendCapabilities, OperationError,
};
use async_trait::async_trait;
use std::collections::HashMap;
use std::path::{Component, Path, PathBuf};
use std::sync::{Arc, Mutex};

pub(crate) struct ConchBackendState {
    pub(crate) backend_id: String,
    pub(crate) workspace_root: BackendPath,
    pub(crate) home_dir: Option<BackendPath>,
    pub(crate) temp_root: BackendPath,
    pub(crate) default_shell: Option<String>,
    pub(crate) control_plane: ConchControlPlane,
    pub(crate) sandbox: ConchSandboxHandle,
    pub(crate) lifecycle: Mutex<ConchLifecycle>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConchLifecycle {
    Active,
    ShuttingDown,
    Closed,
}

#[derive(Clone)]
pub(crate) struct ConchControlPlane {
    pub(crate) transport: ConchControlTransport,
    pub(crate) namespace: String,
}

#[derive(Clone)]
pub(crate) enum ConchControlTransport {
    UnixSocket(PathBuf),
    ApiUrl(String),
}

#[derive(Clone)]
pub(crate) struct ConchSandboxHandle {
    pub(crate) sandbox_id: String,
    pub(crate) ip: String,
    pub(crate) agent_port: u16,
}

pub(crate) struct ConchStartProcess {
    pub(crate) cmd: String,
    pub(crate) args: Vec<String>,
    pub(crate) env: HashMap<String, String>,
    pub(crate) cwd: Option<String>,
    pub(crate) content: Option<String>,
}

pub(crate) struct ConchExecOutput {
    pub(crate) stdout: Vec<u8>,
    pub(crate) stderr: Vec<u8>,
    pub(crate) exit_code: Option<i32>,
    pub(crate) timed_out: bool,
}

pub(crate) struct ConchUploadFile {
    pub(crate) filepath: String,
    pub(crate) content: Vec<u8>,
}

pub struct ConchOperationBackend {
    backend_id: String,
    capabilities: OperationBackendCapabilities,
    paths: ConchPathResolver,
    files: ConchFileSystem,
    search: ConchSearch,
    exec: ConchExec,
}

impl ConchOperationBackend {
    pub(crate) fn new(state: Arc<ConchBackendState>) -> Self {
        Self {
            backend_id: state.backend_id.clone(),
            capabilities: OperationBackendCapabilities {
                supports_atomic_write: false,
                supports_grep: true,
                supports_export_file: true,
                supports_lsp: false,
            },
            paths: ConchPathResolver::new(Arc::clone(&state)),
            files: ConchFileSystem::new(Arc::clone(&state)),
            search: ConchSearch::new(Arc::clone(&state)),
            exec: ConchExec::new(state),
        }
    }
}

impl ConchBackendState {
    pub(crate) fn ensure_active(&self) -> Result<(), OperationError> {
        let lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| OperationError::Transport {
                message: "conch backend state lock poisoned".to_string(),
            })?;
        match *lifecycle {
            ConchLifecycle::Active => Ok(()),
            ConchLifecycle::ShuttingDown => Err(OperationError::Transport {
                message: format!("conch backend {} is shutting down", self.backend_id),
            }),
            ConchLifecycle::Closed => Err(OperationError::Transport {
                message: format!("conch backend {} is already closed", self.backend_id),
            }),
        }
    }

    pub(crate) fn begin_shutdown(&self) -> Result<bool, OperationError> {
        let mut lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| OperationError::Transport {
                message: "conch backend state lock poisoned".to_string(),
            })?;
        match *lifecycle {
            ConchLifecycle::Active => {
                *lifecycle = ConchLifecycle::ShuttingDown;
                Ok(true)
            }
            ConchLifecycle::ShuttingDown | ConchLifecycle::Closed => Ok(false),
        }
    }

    pub(crate) fn finish_shutdown(&self) -> Result<(), OperationError> {
        let mut lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| OperationError::Transport {
                message: "conch backend state lock poisoned".to_string(),
            })?;
        *lifecycle = ConchLifecycle::Closed;
        Ok(())
    }

    pub(crate) fn abort_shutdown(&self) -> Result<(), OperationError> {
        let mut lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| OperationError::Transport {
                message: "conch backend state lock poisoned".to_string(),
            })?;
        if *lifecycle == ConchLifecycle::ShuttingDown {
            *lifecycle = ConchLifecycle::Active;
        }
        Ok(())
    }

    pub(crate) fn resolve_backend_path(
        &self,
        raw_path: &str,
        base: &BackendPath,
    ) -> Result<BackendPath, OperationError> {
        if raw_path == "~" || raw_path.starts_with("~/") {
            let home_dir = self
                .home_dir
                .as_ref()
                .ok_or_else(|| OperationError::Unsupported {
                    message: "home_dir is not configured".to_string(),
                })?;
            let suffix = raw_path.strip_prefix("~/").unwrap_or_default();
            return normalize_backend_path(Path::new(home_dir.0.as_str()).join(suffix).as_path());
        }

        let candidate = Path::new(raw_path);
        if candidate.is_absolute() {
            return normalize_backend_path(candidate);
        }
        normalize_backend_path(Path::new(base.0.as_str()).join(candidate).as_path())
    }
}

pub(crate) fn normalize_backend_path(path: &Path) -> Result<BackendPath, OperationError> {
    if !path.is_absolute() {
        return Err(OperationError::InvalidPath {
            message: format!("path must be absolute: {}", path.display()),
        });
    }

    let mut normalized = PathBuf::new();
    for component in path.components() {
        match component {
            Component::RootDir => normalized.push(Path::new(std::path::MAIN_SEPARATOR_STR)),
            Component::CurDir => {}
            Component::ParentDir => {
                if !normalized.pop() {
                    return Err(OperationError::InvalidPath {
                        message: format!("path escapes root: {}", path.display()),
                    });
                }
            }
            Component::Normal(part) => normalized.push(part),
            Component::Prefix(prefix) => normalized.push(prefix.as_os_str()),
        }
    }

    let text = normalized
        .to_str()
        .ok_or_else(|| OperationError::InvalidPath {
            message: format!("path is not valid utf-8: {}", normalized.display()),
        })?;
    Ok(BackendPath(text.to_string()))
}

pub(crate) fn shell_quote(value: &str) -> String {
    if value.is_empty() {
        return "''".to_string();
    }
    format!("'{}'", value.replace('\'', "'\\''"))
}

#[async_trait]
impl OperationBackend for ConchOperationBackend {
    fn backend_id(&self) -> &str {
        self.backend_id.as_str()
    }

    fn capabilities(&self) -> OperationBackendCapabilities {
        self.capabilities
    }

    fn paths(&self) -> &dyn OperationPathResolver {
        &self.paths as &dyn OperationPathResolver
    }

    fn files(&self) -> &dyn OperationFileSystem {
        &self.files as &dyn OperationFileSystem
    }

    fn search(&self) -> &dyn OperationSearch {
        &self.search as &dyn OperationSearch
    }

    fn exec(&self) -> &dyn OperationExec {
        &self.exec as &dyn OperationExec
    }

    fn export(&self) -> &dyn OperationExport {
        &self.files as &dyn OperationExport
    }

    async fn shutdown(&self) -> Result<(), OperationError> {
        if !self.exec.state().begin_shutdown()? {
            return Ok(());
        }
        match crate::backend::conch::control::delete_sandbox(self.exec.state()).await {
            Ok(()) => {
                self.exec.state().finish_shutdown()?;
                Ok(())
            }
            Err(error) => {
                self.exec.state().abort_shutdown()?;
                Err(error)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn test_state(home_dir: Option<BackendPath>) -> ConchBackendState {
        ConchBackendState {
            backend_id: "test".to_string(),
            workspace_root: BackendPath("/workspace".to_string()),
            home_dir,
            temp_root: BackendPath("/tmp".to_string()),
            default_shell: None,
            control_plane: ConchControlPlane {
                transport: ConchControlTransport::ApiUrl("http://127.0.0.1".to_string()),
                namespace: "test".to_string(),
            },
            sandbox: ConchSandboxHandle {
                sandbox_id: "sandbox".to_string(),
                ip: "127.0.0.1".to_string(),
                agent_port: 1234,
            },
            lifecycle: Mutex::new(ConchLifecycle::Active),
        }
    }

    #[test]
    fn resolves_tilde_paths_against_home_dir() {
        let state = test_state(Some(BackendPath("/home/user".to_string())));
        let resolved = state
            .resolve_backend_path("~/.xiaoo/tools/md_to_html.mjs", &state.workspace_root)
            .expect("resolve");

        assert_eq!(
            resolved,
            BackendPath("/home/user/.xiaoo/tools/md_to_html.mjs".to_string())
        );
    }

    #[test]
    fn tilde_requires_configured_home_dir() {
        let state = test_state(None);
        let error = state
            .resolve_backend_path("~/missing", &state.workspace_root)
            .expect_err("tilde should require home");

        assert!(matches!(error, OperationError::Unsupported { .. }));
    }
}