use std::{
    env,
    sync::{
        atomic::{AtomicU64, Ordering},
        Arc,
    },
};

use anyhow::Result;
use jsonrpc_lite::{Id, JsonRpc};
use lapce_wasi_experimental_http::Response;
use once_cell::sync::Lazy;
pub use psp_types;
use psp_types::{
    lsp_types::{
        notification::{LogMessage, ShowMessage},
        DocumentSelector, LogMessageParams, MessageType, ShowMessageParams, Url,
    },
    ExecuteProcess, ExecuteProcessParams, ExecuteProcessResult, Notification, RegisterDebuggerType,
    RegisterDebuggerTypeParams, Request, StartLspServer, StartLspServerParams,
};
use serde::{de::DeserializeOwned, Serialize};
use serde_json::Value;

pub static PLUGIN_RPC: Lazy<PluginServerRpcHandler> = Lazy::new(PluginServerRpcHandler::new);

/// Helper struct abstracting environment variables
/// names used in lapce to provide revelant host
/// environment information, so plugin maintainers
/// don't have to hardcode specific variable names
pub struct VoltEnvironment {}

impl VoltEnvironment {
    /// Plugin location path encoded as Url
    pub fn uri() -> Result<String, env::VarError> {
        env::var("VOLT_URI")
    }

    /// Operating system name as provided by
    /// std::env::consts::OS
    pub fn operating_system() -> Result<String, env::VarError> {
        env::var("VOLT_OS")
    }

    /// Processor architecture name as provided by
    /// std::env::consts::ARCH
    pub fn architecture() -> Result<String, env::VarError> {
        env::var("VOLT_ARCH")
    }

    /// C library used on host detected by parsing ldd output
    /// provided because of musl-based linux distros and distros
    /// that need statically linked binaries due to how
    /// linking works (e.g. nixOS)
    /// Currently only 2 options are available: glibc | musl
    /// This function will return empty string on non-linux
    /// hosts
    pub fn libc() -> Result<String, env::VarError> {
        env::var("VOLT_LIBC")
    }
}

#[allow(unused_variables)]
pub trait LapcePlugin {
    fn handle_request(&mut self, id: u64, method: String, params: Value) {}
    fn handle_notification(&mut self, method: String, params: Value) {}
}

pub enum PluginServerRpc {
    Request {
        id: u64,
        method: String,
        params: Value,
    },
    Notification {
        method: String,
        params: Value,
    },
}

pub struct PluginServerRpcHandler {
    id: Arc<AtomicU64>,
}

pub struct Http {}

impl Http {
    pub fn get(url: &str) -> Result<Response> {
        let req = http::request::Builder::new()
            .method(http::Method::GET)
            .uri(url)
            .body(None)?;
        let resp = lapce_wasi_experimental_http::request(req)?;
        Ok(resp)
    }
}

#[macro_export]
macro_rules! register_plugin {
    ($t:ty) => {
        thread_local! {
            static STATE: std::cell::RefCell<$t> = std::cell::RefCell::new(Default::default());
        }

        fn main() {}

        #[no_mangle]
        pub fn handle_rpc() {
            if let Ok(rpc) = $crate::parse_stdin() {
                match rpc {
                    $crate::PluginServerRpc::Request { id, method, params } => {
                        STATE.with(|state| {
                            state.borrow_mut().handle_request(id, method, params);
                        });
                    }
                    $crate::PluginServerRpc::Notification { method, params } => {
                        STATE.with(|state| {
                            state.borrow_mut().handle_notification(method, params);
                        });
                    }
                }
            }
        }
    };
}

impl PluginServerRpcHandler {
    fn new() -> Self {
        Self {
            id: Arc::new(AtomicU64::new(0)),
        }
    }

    pub fn stderr(&self, msg: &str) {
        eprintln!("{}", msg);
        unsafe { host_handle_stderr() };
    }

    pub fn window_log_message(&self, kind: MessageType, message: String) {
        self.host_notification(LogMessage::METHOD, LogMessageParams { typ: kind, message });
    }

    pub fn window_show_message(&self, kind: MessageType, message: String) {
        self.host_notification(
            ShowMessage::METHOD,
            ShowMessageParams { typ: kind, message },
        );
    }

