use rasm_core::{
    isa::{DataType, MnemonicKind, Registers},
    lexer::{
        Lexer,
        token::{Span, TokenKind},
    },
    src::SourceCode,
    str_pool::{StrId, StrPool},
};
use std::str::FromStr;

/// Вспомогательный макрос для быстрой генерации атомарных тест-кейсов.
/// Изолированно инициализирует контекст, запускает токенизацию
/// и сравнивает TokenKind, автоматически добавляя TokenKind::End в конец.
#[deny(clippy::approx_constant)]
macro_rules! assert_tokens {
    ($name:ident, $input:expr, [$($expected:expr),* $(,)?]) => {
        #[test]
        fn $name() {
            let source = SourceCode::new($input.to_string());
            let mut pool = StrPool::from_source(&source);
            let mut lexer = Lexer::new(&mut pool, source);
            lexer.tokenize();

            let expected_kinds = vec![$($expected,)* TokenKind::End];

            let actual_kinds: Vec<TokenKind> = lexer.tokens.iter().map(|t| t.kind).collect();
            assert_eq!(actual_kinds, expected_kinds, "Тест провален для строки: {:?}", $input);
        }
    };
}

// =========================================================================
// ГРУППА: Одиночные знаки препинания, спецсимволы и операторы адресации
// =========================================================================
assert_tokens!(comma_symbol, ",", [TokenKind::Comma]);
assert_tokens!(colon_symbol, ":", [TokenKind::Colon]);
assert_tokens!(dot_symbol, ".", [TokenKind::Dot]);
assert_tokens!(open_bracket_symbol, "[", [TokenKind::OpeningSquareBracket]);
assert_tokens!(close_bracket_symbol, "]", [TokenKind::EndingSquareBracket]);
assert_tokens!(plus_operator, "+", [TokenKind::Plus]);
assert_tokens!(minus_operator, "-", [TokenKind::Minus]);
assert_tokens!(asterisk_operator, "*", [TokenKind::Asterisk]);
assert_tokens!(newline_handling, "\n", [TokenKind::Newline]);
assert_tokens!(
    multiple_newlines,
    "\n\n\n",
    [TokenKind::Newline, TokenKind::Newline, TokenKind::Newline]
);
assert_tokens!(
    bracket_pair,
    "[]",
    [
        TokenKind::OpeningSquareBracket,
        TokenKind::EndingSquareBracket
    ]
);
assert_tokens!(
    math_operators_sequence,
    "+-*",
    [TokenKind::Plus, TokenKind::Minus, TokenKind::Asterisk]
);
assert_tokens!(
    separators_sequence,
    ",::,",
    [
        TokenKind::Comma,
        TokenKind::Colon,
        TokenKind::Colon,
        TokenKind::Comma
    ]
);
assert_tokens!(empty_input_produces_only_end, "", []);
assert_tokens!(only_spaces_are_ignored, "    \t  ", []);

// =========================================================================
// ГРУППА: Числовые литералы (Целые числа и числа с плавающей точкой)
// =========================================================================
assert_tokens!(integer_zero, "0", [TokenKind::Integer(0)]);
assert_tokens!(integer_positive, "12345", [TokenKind::Integer(12345)]);
assert_tokens!(hex_lowercase, "0x1a", [TokenKind::Integer(0x1a)]);
assert_tokens!(hex_uppercase, "0XFF", [TokenKind::Integer(0xff)]);
assert_tokens!(
    hex_complex_large,
    "0x123456789ABCDEF",
    [TokenKind::Integer(0x123456789abcdef)]
);
assert_tokens!(float_simple, "3.146", [TokenKind::Float(3.146)]);
assert_tokens!(float_zero, "0.0", [TokenKind::Float(0.0)]);
assert_tokens!(float_large, "1234.5678", [TokenKind::Float(1234.5678)]);
assert_tokens!(
    negative_integer_parsed_as_minus_and_int,
    "-5",
    [TokenKind::Minus, TokenKind::Integer(5)]
);
assert_tokens!(
    negative_float_parsed_as_minus_and_float,
    "-0.5",
    [TokenKind::Minus, TokenKind::Float(0.5)]
);
assert_tokens!(
    multiple_integers_separated_by_spaces,
    "10 20 30",
    [
        TokenKind::Integer(10),
        TokenKind::Integer(20),
        TokenKind::Integer(30)
    ]
);
assert_tokens!(
    integers_separated_by_commas,
    "1,2,3",
    [
        TokenKind::Integer(1),
        TokenKind::Comma,
        TokenKind::Integer(2),
        TokenKind::Comma,
        TokenKind::Integer(3)
    ]
);
assert_tokens!(
    float_and_integer_sequence,
    "4.2 42",
    [TokenKind::Float(4.2), TokenKind::Integer(42)]
);
assert_tokens!(
    hex_and_float_sequence,
    "0x10 1.0",
    [TokenKind::Integer(16), TokenKind::Float(1.0)]
);
assert_tokens!(trailing_dot_float, "42.", [TokenKind::Float(42.0)]);

