use crate::{LanguageServer, span_to_range};
use lsp_types::{
    Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams, Uri,
    notification::{Notification, PublishDiagnostics},
};
use miette::{IntoDiagnostic, Result, miette};

impl LanguageServer {
    pub(crate) fn publish_diagnostics_for_file(&mut self, uri: Uri) -> Result<()> {
        let mut engine_state = self.new_engine_state(Some(&uri));
        engine_state.generate_nu_constant();

        let Some((_, span, working_set)) = self.parse_file(&mut engine_state, &uri, true) else {
            return Ok(());
        };

        let mut diagnostics = PublishDiagnosticsParams {
            uri: uri.clone(),
            diagnostics: Vec::new(),
            version: None,
        };

        let docs = match self.docs.lock() {
            Ok(it) => it,
            Err(err) => return Err(miette!(err.to_string())),
        };
        let file = docs
            .get_document(&uri)
            .ok_or_else(|| miette!("\nFailed to get document"))?;
        for err in working_set.parse_errors.iter() {
            let message = err.to_string();

            diagnostics.diagnostics.push(Diagnostic {
                range: span_to_range(&err.span(), file, span.start),
                severity: Some(DiagnosticSeverity::ERROR),
                message,
                ..Default::default()
            });
        }

        for warn in working_set.parse_warnings.iter() {
            let message = warn.to_string();

            diagnostics.diagnostics.push(Diagnostic {
                range: span_to_range(&warn.span(), file, span.start),
                severity: Some(DiagnosticSeverity::WARNING),
                message,
                ..Default::default()
            });
        }

        self.connection
            .sender
            .send(lsp_server::Message::Notification(
                lsp_server::Notification::new(PublishDiagnostics::METHOD.to_string(), diagnostics),
            ))
            .into_diagnostic()
    }
}

#[cfg(test)]
mod tests {
    use crate::path_to_uri;
    use crate::tests::{initialize_language_server, open_unchecked, update};
    use assert_json_diff::assert_json_eq;
    use nu_test_support::fs::fixtures;

    #[test]
    fn publish_diagnostics_variable_does_not_exists() {
        let (client_connection, _recv) = initialize_language_server(None, None);

        let mut script = fixtures();
        script.push("lsp");
        script.push("diagnostics");
        script.push("var.nu");
        let script = path_to_uri(&script);

        let notification = open_unchecked(&client_connection, script.clone());

        assert_json_eq!(
            notification,
            serde_json::json!({
                "method": "textDocument/publishDiagnostics",
                "params": {
                    "uri": script,
                    "diagnostics": [{
                        "range": {
                            "start": { "line": 0, "character": 6 },
                            "end": { "line": 0, "character": 30 }
                        },
                        "message": "Variable not found.",
                        "severity": 1
                    }]
                }
            })
        );
    }

    #[test]
    fn publish_diagnostics_fixed_unknown_variable() {
        let (client_connection, _recv) = initialize_language_server(None, None);

        let mut script = fixtures();
        script.push("lsp");
        script.push("diagnostics");
        script.push("var.nu");
        let script = path_to_uri(&script);

        open_unchecked(&client_connection, script.clone());
        let notification = update(
            &client_connection,
            script.clone(),
            String::from("$env"),
            Some(lsp_types::Range {
                start: lsp_types::Position {
                    line: 0,
                    character: 6,
                },
                end: lsp_types::Position {
                    line: 0,
                    character: 30,
                },
            }),
        );

        assert_json_eq!(
            notification,
            serde_json::json!({
                "method": "textDocument/publishDiagnostics",
                "params": {
                    "uri": script,
                    "diagnostics": []
                }
            })
        );
    }

    #[test]
    fn publish_diagnostics_none() {
        let (client_connection, _recv) = initialize_language_server(None, None);

        let mut script = fixtures();
        script.push("lsp");
        script.push("diagnostics");
        script.push("pwd.nu");
        let script = path_to_uri(&script);

        let notification = open_unchecked(&client_connection, script.clone());

        assert_json_eq!(
            notification,
            serde_json::json!({
                "method": "textDocument/publishDiagnostics",
                "params": {
                    "uri": script,
                    "diagnostics": []
                }
            })
        );
    }
}
