use gshell::{
    ast::{BoolOp, CommandNode, Redirection, RedirectionKind, ShellExpr, SimpleCommand},
    expand::{QuoteKind, Word, WordSegment},
    lexer::{Lexer, Token},
    parser::{ParseErrorKind, ParsedCommand, Parser},
};

fn lit(text: &str) -> Word {
    Word::literal(text)
}

#[test]
fn operator_tokenization_works() {
    let lexer = Lexer;
    let tokens = lexer
        .tokenize("a | b && c || d ; (e)")
        .expect("tokenization should succeed");

    assert_eq!(
        tokens,
        vec![
            Token::Word(lit("a")),
            Token::Pipe,
            Token::Word(lit("b")),
            Token::AndIf,
            Token::Word(lit("c")),
            Token::OrIf,
            Token::Word(lit("d")),
            Token::Semicolon,
            Token::LParen,
            Token::Word(lit("e")),
            Token::RParen,
        ]
    );
}

#[test]
fn trailing_background_tokenization_works() {
    let lexer = Lexer;
    let tokens = lexer
        .tokenize("sleep 1 &")
        .expect("tokenization should succeed");

    assert_eq!(
        tokens,
        vec![
            Token::Word(lit("sleep")),
            Token::Word(lit("1")),
            Token::Ampersand,
        ]
    );
}

#[test]
fn redirect_tokenization_works() {
    let lexer = Lexer;
    let tokens = lexer
        .tokenize("echo hi > out 2>> err < in <<EOF")
        .expect("tokenization should succeed");

    assert_eq!(
        tokens,
        vec![
            Token::Word(lit("echo")),
            Token::Word(lit("hi")),
            Token::RedirectOut,
            Token::Word(lit("out")),
            Token::IoNumber(2),
            Token::RedirectAppend,
            Token::Word(lit("err")),
            Token::RedirectIn,
            Token::Word(lit("in")),
            Token::RedirectHeredoc,
            Token::Word(lit("EOF")),
        ]
    );
}

#[test]
fn lexer_preserves_variable_segments() {
    let lexer = Lexer;
    let tokens = lexer
        .tokenize("echo $HOME $?")
        .expect("tokenization should succeed");

    assert_eq!(
        tokens,
        vec![
            Token::Word(Word::literal("echo")),
            Token::Word(Word::new(vec![WordSegment::Variable {
                name: "HOME".into(),
                quote: QuoteKind::Unquoted,
            }])),
            Token::Word(Word::new(vec![WordSegment::LastStatus {
                quote: QuoteKind::Unquoted,
            }])),
        ]
    );
}

#[test]
fn lexer_preserves_quote_context() {
    let lexer = Lexer;
    let tokens = lexer
        .tokenize(r#"echo '$HOME' "$HOME""#)
        .expect("tokenization should succeed");

    assert_eq!(
        tokens,
        vec![
            Token::Word(Word::literal("echo")),
            Token::Word(Word::new(vec![WordSegment::Literal {
                text: "$HOME".into(),
                quote: QuoteKind::SingleQuoted,
            }])),
            Token::Word(Word::new(vec![WordSegment::Variable {
                name: "HOME".into(),
                quote: QuoteKind::DoubleQuoted,
            }])),
        ]
    );
}

#[test]
fn function_definition_tokenization_works() {
    let lexer = Lexer;
    let tokens = lexer
        .tokenize("greet() { echo hi; }")
        .expect("tokenization should succeed");

    assert_eq!(
        tokens,
        vec![
            Token::Word(lit("greet")),
            Token::LParen,
            Token::RParen,
            Token::LBrace,
            Token::Word(lit("echo")),
            Token::Word(lit("hi")),
            Token::Semicolon,
            Token::RBrace,
        ]
    );
}

#[test]
fn parses_pipeline_ast() {
    let parser = Parser::default();
    let parsed = parser.parse("echo hi | cat").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Pipeline(vec![
            CommandNode::Simple(SimpleCommand::new(vec![lit("echo"), lit("hi")])),
            CommandNode::Simple(SimpleCommand::new(vec![lit("cat")])),
        ]))
    );
}

#[test]
fn parses_sequence_ast() {
    let parser = Parser::default();
    let parsed = parser.parse("echo hi ; pwd").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Sequence(vec![
            ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![
                lit("echo"),
                lit("hi")
            ]))),
            ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![lit("pwd")]))),
        ]))
    );
}