// =========================================================================
// ГРУППА: Архитектурные регистры (GPRs, Rip, Rflags и др.)
// =========================================================================
assert_tokens!(
    register_rax,
    "rax",
    [TokenKind::Register(Registers::from_str("rax").unwrap())]
);
assert_tokens!(
    register_rbx_case_insensitive,
    "RBX",
    [TokenKind::Register(Registers::from_str("rbx").unwrap())]
);
assert_tokens!(
    register_rcx_mixed_case,
    "Rcx",
    [TokenKind::Register(Registers::from_str("rcx").unwrap())]
);
assert_tokens!(
    register_rdx,
    "rdx",
    [TokenKind::Register(Registers::from_str("rdx").unwrap())]
);
assert_tokens!(
    register_rsp,
    "rsp",
    [TokenKind::Register(Registers::from_str("rsp").unwrap())]
);
assert_tokens!(
    register_rbp,
    "rbp",
    [TokenKind::Register(Registers::from_str("rbp").unwrap())]
);
assert_tokens!(
    register_rsi,
    "rsi",
    [TokenKind::Register(Registers::from_str("rsi").unwrap())]
);
assert_tokens!(
    register_rdi,
    "rdi",
    [TokenKind::Register(Registers::from_str("rdi").unwrap())]
);
assert_tokens!(
    register_r8,
    "r8",
    [TokenKind::Register(Registers::from_str("r8").unwrap())]
);
assert_tokens!(
    register_r15,
    "r15",
    [TokenKind::Register(Registers::from_str("r15").unwrap())]
);
assert_tokens!(
    register_rip,
    "rip",
    [TokenKind::Register(Registers::from_str("rip").unwrap())]
);
assert_tokens!(
    register_rflags,
    "rflags",
    [TokenKind::Register(Registers::from_str("rflags").unwrap())]
);
assert_tokens!(
    multiple_registers_in_row,
    "rax rbx rsi",
    [
        TokenKind::Register(Registers::from_str("rax").unwrap()),
        TokenKind::Register(Registers::from_str("rbx").unwrap()),
        TokenKind::Register(Registers::from_str("rsi").unwrap())
    ]
);
assert_tokens!(
    registers_with_surrounding_spaces,
    "   rax   ",
    [TokenKind::Register(Registers::from_str("rax").unwrap())]
);

