use crate::backend::conch::backend::{
    ConchBackendState, ConchControlTransport, ConchSandboxHandle,
};
use agent_contracts::backend::{OperationBackendBuildError, OperationError};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Clone)]
pub(crate) struct ConchCreateOptions {
    pub(crate) namespace: String,
    pub(crate) sandbox_id: String,
    pub(crate) image_name: String,
    pub(crate) snapshot_id: Option<String>,
    pub(crate) use_snapshot: bool,
    pub(crate) vmm_name: String,
    pub(crate) vcpu_num: i64,
    pub(crate) ram_mb: i64,
}

#[derive(Debug, Serialize)]
struct SandboxCreateRequest<'a> {
    namespace: &'a str,
    snapshot_id: &'a str,
    image_name: &'a str,
    use_snapshot: bool,
    vmm_name: &'a str,
    sandbox_id: &'a str,
    vcpu_num: i64,
    ram_mb: i64,
}

#[derive(Debug, Deserialize)]
struct SandboxCreateResponse {
    status: String,
    ip: String,
}

#[derive(Debug, Serialize)]
struct SandboxDeleteRequest<'a> {
    namespace: &'a str,
    sandbox_id: &'a str,
}

#[derive(Debug, Deserialize)]
struct SandboxDeleteResponse {
    status: String,
}

pub(crate) async fn create_sandbox(
    transport: &ConchControlTransport,
    options: &ConchCreateOptions,
) -> Result<ConchSandboxHandle, OperationBackendBuildError> {
    let client = build_client(transport)?;
    let url = control_plane_url(transport, "/api/sandbox/create");
    let snapshot_id = options.snapshot_id.as_deref().unwrap_or("");
    let response = client
        .post(url)
        .json(&SandboxCreateRequest {
            namespace: options.namespace.as_str(),
            snapshot_id,
            image_name: options.image_name.as_str(),
            use_snapshot: options.use_snapshot,
            vmm_name: options.vmm_name.as_str(),
            sandbox_id: options.sandbox_id.as_str(),
            vcpu_num: options.vcpu_num,
            ram_mb: options.ram_mb,
        })
        .send()
        .await
        .map_err(|error| OperationBackendBuildError::BuildFailed {
            message: format!("failed to call conch create endpoint: {error}"),
        })?;
    let status = response.status();
    let body = response
        .text()
        .await
        .map_err(|error| OperationBackendBuildError::BuildFailed {
            message: format!("failed to read conch create response: {error}"),
        })?;

    if !status.is_success() {
        return Err(OperationBackendBuildError::BuildFailed {
            message: format!("conch create failed with {status}: {body}"),
        });
    }

    let parsed: SandboxCreateResponse = serde_json::from_str(body.as_str()).map_err(|error| {
        OperationBackendBuildError::BuildFailed {
            message: format!("invalid conch create response: {error}"),
        }
    })?;
    if parsed.status != "ok" {
        return Err(OperationBackendBuildError::BuildFailed {
            message: format!("conch create returned non-ok status: {}", parsed.status),
        });
    }

    Ok(ConchSandboxHandle {
        sandbox_id: options.sandbox_id.clone(),
        ip: parsed.ip,
        agent_port: 4064,
    })
}

pub(crate) async fn delete_sandbox(state: &ConchBackendState) -> Result<(), OperationError> {
    let client = build_client(&state.control_plane.transport).map_err(|error| {
        OperationError::Transport {
            message: error.to_string(),
        }
    })?;
    let url = control_plane_url(&state.control_plane.transport, "/api/sandbox/delete");
    let response = client
        .post(url)
        .json(&SandboxDeleteRequest {
            namespace: state.control_plane.namespace.as_str(),
            sandbox_id: state.sandbox.sandbox_id.as_str(),
        })
        .send()
        .await
        .map_err(|error| OperationError::Transport {
            message: format!("failed to call conch delete endpoint: {error}"),
        })?;
    let status = response.status();
    let body = response
        .text()
        .await
        .map_err(|error| OperationError::Transport {
            message: format!("failed to read conch delete response: {error}"),
        })?;
    if !status.is_success() {
        return Err(OperationError::Transport {
            message: format!("conch delete failed with {status}: {body}"),
        });
    }

    let parsed: SandboxDeleteResponse =
        serde_json::from_str(body.as_str()).map_err(|error| OperationError::Transport {
            message: format!("invalid conch delete response: {error}"),
        })?;
    if parsed.status != "ok" {
        return Err(OperationError::Transport {
            message: format!("conch delete returned non-ok status: {}", parsed.status),
        });
    }
    Ok(())
}

fn build_client(transport: &ConchControlTransport) -> Result<Client, OperationBackendBuildError> {
    let builder = Client::builder();
    match transport {
        ConchControlTransport::ApiUrl(_) => {
            builder
                .build()
                .map_err(|error| OperationBackendBuildError::BuildFailed {
                    message: format!("failed to build conch http client: {error}"),
                })
        }
        ConchControlTransport::UnixSocket(path) => unix_socket_client(builder, path),
    }
}

#[cfg(unix)]
fn unix_socket_client(
    builder: reqwest::ClientBuilder,
    path: &PathBuf,
) -> Result<Client, OperationBackendBuildError> {
    builder.unix_socket(path.clone()).build().map_err(|error| {
        OperationBackendBuildError::BuildFailed {
            message: format!("failed to build conch unix-socket client: {error}"),
        }
    })
}

#[cfg(not(unix))]
fn unix_socket_client(
    _builder: reqwest::ClientBuilder,
    _path: &PathBuf,
) -> Result<Client, OperationBackendBuildError> {
    Err(OperationBackendBuildError::BuildFailed {
        message: "unix socket control plane is only supported on unix hosts".to_string(),
    })
}

fn control_plane_url(transport: &ConchControlTransport, path: &str) -> String {
    match transport {
        ConchControlTransport::ApiUrl(base_url) => {
            format!("{}{}", base_url.trim_end_matches('/'), path)
        }
        ConchControlTransport::UnixSocket(_) => format!("http://localhost{path}"),
    }
}