#[test]
fn parses_redirection_ast() {
    let parser = Parser::default();
    let parsed = parser
        .parse("echo hi > out.txt")
        .expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Simple(
            SimpleCommand::with_redirections(
                vec![lit("echo"), lit("hi")],
                vec![Redirection {
                    fd: None,
                    kind: RedirectionKind::OutputTruncate,
                    target: lit("out.txt"),
                }],
            )
        )))
    );
}

#[test]
fn parses_leading_assignments_as_command_prefix() {
    let parser = Parser::default();
    let parsed = parser
        .parse("NAME=value echo hi")
        .expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Simple(
            SimpleCommand::with_assignments(
                vec![("NAME".into(), lit("value"))],
                vec![lit("echo"), lit("hi")],
                vec![],
            )
        )))
    );
}

#[test]
fn parses_assignment_only_command() {
    let parser = Parser::default();
    let parsed = parser.parse("NAME=value").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Simple(
            SimpleCommand::with_assignments(vec![("NAME".into(), lit("value"))], vec![], vec![])
        )))
    );
}

#[test]
fn parses_grouped_command_ast() {
    let parser = Parser::default();
    let parsed = parser.parse("(echo hi)").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Subshell(Box::new(
            ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![
                lit("echo"),
                lit("hi")
            ])))
        ))))
    );
}

#[test]
fn parses_function_definition_ast() {
    let parser = Parser::default();
    let parsed = parser
        .parse("greet() { echo hi; }")
        .expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::FunctionDef {
            name: "greet".into(),
            body: Box::new(ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(
                vec![lit("echo"), lit("hi"),]
            )))),
        }))
    );
}

#[test]
fn parses_trailing_background_ast() {
    let parser = Parser::default();
    let parsed = parser.parse("sleep 1 &").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Background(ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(
            vec![lit("sleep"), lit("1")]
        ))))
    );
}

#[test]
fn malformed_function_definition_is_rejected() {
    let parser = Parser::default();
    let err = parser
        .parse("greet() { echo hi")
        .expect_err("parse should fail");

    assert_eq!(err.kind, ParseErrorKind::Incomplete);
    assert!(err.message.contains("unclosed function body"));
}

#[test]
fn pipeline_precedence_is_higher_than_boolean_chain() {
    let parser = Parser::default();
    let parsed = parser.parse("a | b && c").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::BooleanChain {
            first: Box::new(ShellExpr::Pipeline(vec![
                CommandNode::Simple(SimpleCommand::new(vec![lit("a")])),
                CommandNode::Simple(SimpleCommand::new(vec![lit("b")])),
            ])),
            rest: vec![(
                BoolOp::And,
                ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![lit("c")])))
            )],
        })
    );
}

#[test]
fn boolean_chain_parses_left_to_right() {
    let parser = Parser::default();
    let parsed = parser.parse("a && b || c").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::BooleanChain {
            first: Box::new(ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(
                vec![lit("a")]
            )))),
            rest: vec![
                (
                    BoolOp::And,
                    ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![lit("b")])))
                ),
                (
                    BoolOp::Or,
                    ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![lit("c")])))
                ),
            ],
        })
    );
}

#[test]
fn sequence_parsing_works() {
    let parser = Parser::default();
    let parsed = parser.parse("a ; b ; c").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Sequence(vec![
            ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![lit("a")]))),
            ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![lit("b")]))),
            ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![lit("c")]))),
        ]))
    );
}

#[test]
fn redirect_attaches_to_simple_command() {
    let parser = Parser::default();
    let parsed = parser
        .parse("echo hi 2>> err.log")
        .expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Simple(
            SimpleCommand::with_redirections(
                vec![lit("echo"), lit("hi")],
                vec![Redirection {
                    fd: Some(2),
                    kind: RedirectionKind::OutputAppend,
                    target: lit("err.log"),
                }]
            )
        )))
    );
}

#[test]
fn parses_heredoc_descriptor_and_body() {
    let parser = Parser::default();
    let parsed = parser
        .parse("cat <<EOF\nhello\nEOF\n")
        .expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Simple(
            SimpleCommand::with_redirections(
                vec![lit("cat")],
                vec![Redirection {
                    fd: None,
                    kind: RedirectionKind::HereDoc {
                        body: "hello\n".into(),
                        expand: true,
                    },
                    target: lit("EOF"),
                }],
            )
        )))
    );
}

