use crate::parsers::wasot::is_meta_line;
use std::fs::File;
use std::io::{BufRead, BufReader, Read};
use std::path::Path;

/// Fast scanner that finds the maximum base36 ID in a Wasot file
pub struct IdScanner;

impl IdScanner {
    /// Scan a file and find the maximum base36 numeric ID
    pub fn scan_file<P: AsRef<Path>>(file_path: P) -> Result<Option<u64>, std::io::Error> {
        let file = File::open(file_path)?;
        Self::scan_reader(file)
    }

    /// Scan from a reader and find the maximum base36 numeric ID
    pub fn scan_reader<R: Read>(reader: R) -> Result<Option<u64>, std::io::Error> {
        let buf_reader = BufReader::new(reader);
        let mut max_id: Option<u64> = None;

        for line in buf_reader.lines() {
            let line = line?;

            // Quick check if it's a meta-line
            if is_meta_line(&line) {
                // Extract ID using a fast regex-free approach
                if let Some(id_value) = Self::extract_id_fast(&line) {
                    // Try to parse as base36
                    if let Ok(numeric_id) = crate::base36::base36_to_u64(&id_value) {
                        max_id = Some(max_id.map_or(numeric_id, |current| current.max(numeric_id)));
                    }
                }
            }
        }

        Ok(max_id)
    }

    /// Fast ID extraction without regex
    /// Looking for pattern: whitespace + colon + lowercase letters/numbers/dots/dashes
    fn extract_id_fast(line: &str) -> Option<String> {
        // Find the last occurrence of " :" in the line
        let mut colon_pos = None;
        let bytes = line.as_bytes();

        for i in (1..bytes.len()).rev() {
            if bytes[i] == b':' && bytes[i - 1] == b' ' {
                colon_pos = Some(i);
                break;
            }
        }

        let colon_pos = colon_pos?;

        // Extract everything after the colon
        let id_start = colon_pos + 1;
        if id_start >= bytes.len() {
            return None;
        }

        let id_bytes = &bytes[id_start..];

        // Find the end of the ID (first non-valid character)
        let mut id_end = 0;
        for (i, &byte) in id_bytes.iter().enumerate() {
            match byte {
                b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' => {
                    id_end = i + 1;
                }
                b' ' | b'\t' => {
                    // Allow trailing whitespace
                    continue;
                }
                _ => break,
            }
        }

        if id_end == 0 {
            return None;
        }