// =========================================================================
// ГРУППА: Мнемоники инструкций процессора (MnemonicKind)
// =========================================================================
assert_tokens!(
    mnemonic_mov,
    "mov",
    [TokenKind::Mnemonic(MnemonicKind::from_str("mov").unwrap())]
);
assert_tokens!(
    mnemonic_add_uppercase,
    "ADD",
    [TokenKind::Mnemonic(MnemonicKind::from_str("add").unwrap())]
);
assert_tokens!(
    mnemonic_sub_mixed_case,
    "Sub",
    [TokenKind::Mnemonic(MnemonicKind::from_str("sub").unwrap())]
);
assert_tokens!(
    mnemonic_mul,
    "mul",
    [TokenKind::Mnemonic(MnemonicKind::from_str("mul").unwrap())]
);
assert_tokens!(
    mnemonic_div,
    "div",
    [TokenKind::Mnemonic(MnemonicKind::from_str("div").unwrap())]
);
assert_tokens!(
    mnemonic_jmp,
    "jmp",
    [TokenKind::Mnemonic(MnemonicKind::from_str("jmp").unwrap())]
);
assert_tokens!(
    mnemonic_cmp,
    "cmp",
    [TokenKind::Mnemonic(MnemonicKind::from_str("cmp").unwrap())]
);
assert_tokens!(
    mnemonic_je,
    "je",
    [TokenKind::Mnemonic(MnemonicKind::from_str("je").unwrap())]
);
assert_tokens!(
    mnemonic_jne,
    "jne",
    [TokenKind::Mnemonic(MnemonicKind::from_str("jne").unwrap())]
);
assert_tokens!(
    mnemonic_push,
    "push",
    [TokenKind::Mnemonic(MnemonicKind::from_str("push").unwrap())]
);
assert_tokens!(
    mnemonic_pop,
    "pop",
    [TokenKind::Mnemonic(MnemonicKind::from_str("pop").unwrap())]
);
assert_tokens!(
    mnemonic_call,
    "call",
    [TokenKind::Mnemonic(MnemonicKind::from_str("call").unwrap())]
);
assert_tokens!(
    mnemonic_ret,
    "ret",
    [TokenKind::Mnemonic(MnemonicKind::from_str("ret").unwrap())]
);
assert_tokens!(
    mnemonic_syscall,
    "syscall",
    [TokenKind::Mnemonic(
        MnemonicKind::from_str("syscall").unwrap()
    )]
);
assert_tokens!(
    multiple_mnemonics_sequence,
    "mov add sub jmp",
    [
        TokenKind::Mnemonic(MnemonicKind::from_str("mov").unwrap()),
        TokenKind::Mnemonic(MnemonicKind::from_str("add").unwrap()),
        TokenKind::Mnemonic(MnemonicKind::from_str("sub").unwrap()),
        TokenKind::Mnemonic(MnemonicKind::from_str("jmp").unwrap())
    ]
);

// =========================================================================
// ГРУППА: Размеры типов данных (DataType)
// =========================================================================
assert_tokens!(
    datatype_byte,
    "byte",
    [TokenKind::Type(DataType::from_str("byte").unwrap())]
);
assert_tokens!(
    datatype_word_uppercase,
    "WORD",
    [TokenKind::Type(DataType::from_str("word").unwrap())]
);
assert_tokens!(
    datatype_dword_mixed_case,
    "Dword",
    [TokenKind::Type(DataType::from_str("dword").unwrap())]
);
assert_tokens!(
    datatype_qword,
    "qword",
    [TokenKind::Type(DataType::from_str("qword").unwrap())]
);
assert_tokens!(
    all_datatypes_sequence,
    "byte word dword qword",
    [
        TokenKind::Type(DataType::from_str("byte").unwrap()),
        TokenKind::Type(DataType::from_str("word").unwrap()),
        TokenKind::Type(DataType::from_str("dword").unwrap()),
        TokenKind::Type(DataType::from_str("qword").unwrap())
    ]
);
assert_tokens!(
    datatype_qword_with_ptr_keyword,
    "qword ptr",
    [
        TokenKind::Type(DataType::from_str("qword").unwrap()),
        TokenKind::Ident(StrId(0))
    ]
);
assert_tokens!(
    datatype_with_brackets,
    "byte ptr [",
    [
        TokenKind::Type(DataType::from_str("byte").unwrap()),
        TokenKind::Ident(StrId(0)),
        TokenKind::OpeningSquareBracket
    ]
);
assert_tokens!(
    datatype_dword_spaced,
    "  dword  ",
    [TokenKind::Type(DataType::from_str("dword").unwrap())]
);
assert_tokens!(
    datatype_followed_by_integer,
    "qword 42",
    [
        TokenKind::Type(DataType::from_str("qword").unwrap()),
        TokenKind::Integer(42)
    ]
);

// =========================================================================
// ГРУППА: Пользовательские идентификаторы, локальные и глобальные метки
// =========================================================================
assert_tokens!(ident_standard_label, "main", [TokenKind::Ident(StrId(0))]);
assert_tokens!(
    ident_with_underscores_and_numbers,
    "my_func_123",
    [TokenKind::Ident(StrId(0))]
);
assert_tokens!(
    ident_local_label_starts_with_dot,
    ".loop",
    [TokenKind::Dot, TokenKind::Ident(StrId(0))]
);
assert_tokens!(
    ident_label_declaration_with_colon,
    "start:",
    [TokenKind::Ident(StrId(0)), TokenKind::Colon]
);
assert_tokens!(
    ident_local_label_declaration_with_colon,
    ".L1:",
    [TokenKind::Dot, TokenKind::Ident(StrId(0)), TokenKind::Colon]
);
assert_tokens!(
    ident_camel_case,
    "MyCustomIdent",
    [TokenKind::Ident(StrId(0))]
);
assert_tokens!(
    ident_directive_data_syntax,
    ".data",
    [TokenKind::Dot, TokenKind::Ident(StrId(0))]
);
assert_tokens!(
    ident_directive_text_syntax,
    ".text",
    [TokenKind::Dot, TokenKind::Ident(StrId(0))]
);
assert_tokens!(
    multiple_unique_idents,
    "foo bar baz",
    [
        TokenKind::Ident(StrId(0)),
        TokenKind::Ident(StrId(1)),
        TokenKind::Ident(StrId(2))
    ]
);
assert_tokens!(
    ident_containing_numbers_inside,
    "val32_bit",
    [TokenKind::Ident(StrId(0))]
);