#[test]
fn quoted_heredoc_delimiter_disables_expansion() {
    let parser = Parser::default();
    let parsed = parser
        .parse("cat <<'EOF'\nhello $HOME\nEOF\n")
        .expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Simple(
            SimpleCommand::with_redirections(
                vec![lit("cat")],
                vec![Redirection {
                    fd: None,
                    kind: RedirectionKind::HereDoc {
                        body: "hello $HOME\n".into(),
                        expand: false,
                    },
                    target: Word::new(vec![WordSegment::Literal {
                        text: "EOF".into(),
                        quote: QuoteKind::SingleQuoted,
                    }]),
                }],
            )
        )))
    );
}

#[test]
fn parser_reports_missing_heredoc_terminator_as_incomplete() {
    let parser = Parser::default();
    let err = parser
        .parse("cat <<EOF\nhello\n")
        .expect_err("parse should fail");

    assert_eq!(err.kind, ParseErrorKind::Incomplete);
    assert!(err.message.contains("heredoc missing terminator"));
}

#[test]
fn lexer_tokenizes_nested_command_substitution() {
    let lexer = Lexer;
    let tokens = lexer
        .tokenize("echo $(printf $(pwd))")
        .expect("tokenization should succeed");

    assert_eq!(
        tokens,
        vec![
            Token::Word(Word::literal("echo")),
            Token::Word(Word::new(vec![WordSegment::CommandSubstitution {
                expr: Box::new(ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(
                    vec![
                        Word::new(vec![WordSegment::Literal {
                            text: "printf".into(),
                            quote: QuoteKind::Unquoted,
                        }]),
                        Word::new(vec![WordSegment::CommandSubstitution {
                            expr: Box::new(ShellExpr::Command(CommandNode::Simple(
                                SimpleCommand::new(vec![Word::literal("pwd")])
                            ))),
                            quote: QuoteKind::Unquoted,
                        }])
                    ]
                )))),
                quote: QuoteKind::Unquoted,
            }])),
        ]
    );
}

#[test]
fn lexer_tokenizes_double_quoted_command_substitution() {
    let lexer = Lexer;
    let tokens = lexer
        .tokenize(r#"echo "$(pwd)""#)
        .expect("tokenization should succeed");

    assert_eq!(
        tokens,
        vec![
            Token::Word(Word::literal("echo")),
            Token::Word(Word::new(vec![WordSegment::CommandSubstitution {
                expr: Box::new(ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(
                    vec![Word::literal("pwd")]
                )))),
                quote: QuoteKind::DoubleQuoted,
            }])),
        ]
    );
}

#[test]
fn parser_reports_unclosed_command_substitution_as_incomplete() {
    let parser = Parser::default();
    let err = parser
        .parse("echo $(printf $(pwd)")
        .expect_err("parse should fail");

    assert_eq!(err.kind, ParseErrorKind::Incomplete);
    assert!(err.message.contains("unterminated command substitution"));
}

#[test]
fn parser_distinguishes_subshell_from_command_substitution() {
    let parser = Parser::default();
    let parsed = parser.parse("(echo hi)").expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Subshell(Box::new(
            ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(vec![
                lit("echo"),
                lit("hi"),
            ])))
        ))))
    );
}

#[test]
fn parser_keeps_command_substitution_inside_word_segments() {
    let parser = Parser::default();
    let parsed = parser
        .parse("echo prefix$(pwd)suffix")
        .expect("parse should succeed");

    assert_eq!(
        parsed,
        ParsedCommand::Expr(ShellExpr::Command(CommandNode::Simple(SimpleCommand::new(
            vec![
                Word::literal("echo"),
                Word::new(vec![
                    WordSegment::Literal {
                        text: "prefix".into(),
                        quote: QuoteKind::Unquoted,
                    },
                    WordSegment::CommandSubstitution {
                        expr: Box::new(ShellExpr::Command(CommandNode::Simple(
                            SimpleCommand::new(vec![Word::literal("pwd")])
                        ))),
                        quote: QuoteKind::Unquoted,
                    },
                    WordSegment::Literal {
                        text: "suffix".into(),
                        quote: QuoteKind::Unquoted,
                    },
                ]),
            ]
        ))))
    );
}
