use nu_parser::ParseError;
use nu_parser::*;
use nu_protocol::{
    ast::{Expr, Expression, Pipeline, Statement},
    engine::{Command, EngineState, Stack, StateWorkingSet},
    Signature, SyntaxShape,
};

#[cfg(test)]
#[derive(Clone)]
pub struct Let;

#[cfg(test)]
impl Command for Let {
    fn name(&self) -> &str {
        "let"
    }

    fn usage(&self) -> &str {
        "Create a variable and give it a value."
    }

    fn signature(&self) -> nu_protocol::Signature {
        Signature::build("let")
            .required("var_name", SyntaxShape::VarWithOptType, "variable name")
            .required(
                "initial_value",
                SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
                "equals sign followed by value",
            )
    }

    fn run(
        &self,
        _engine_state: &EngineState,
        _stack: &mut Stack,
        _call: &nu_protocol::ast::Call,
        _input: nu_protocol::PipelineData,
    ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
        todo!()
    }
}

#[test]
pub fn parse_int() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let (block, err) = parse(&mut working_set, None, b"3", true);

    assert!(err.is_none());
    assert!(block.len() == 1);
    match &block[0] {
        Statement::Pipeline(Pipeline { expressions }) => {
            assert!(expressions.len() == 1);
            assert!(matches!(
                expressions[0],
                Expression {
                    expr: Expr::Int(3),
                    ..
                }
            ))
        }
        _ => panic!("No match"),
    }
}

#[test]
pub fn parse_call() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'));
    working_set.add_decl(sig.predeclare());

    let (block, err) = parse(&mut working_set, None, b"foo", true);

    assert!(err.is_none());
    assert!(block.len() == 1);

    match &block[0] {
        Statement::Pipeline(Pipeline { expressions }) => {
            assert_eq!(expressions.len(), 1);

            if let Expression {
                expr: Expr::Call(call),
                ..
            } = &expressions[0]
            {
                assert_eq!(call.decl_id, 0);
            }
        }
        _ => panic!("not a call"),
    }
}

#[test]
pub fn parse_call_missing_flag_arg() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let sig = Signature::build("foo").named("jazz", SyntaxShape::Int, "jazz!!", Some('j'));
    working_set.add_decl(sig.predeclare());

    let (_, err) = parse(&mut working_set, None, b"foo --jazz", true);
    assert!(matches!(err, Some(ParseError::MissingFlagParam(..))));
}

#[test]
pub fn parse_call_missing_short_flag_arg() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'));
    working_set.add_decl(sig.predeclare());

    let (_, err) = parse(&mut working_set, None, b"foo -j", true);
    assert!(matches!(err, Some(ParseError::MissingFlagParam(..))));
}

#[test]
pub fn parse_call_too_many_shortflag_args() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let sig = Signature::build("foo")
        .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'))
        .named("--math", SyntaxShape::Int, "math!!", Some('m'));
    working_set.add_decl(sig.predeclare());
    let (_, err) = parse(&mut working_set, None, b"foo -mj", true);
    assert!(matches!(
        err,
        Some(ParseError::ShortFlagBatchCantTakeArg(..))
    ));
}

#[test]
pub fn parse_call_unknown_shorthand() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j'));
    working_set.add_decl(sig.predeclare());
    let (_, err) = parse(&mut working_set, None, b"foo -mj", true);
    assert!(matches!(err, Some(ParseError::UnknownFlag(..))));
}

#[test]
pub fn parse_call_extra_positional() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j'));
    working_set.add_decl(sig.predeclare());
    let (_, err) = parse(&mut working_set, None, b"foo -j 100", true);
    assert!(matches!(err, Some(ParseError::ExtraPositional(..))));
}

#[test]
pub fn parse_call_missing_req_positional() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!");
    working_set.add_decl(sig.predeclare());
    let (_, err) = parse(&mut working_set, None, b"foo", true);
    assert!(matches!(err, Some(ParseError::MissingPositional(..))));
}

#[test]
pub fn parse_call_missing_req_flag() {
    let engine_state = EngineState::new();
    let mut working_set = StateWorkingSet::new(&engine_state);

    let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None);
    working_set.add_decl(sig.predeclare());
    let (_, err) = parse(&mut working_set, None, b"foo", true);
    assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..))));
}

mod range {
    use super::*;
    use nu_protocol::ast::{RangeInclusion, RangeOperator};

    #[test]
    fn parse_inclusive_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (block, err) = parse(&mut working_set, None, b"0..10", true);

        assert!(err.is_none());
        assert!(block.len() == 1);
        match &block[0] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            None,
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::Inclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_exclusive_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (block, err) = parse(&mut working_set, None, b"0..<10", true);

        assert!(err.is_none());
        assert!(block.len() == 1);
        match &block[0] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            None,
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::RightExclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_reverse_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (block, err) = parse(&mut working_set, None, b"10..0", true);

        assert!(err.is_none());
        assert!(block.len() == 1);
        match &block[0] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            None,
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::Inclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_subexpression_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (block, err) = parse(&mut working_set, None, b"(3 - 3)..<(8 + 2)", true);

        assert!(err.is_none());
        assert!(block.len() == 1);
        match &block[0] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            None,
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::RightExclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_variable_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        working_set.add_decl(Box::new(Let));

        let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..10", true);

        assert!(err.is_none());
        assert!(block.len() == 2);
        match &block[1] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            None,
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::Inclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_subexpression_variable_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        working_set.add_decl(Box::new(Let));

        let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..<($a + 10)", true);

        assert!(err.is_none());
        assert!(block.len() == 2);
        match &block[1] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            None,
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::RightExclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_right_unbounded_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (block, err) = parse(&mut working_set, None, b"0..", true);

        assert!(err.is_none());
        assert!(block.len() == 1);
        match &block[0] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            None,
                            None,
                            RangeOperator {
                                inclusion: RangeInclusion::Inclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_left_unbounded_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (block, err) = parse(&mut working_set, None, b"..10", true);

        assert!(err.is_none());
        assert!(block.len() == 1);
        match &block[0] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            None,
                            None,
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::Inclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_negative_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (block, err) = parse(&mut working_set, None, b"-10..-3", true);

        assert!(err.is_none());
        assert!(block.len() == 1);
        match &block[0] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            None,
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::Inclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn parse_float_range() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (block, err) = parse(&mut working_set, None, b"2.0..4.0..10.0", true);

        assert!(err.is_none());
        assert!(block.len() == 1);
        match &block[0] {
            Statement::Pipeline(Pipeline { expressions }) => {
                assert!(expressions.len() == 1);
                assert!(matches!(
                    expressions[0],
                    Expression {
                        expr: Expr::Range(
                            Some(_),
                            Some(_),
                            Some(_),
                            RangeOperator {
                                inclusion: RangeInclusion::Inclusive,
                                ..
                            }
                        ),
                        ..
                    }
                ))
            }
            _ => panic!("No match"),
        }
    }

    #[test]
    fn bad_parse_does_crash() {
        let engine_state = EngineState::new();
        let mut working_set = StateWorkingSet::new(&engine_state);

        let (_, err) = parse(&mut working_set, None, b"(0)..\"a\"", true);

        assert!(err.is_some());
    }
}