    pub fn start_lsp(
        &self,
        server_uri: Url,
        server_args: Vec<String>,
        document_selector: DocumentSelector,
        options: Option<Value>,
    ) {
        self.host_notification(
            StartLspServer::METHOD,
            StartLspServerParams {
                server_uri,
                server_args,
                document_selector,
                options,
            },
        );
    }

    pub fn execute_process(
        &self,
        program: String,
        args: Vec<String>,
    ) -> Result<ExecuteProcessResult, jsonrpc_lite::Error> {
        self.host_request(
            ExecuteProcess::METHOD,
            ExecuteProcessParams { program, args },
        )
    }

    pub fn register_debugger_type(
        &self,
        debugger_type: String,
        program: String,
        args: Option<Vec<String>>,
    ) -> Result<(), jsonrpc_lite::Error> {
        self.host_request(
            RegisterDebuggerType::METHOD,
            RegisterDebuggerTypeParams {
                debugger_type,
                program,
                args,
            },
        )
    }

    fn host_request<P: Serialize, D: DeserializeOwned>(
        &self,
        method: &str,
        params: P,
    ) -> Result<D, jsonrpc_lite::Error> {
        let id = self.id.fetch_add(1, Ordering::Relaxed);
        let params = serde_json::to_value(params).unwrap();
        send_host_request(id, method, &params);
        let mut msg = String::new();
        std::io::stdin().read_line(&mut msg).unwrap();

        match JsonRpc::parse(&msg) {
            Ok(rpc) => {
                if let Some(value) = rpc.get_result() {
                    let result: Result<D, serde_json::Error> =
                        serde_json::from_value(value.clone());
                    result.map_err(|_| jsonrpc_lite::Error::invalid_request())
                } else if let Some(err) = rpc.get_error() {
                    Err(err.clone())
                } else {
                    Err(jsonrpc_lite::Error::invalid_request())
                }
            }
            _ => Err(jsonrpc_lite::Error::invalid_request()),
        }
    }

    fn host_notification<P: Serialize>(&self, method: &str, params: P) {
        let params = serde_json::to_value(params).unwrap();
        send_host_notification(method, &params);
    }
}

fn number_from_id(id: &Id) -> u64 {
    match *id {
        Id::Num(n) => n as u64,
        Id::Str(ref s) => s
            .parse::<u64>()
            .expect("failed to convert string id to u64"),
        _ => panic!("unexpected value for id: None"),
    }
}

pub fn parse_stdin() -> Result<PluginServerRpc, serde_json::Error> {
    let mut msg = String::new();
    std::io::stdin().read_line(&mut msg).unwrap();
    let rpc = match JsonRpc::parse(&msg) {
        Ok(value @ JsonRpc::Request(_)) => {
            let id = number_from_id(&value.get_id().unwrap());
            PluginServerRpc::Request {
                id,
                method: value.get_method().unwrap().to_string(),
                params: serde_json::to_value(value.get_params().unwrap()).unwrap(),
            }
        }
        Ok(value @ JsonRpc::Notification(_)) => PluginServerRpc::Notification {
            method: value.get_method().unwrap().to_string(),
            params: serde_json::to_value(value.get_params().unwrap()).unwrap(),
        },
        Ok(_value @ JsonRpc::Success(_)) => {
            todo!()
        }
        Ok(_value @ JsonRpc::Error(_)) => {
            todo!()
        }
        Err(_err) => {
            todo!()
        }
    };
    Ok(rpc)
}

pub fn object_from_stdin<T: DeserializeOwned>() -> Result<T, serde_json::Error> {
    let mut json = String::new();
    std::io::stdin().read_line(&mut json).unwrap();
    serde_json::from_str(&json)
}

pub fn object_to_stdout(object: &impl Serialize) {
    println!("{}", serde_json::to_string(object).unwrap());
}

fn send_host_notification(method: &str, params: &Value) {
    object_to_stdout(&serde_json::json!({
        "jsonrpc": "2.0",
        "method": method,
        "params": params,
    }));
    unsafe { host_handle_rpc() };
}

fn send_host_request(id: u64, method: &str, params: &Value) {
    object_to_stdout(&serde_json::json!({
        "jsonrpc": "2.0",
        "id": id,
        "method": method,
        "params": params,
    }));
    unsafe { host_handle_rpc() };
}

#[link(wasm_import_module = "lapce")]
extern "C" {
    fn host_handle_rpc();
    fn host_handle_stderr();
}