// =========================================================================
// ГРУППА: Однострочные комментарии (Символ `;`)
// =========================================================================
assert_tokens!(comment_line_pure, "; это просто комментарий", []);
assert_tokens!(
    comment_after_valid_instruction,
    "mov rax, rbx ; копируем",
    [
        TokenKind::Mnemonic(MnemonicKind::from_str("mov").unwrap()),
        TokenKind::Register(Registers::from_str("rax").unwrap()),
        TokenKind::Comma,
        TokenKind::Register(Registers::from_str("rbx").unwrap())
    ]
);
assert_tokens!(
    comment_on_separate_newline_between_code,
    "mov rax, 1\n; комментарий\nadd rax, 2",
    [
        TokenKind::Mnemonic(MnemonicKind::from_str("mov").unwrap()),
        TokenKind::Register(Registers::from_str("rax").unwrap()),
        TokenKind::Comma,
        TokenKind::Integer(1),
        TokenKind::Newline,
        TokenKind::Newline,
        TokenKind::Mnemonic(MnemonicKind::from_str("add").unwrap()),
        TokenKind::Register(Registers::from_str("rax").unwrap()),
        TokenKind::Comma,
        TokenKind::Integer(2)
    ]
);
assert_tokens!(
    comment_containing_special_characters,
    "; !!! [, + - *] ???",
    []
);
assert_tokens!(
    multiple_consecutive_comments,
    "; комм1\n; комм2\n; комм3",
    [TokenKind::Newline, TokenKind::Newline]
);
assert_tokens!(comment_empty, ";", []);
assert_tokens!(comment_preceded_by_spaces, "   ; со смещением", []);
assert_tokens!(
    comment_at_the_very_end_of_file,
    "syscall ; выход",
    [TokenKind::Mnemonic(
        MnemonicKind::from_str("syscall").unwrap()
    )]
);
assert_tokens!(comment_disabling_valid_code, "; mov rax, rcx", []);
assert_tokens!(
    comment_with_windows_carriage_return,
    "; комм\r\nmov rax, 1",
    [
        TokenKind::Newline,
        TokenKind::Mnemonic(MnemonicKind::from_str("mov").unwrap()),
        TokenKind::Register(Registers::from_str("rax").unwrap()),
        TokenKind::Comma,
        TokenKind::Integer(1)
    ]
);

// =========================================================================
// ГРУППА: Сложные выражения, адресация памяти и стресс-тесты
// =========================================================================
assert_tokens!(
    complex_memory_addressing_with_scale_and_displacement,
    "mov rax, [rbx + rcx * 4 + 0x20]",
    [
        TokenKind::Mnemonic(MnemonicKind::from_str("mov").unwrap()),
        TokenKind::Register(Registers::from_str("rax").unwrap()),
        TokenKind::Comma,
        TokenKind::OpeningSquareBracket,
        TokenKind::Register(Registers::from_str("rbx").unwrap()),
        TokenKind::Plus,
        TokenKind::Register(Registers::from_str("rcx").unwrap()),
        TokenKind::Asterisk,
        TokenKind::Integer(4),
        TokenKind::Plus,
        TokenKind::Integer(0x20),
        TokenKind::EndingSquareBracket
    ]
);

assert_tokens!(
    complex_instruction_with_datatype_ptr,
    "add qword ptr [rdi - 8], 1",
    [
        TokenKind::Mnemonic(MnemonicKind::from_str("add").unwrap()),
        TokenKind::Type(DataType::from_str("qword").unwrap()),
        TokenKind::Ident(StrId(0)),
        TokenKind::OpeningSquareBracket,
        TokenKind::Register(Registers::from_str("rdi").unwrap()),
        TokenKind::Minus,
        TokenKind::Integer(8),
        TokenKind::EndingSquareBracket,
        TokenKind::Comma,
        TokenKind::Integer(1)
    ]
);