        // Extract the ID string
        let id_str = std::str::from_utf8(&id_bytes[..id_end]).ok()?;
        Some(id_str.trim().to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Cursor;

    #[test]
    fn test_extract_id_fast() {
        assert_eq!(
            IdScanner::extract_id_fast(".div :main"),
            Some("main".to_string())
        );
        assert_eq!(
            IdScanner::extract_id_fast(".p some text :test"),
            Some("test".to_string())
        );
        assert_eq!(
            IdScanner::extract_id_fast(".block :123"),
            Some("123".to_string())
        );
        assert_eq!(
            IdScanner::extract_id_fast(".block :abc-123"),
            Some("abc-123".to_string())
        );
        assert_eq!(
            IdScanner::extract_id_fast(".block :test.sub"),
            Some("test.sub".to_string())
        );
        assert_eq!(
            IdScanner::extract_id_fast(".block :test_123"),
            Some("test_123".to_string())
        );
        assert_eq!(
            IdScanner::extract_id_fast(".block :123   "),
            Some("123".to_string())
        );
        assert_eq!(IdScanner::extract_id_fast(".block"), None);
        assert_eq!(IdScanner::extract_id_fast(".block :"), None);
    }

    #[test]
    fn test_scan_reader() {
        let content = r#".block :10
Some content
.div :z
.section :100
More content
.p :invalid-ID
.span :5000
"#;
        let reader = Cursor::new(content);
        let max_id = IdScanner::scan_reader(reader).unwrap();

        // Should find :5000 as the maximum
        // 5000 in base36 = 5*36^3 + 0*36^2 + 0*36 + 0 = 5*46656 = 233280 in decimal
        assert_eq!(max_id, Some(233280));
    }

    #[test]
    fn test_scan_reader_base36() {
        let content = r#".block :a
.div :z
.section :10
.p :zz
.span :100
"#;
        let reader = Cursor::new(content);
        let max_id = IdScanner::scan_reader(reader).unwrap();

        // "zz" = 35*36 + 35 = 1295
        // "100" = 1*36^2 + 0*36 + 0 = 1296
        assert_eq!(max_id, Some(1296));
    }

    #[test]
    fn test_scan_empty_file() {
        let content = "";
        let reader = Cursor::new(content);
        let max_id = IdScanner::scan_reader(reader).unwrap();
        assert_eq!(max_id, None);
    }

    #[test]
    fn test_scan_no_ids() {
        let content = r#".block
Some content
.div
.section
"#;
        let reader = Cursor::new(content);
        let max_id = IdScanner::scan_reader(reader).unwrap();
        assert_eq!(max_id, None);
    }

    #[test]
    fn test_scan_mixed_ids() {
        let content = r#".block :123
.div :not-base36
.section :456
.p :ABC123
.span :789
"#;
        let reader = Cursor::new(content);
        let max_id = IdScanner::scan_reader(reader).unwrap();

        // Should find 789 as maximum
        // 789 in base36 = 21*36 + 33 = 756 + 33 = 789... wait that's wrong
        // Let me recalculate: 789 base36 = 7*36^2 + 8*36 + 9 = 7*1296 + 288 + 9 = 9072 + 297 = 9369
        assert_eq!(max_id, Some(9369));
    }

    #[test]
    fn test_scan_file_success() {
        // Test lines 11-13 in function `scan_file`
        use std::env;
        use std::fs;

        let temp_dir = env::temp_dir();
        let temp_path = temp_dir.join("test_wasot_file.txt");
        let content = ".block :10\n.div :20\n.section :30\n";
        fs::write(&temp_path, content).unwrap();

        let result = IdScanner::scan_file(&temp_path);
        assert!(result.is_ok());
        let max_id = result.unwrap();
        assert!(max_id.is_some());

        // Clean up
        let _ = fs::remove_file(temp_path);
    }

    #[test]
    fn test_scan_file_not_found() {
        let result = IdScanner::scan_file("/tmp/nonexistent_file_12345.txt");
        assert!(result.is_err());
    }

    #[test]
    fn test_scan_reader_basic_functionality() {
        // Test lines 17-19, 21-22 in function `scan_reader`
        let content = ".block :10\nSome content\n.div :20\n";
        let reader = Cursor::new(content);

        let result = IdScanner::scan_reader(reader);
        assert!(result.is_ok());

        let max_id = result.unwrap();
        // Should find 20 as the maximum (20 in base36 = 72 in decimal)
        assert_eq!(max_id, Some(72));
    }

    #[test]
    fn test_scan_reader_with_meta_line_check() {
        // Test line 25 in function `scan_reader`
        let content = "regular text line\n.block :10\nanother regular line\n.div :20\n";
        let reader = Cursor::new(content);

        let result = IdScanner::scan_reader(reader);
        assert!(result.is_ok());

        let max_id = result.unwrap();
        // Should find 20 as maximum (ignoring non-meta lines)
        assert_eq!(max_id, Some(72)); // 20 in base36 = 72 in decimal
    }

    #[test]
    fn test_scan_reader_with_id_extraction() {
        // Test line 27 in function `scan_reader`
        let content = ".block :abc\n.div :def\n.section :xyz\n";
        let reader = Cursor::new(content);

        let result = IdScanner::scan_reader(reader);
        assert!(result.is_ok());

        let max_id = result.unwrap();
        // xyz should be the largest: x=33, y=34, z=35 -> 33*36^2 + 34*36 + 35 = 44027
        assert!(max_id.is_some());
    }

    #[test]
    fn test_scan_reader_with_base36_conversion() {
        // Test lines 29-30 in function `scan_reader`
        let content = ".block :10\n.div :invalid-base36\n.section :20\n";
        let reader = Cursor::new(content);

        let result = IdScanner::scan_reader(reader);
        assert!(result.is_ok());

        let max_id = result.unwrap();
        // Should ignore invalid-base36 and find 20 as maximum
        assert_eq!(max_id, Some(72)); // 20 in base36 = 72 in decimal
    }

    #[test]
    fn test_extract_id_fast_colon_search() {
        // Test lines 43-44, 46-48 in function `extract_id_fast`
        assert_eq!(
            IdScanner::extract_id_fast(".block multiple :words :final"),
            Some("final".to_string())
        );

        // Test with no colon
        assert_eq!(IdScanner::extract_id_fast(".block"), None);

        // Test with colon but no space before it
        assert_eq!(IdScanner::extract_id_fast(".block:nospace"), None);

        // Test with space and colon
        assert_eq!(
            IdScanner::extract_id_fast(".block :id"),
            Some("id".to_string())
        );
    }

    #[test]
    fn test_extract_id_fast_colon_position() {
        // Test line 53 in function `extract_id_fast`
        // Test case where colon_pos is not found
        assert_eq!(IdScanner::extract_id_fast("no colon here"), None);

        // Test case where colon is found
        assert_eq!(
            IdScanner::extract_id_fast(".test :found"),
            Some("found".to_string())
        );
    }

    #[test]
    fn test_extract_id_fast_id_boundaries() {
        // Test lines 56-58 in function `extract_id_fast`
        // Test case where colon is at the end
        assert_eq!(IdScanner::extract_id_fast(".block :"), None);

        // Test case where there's content after colon
        assert_eq!(
            IdScanner::extract_id_fast(".block :valid"),
            Some("valid".to_string())
        );
    }

    #[test]
    fn test_extract_id_fast_character_validation() {
        // Test lines 64-68 in function `extract_id_fast`
        // Test with invalid characters
        assert_eq!(
            IdScanner::extract_id_fast(".block :test@invalid"),
            Some("test".to_string()) // Should stop at @
        );

        // Test with tab
        assert_eq!(
            IdScanner::extract_id_fast(".block :123\t"),
            Some("123".to_string())
        );
    }

    #[test]
    fn test_extract_id_fast_empty_id() {
        // Test lines 78-79 in function `extract_id_fast`
        // Test case where id_end is 0 (no valid characters)
        assert_eq!(IdScanner::extract_id_fast(".block : "), None);
        assert_eq!(IdScanner::extract_id_fast(".block :\t"), None);
        assert_eq!(IdScanner::extract_id_fast(".block :@#$"), None);
    }

    #[test]
    fn test_extract_id_fast_string_conversion() {
        // Test lines 83-84 in function `extract_id_fast`
        assert_eq!(
            IdScanner::extract_id_fast(".block :valid-id"),
            Some("valid-id".to_string())
        );

        // Test with trimming
        assert_eq!(
            IdScanner::extract_id_fast(".block :  trimmed  "),
            Some("trimmed".to_string())
        );
    }

    #[test]
    fn test_extract_id_fast_edge_cases() {
        // Additional edge case tests
        assert_eq!(IdScanner::extract_id_fast(""), None);
        assert_eq!(IdScanner::extract_id_fast(" :"), None);
        assert_eq!(IdScanner::extract_id_fast(" :id"), Some("id".to_string()));
        assert_eq!(IdScanner::extract_id_fast(".test :"), None);

        // Test with multiple colons
        assert_eq!(
            IdScanner::extract_id_fast(".test :first :second"),
            Some("second".to_string())
        );
    }

    #[test]
    fn test_scan_reader_max_comparison() {
        let content = ".block :5\n.div :a\n.section :z\n";
        let reader = Cursor::new(content);
        let max_id = IdScanner::scan_reader(reader).unwrap();

        // z = 35 in decimal, which should be the maximum
        assert_eq!(max_id, Some(35));
    }
}
