use crate::commands::command::whole_stream_command;
use crate::commands::run_alias::AliasCommand;
use crate::commands::UnevaluatedCallInfo;
use crate::prelude::*;
use log::{log_enabled, trace};
use nu_errors::ShellError;
use nu_protocol::hir::InternalCommand;
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};

pub(crate) async fn run_internal_command(
    command: InternalCommand,
    context: &mut Context,
    input: InputStream,
    it: &Value,
    vars: &IndexMap<String, Value>,
    env: &IndexMap<String, String>,
) -> Result<InputStream, ShellError> {
    if log_enabled!(log::Level::Trace) {
        trace!(target: "nu::run::internal", "->");
        trace!(target: "nu::run::internal", "{}", command.name);
    }

    let scope = Scope {
        it: it.clone(),
        vars: vars.clone(),
        env: env.clone(),
    };
    let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
    let internal_command = context.expect_command(&command.name);

    let result = {
        context
            .run_command(
                internal_command?,
                Tag::unknown_anchor(command.name_span),
                command.args.clone(),
                &scope,
                objects,
            )
            .await
    };

    let head = Arc::new(command.args.head.clone());
    //let context = Arc::new(context.clone());
    let context = context.clone();
    let command = Arc::new(command);
    let scope = Arc::new(scope);
    // let scope = scope.clone();

    Ok(InputStream::from_stream(
        result
            .then(move |item| {
                let head = head.clone();
                let command = command.clone();
                let mut context = context.clone();
                let scope = scope.clone();
                async move {
                    match item {
                        Ok(ReturnSuccess::Action(action)) => match action {
                            CommandAction::ChangePath(path) => {
                                context.shell_manager.set_path(path);
                                InputStream::from_stream(futures::stream::iter(vec![]))
                            }
                            CommandAction::Exit => std::process::exit(0), // TODO: save history.txt
                            CommandAction::Error(err) => {
                                context.error(err.clone());
                                InputStream::one(UntaggedValue::Error(err).into_untagged_value())
                            }
                            CommandAction::AutoConvert(tagged_contents, extension) => {
                                let contents_tag = tagged_contents.tag.clone();
                                let command_name = format!("from {}", extension);
                                let command = command.clone();
                                if let Some(converter) = context.registry.get_command(&command_name)
                                {
                                    let new_args = RawCommandArgs {
                                        host: context.host.clone(),
                                        ctrl_c: context.ctrl_c.clone(),
                                        current_errors: context.current_errors.clone(),
                                        shell_manager: context.shell_manager.clone(),
                                        call_info: UnevaluatedCallInfo {
                                            args: nu_protocol::hir::Call {
                                                head: (&*head).clone(),
                                                positional: None,
                                                named: None,
                                                span: Span::unknown(),
                                                is_last: false,
                                            },
                                            name_tag: Tag::unknown_anchor(command.name_span),
                                            scope: (&*scope).clone(),
                                        },
                                    };
                                    let mut result = converter
                                        .run(
                                            new_args.with_input(vec![tagged_contents]),
                                            &context.registry,
                                        )
                                        .await;
                                    let result_vec: Vec<Result<ReturnSuccess, ShellError>> =
                                        result.drain_vec().await;

                                    let mut output = vec![];
                                    for res in result_vec {
                                        match res {
                                            Ok(ReturnSuccess::Value(Value {
                                                value: UntaggedValue::Table(list),
                                                ..
                                            })) => {
                                                for l in list {
                                                    output.push(Ok(l));
                                                }
                                            }
                                            Ok(ReturnSuccess::Value(Value { value, .. })) => {
                                                output.push(Ok(
                                                    value.into_value(contents_tag.clone())
                                                ));
                                            }
                                            Err(e) => output.push(Err(e)),
                                            _ => {}
                                        }
                                    }

                                    futures::stream::iter(output).to_input_stream()
                                } else {
                                    InputStream::one(tagged_contents)
                                }
                            }
                            CommandAction::EnterHelpShell(value) => match value {
                                Value {
                                    value: UntaggedValue::Primitive(Primitive::String(cmd)),
                                    tag,
                                } => {
                                    context.shell_manager.insert_at_current(Box::new(
                                        match HelpShell::for_command(
                                            UntaggedValue::string(cmd).into_value(tag),
                                            &context.registry(),
                                        ) {
                                            Ok(v) => v,
                                            Err(err) => {
                                                return InputStream::one(
                                                    UntaggedValue::Error(err).into_untagged_value(),
                                                )
                                            }
                                        },
                                    ));
                                    InputStream::from_stream(futures::stream::iter(vec![]))
                                }
                                _ => {
                                    context.shell_manager.insert_at_current(Box::new(
                                        match HelpShell::index(&context.registry()) {
                                            Ok(v) => v,
                                            Err(err) => {
                                                return InputStream::one(
                                                    UntaggedValue::Error(err).into_untagged_value(),
                                                )
                                            }
                                        },
                                    ));
                                    InputStream::from_stream(futures::stream::iter(vec![]))
                                }
                            },
                            CommandAction::EnterValueShell(value) => {
                                context
                                    .shell_manager
                                    .insert_at_current(Box::new(ValueShell::new(value)));
                                InputStream::from_stream(futures::stream::iter(vec![]))
                            }
                            CommandAction::EnterShell(location) => {
                                context.shell_manager.insert_at_current(Box::new(
                                    match FilesystemShell::with_location(
                                        location,
                                        context.registry().clone(),
                                    ) {
                                        Ok(v) => v,
                                        Err(err) => {
                                            return InputStream::one(
                                                UntaggedValue::Error(err.into())
                                                    .into_untagged_value(),
                                            )
                                        }
                                    },
                                ));
                                InputStream::from_stream(futures::stream::iter(vec![]))
                            }
                            CommandAction::AddAlias(name, args, block) => {
                                context.add_commands(vec![whole_stream_command(
                                    AliasCommand::new(name, args, block),
                                )]);
                                InputStream::from_stream(futures::stream::iter(vec![]))
                            }
                            CommandAction::PreviousShell => {
                                context.shell_manager.prev();
                                InputStream::from_stream(futures::stream::iter(vec![]))
                            }
                            CommandAction::NextShell => {
                                context.shell_manager.next();
                                InputStream::from_stream(futures::stream::iter(vec![]))
                            }
                            CommandAction::LeaveShell => {
                                context.shell_manager.remove_at_current();
                                if context.shell_manager.is_empty() {
                                    std::process::exit(0); // TODO: save history.txt
                                }
                                InputStream::from_stream(futures::stream::iter(vec![]))
                            }
                        },

                        Ok(ReturnSuccess::Value(Value {
                            value: UntaggedValue::Error(err),
                            tag,
                        })) => {
                            context.error(err.clone());
                            InputStream::one(UntaggedValue::Error(err).into_value(tag))
                        }

                        Ok(ReturnSuccess::Value(v)) => InputStream::one(v),

                        Ok(ReturnSuccess::DebugValue(v)) => {
                            let doc = PrettyDebug::pretty_doc(&v);
                            let mut buffer = termcolor::Buffer::ansi();

                            let _ = doc.render_raw(
                                context.with_host(|host| host.width() - 5),
                                &mut nu_source::TermColored::new(&mut buffer),
                            );

                            let value = String::from_utf8_lossy(buffer.as_slice());

                            InputStream::one(UntaggedValue::string(value).into_untagged_value())
                        }

                        Err(err) => {
                            context.error(err.clone());
                            InputStream::one(UntaggedValue::Error(err).into_untagged_value())
                        }
                    }
                }
            })
            .flatten()
            .take_while(|x| futures::future::ready(!x.is_error())),
    ))
}