assert_tokens!(
    control_flow_jump_to_local_label,
    "jmp .loop_start",
    [
        TokenKind::Mnemonic(MnemonicKind::from_str("jmp").unwrap()),
        TokenKind::Dot,
        TokenKind::Ident(StrId(0))
    ]
);

assert_tokens!(
    multiline_program_block,
    "main:\n    mov rax, 60\n    syscall",
    [
        TokenKind::Ident(StrId(0)),
        TokenKind::Colon,
        TokenKind::Newline,
        TokenKind::Mnemonic(MnemonicKind::from_str("mov").unwrap()),
        TokenKind::Register(Registers::from_str("rax").unwrap()),
        TokenKind::Comma,
        TokenKind::Integer(60),
        TokenKind::Newline,
        TokenKind::Mnemonic(MnemonicKind::from_str("syscall").unwrap())
    ]
);

assert_tokens!(
    memory_addressing_with_index_and_scale_only,
    "[rsi * 8]",
    [
        TokenKind::OpeningSquareBracket,
        TokenKind::Register(Registers::from_str("rsi").unwrap()),
        TokenKind::Asterisk,
        TokenKind::Integer(8),
        TokenKind::EndingSquareBracket
    ]
);

#[test]
fn token_span_coordinates_are_exactly_accurate() {
    let source = SourceCode::new("mov rax".to_string());
    let mut pool = StrPool::from_source(&source);
    let mut lexer = Lexer::new(&mut pool, source);
    lexer.tokenize();

    assert_eq!(lexer.tokens[0].pos, Span::new(0, 3));
    assert_eq!(lexer.tokens[1].pos, Span::new(4, 7));
}

#[test]
fn string_pool_correctly_deduplicates_matching_identifiers() {
    let source = SourceCode::new("label1 label2 label1".to_string());
    let mut pool = StrPool::from_source(&source);
    let mut lexer = Lexer::new(&mut pool, source);
    lexer.tokenize();

    let id1 = match lexer.tokens[0].kind {
        TokenKind::Ident(id) => id,
        _ => panic!(),
    };
    let id2 = match lexer.tokens[1].kind {
        TokenKind::Ident(id) => id,
        _ => panic!(),
    };
    let id3 = match lexer.tokens[2].kind {
        TokenKind::Ident(id) => id,
        _ => panic!(),
    };

    assert_ne!(id1, id2, "Разные имена должны получать уникальные StrId");
    assert_eq!(
        id1, id3,
        "Идентичные строки должны разделять один и тот же StrId"
    );
}

#[test]
fn unexpected_characters_are_logged_into_errors_and_skipped() {
    let source = SourceCode::new("mov @, #".to_string());
    let mut pool = StrPool::from_source(&source);
    let mut lexer = Lexer::new(&mut pool, source);
    lexer.tokenize();

    assert_eq!(
        lexer.tokens[0].kind,
        TokenKind::Mnemonic(MnemonicKind::from_str("mov").unwrap())
    );
    assert_eq!(lexer.tokens[1].kind, TokenKind::Comma);
    assert_eq!(lexer.tokens[2].kind, TokenKind::End);
    assert_eq!(
        lexer.errors.len(),
        2,
        "Оба спецсимвола '@' и '#' должны уйти в коллекцию ошибок"
    );
}

#[test]
fn multiple_dots_in_numeric_literal_fails_gracefully_as_invalid_float() {
    let source = SourceCode::new("1.2.3.4".to_string());
    let mut pool = StrPool::from_source(&source);
    let mut lexer = Lexer::new(&mut pool, source);
    lexer.tokenize();

    assert!(matches!(lexer.tokens[0].kind, TokenKind::Float(_)));
    assert_eq!(lexer.errors.len(), 1);
}

#[test]
fn invalid_hexadecimal_characters_are_caught_by_parser_boundary() {
    let source = SourceCode::new("0xG123".to_string());
    let mut pool = StrPool::from_source(&source);
    let mut lexer = Lexer::new(&mut pool, source);
    lexer.tokenize();

    assert_eq!(lexer.tokens[0].kind, TokenKind::Integer(0));
    assert_eq!(lexer.errors.len(), 1);
}
