//! Integration test that validates the morphology parser against parsing.csv

use biblicist::{Case, Gender, Mood, Number, Parsing, PartOfSpeech, Person, Tense, Voice};
use std::fs::File;
use std::io::{BufRead, BufReader};

struct CsvRow {
    abbreviation: String,
    function: String,
    case: String,
    number: String,
    gender: String,
    _extra: String,
    _name_type: String,
    _original_language: String,
    _adj_numb: String,
    person: String,
    _indeclinable: String,
    _name_in_original_language: String,
    tense: String,
    voice: String,
    mood: String,
    _form: String,
}

impl CsvRow {
    fn from_line(line: &str) -> Option<Self> {
        let parts: Vec<&str> = line.split(',').collect();
        if parts.len() < 16 {
            return None;
        }
        Some(Self {
            abbreviation: parts[0].to_string(),
            function: parts[1].to_string(),
            case: parts[2].to_string(),
            number: parts[3].to_string(),
            gender: parts[4].to_string(),
            _extra: parts[5].to_string(),
            _name_type: parts[6].to_string(),
            _original_language: parts[7].to_string(),
            _adj_numb: parts[8].to_string(),
            person: parts[9].to_string(),
            _indeclinable: parts[10].to_string(),
            _name_in_original_language: parts[11].to_string(),
            tense: parts[12].to_string(),
            voice: parts[13].to_string(),
            mood: parts[14].to_string(),
            _form: parts[15].to_string(),
        })
    }
}

fn expected_pos(function: &str) -> Option<PartOfSpeech> {
    match function {
        "Adjective" => Some(PartOfSpeech::Adjective),
        "Adverb" | "Adverb or adverb and particle combined" => Some(PartOfSpeech::Adverb),
        "Aramaic transliterated word" => Some(PartOfSpeech::Aramaic),
        "Conjunction" => Some(PartOfSpeech::Conjunction),
        "Correlative pronoun" => Some(PartOfSpeech::Correlative),
        "Correlative or Interrogative pronoun" => Some(PartOfSpeech::Correlative),
        "Definite article" => Some(PartOfSpeech::Article),
        "Demonstrative pronoun" | "Demonstrative pronoun+Conjunction" => {
            Some(PartOfSpeech::Demonstrative)
        }
        "Indefinite pronoun" => Some(PartOfSpeech::Indefinite),
        "Indeclinable Noun of Other type" | "Indeclinable Proper Noun" => Some(PartOfSpeech::Noun),
        "Interjection" => Some(PartOfSpeech::Interjection),
        "Interrogative pronoun" => Some(PartOfSpeech::Interrogative),
        "Noun" => Some(PartOfSpeech::Noun),
        "Particle" | "Particle or Disjunctive" => Some(PartOfSpeech::Particle),
        "Personal pronoun" => Some(PartOfSpeech::Pronoun),
        "Possessive pronoun" => Some(PartOfSpeech::Possessive),
        "Preposition" => Some(PartOfSpeech::Preposition),
        "Reciprocal pronoun" => Some(PartOfSpeech::Reciprocal),
        "Reflexive pronoun" => Some(PartOfSpeech::Reflexive),
        "Relative pronoun" => Some(PartOfSpeech::Relative),
        "Verb" => Some(PartOfSpeech::Verb),
        "" => None,
        other => {
            eprintln!("Unknown function: {}", other);
            None
        }
    }
}

fn expected_case(case: &str) -> Option<Case> {
    match case {
        "Accusative" => Some(Case::Accusative),
        "Dative" => Some(Case::Dative),
        "Genitive" => Some(Case::Genitive),
        "Nominative" => Some(Case::Nominative),
        "Vocative" => Some(Case::Vocative),
        "" => None,
        other => {
            eprintln!("Unknown case: {}", other);
            None
        }
    }
}

fn expected_number(number: &str) -> Option<Number> {
    match number {
        "Singular" => Some(Number::Singular),
        "Plural" => Some(Number::Plural),
        "" => None,
        other => {
            eprintln!("Unknown number: {}", other);
            None
        }
    }
}

fn expected_gender(gender: &str) -> Option<Gender> {
    match gender {
        "Masculine" => Some(Gender::Masculine),
        "Feminine" => Some(Gender::Feminine),
        "Neuter" => Some(Gender::Neuter),
        "" => None,
        other => {
            eprintln!("Unknown gender: {}", other);
            None
        }
    }
}

fn expected_person(person: &str) -> Option<Person> {
    match person {
        "1st" | "1st Person" => Some(Person::First),
        "2nd" | "2nd Person" => Some(Person::Second),
        "3rd" | "3rd Person" => Some(Person::Third),
        "" => None,
        other => {
            eprintln!("Unknown person: {}", other);
            None
        }
    }
}

fn expected_tense(tense: &str, abbreviation: &str) -> Option<Tense> {
    if abbreviation.starts_with("V-2") {
        return Some(Tense::Second);
    }
    match tense {
        "Present" => Some(Tense::Present),
        "Imperfect" => Some(Tense::Imperfect),
        "Future" => Some(Tense::Future),
        "Aorist" => Some(Tense::Aorist),
        "Perfect" => Some(Tense::Perfect),
        "Pluperfect" => Some(Tense::Pluperfect),
        "2nd Aorist" | "2nd Future" | "2nd Perfect" | "2nd Pluperfect" | "2nd Present" => {
            Some(Tense::Second)
        }
        "" => None,
        other => {
            eprintln!("Unknown tense: {}", other);
            None
        }
    }
}

fn expected_voice(voice: &str) -> Option<Voice> {
    match voice {
        "Active" => Some(Voice::Active),
        "Middle" => Some(Voice::Middle),
        "Passive" => Some(Voice::Passive),
        "Middle or Passive" | "indefinite voice" => Some(Voice::MiddleOrPassive),
        "Middle Deponent" => Some(Voice::MiddleDeponent),
        "Passive Deponent" => Some(Voice::PassiveDeponent),
        "Middle or Passive Deponent" => Some(Voice::MiddleOrPassiveDeponent),
        "Impersonal Active" | "impersonal active" => Some(Voice::Impersonal),
        "" => None,
        other => {
            eprintln!("Unknown voice: {}", other);
            None
        }
    }
}

fn expected_mood(mood: &str) -> Option<Mood> {
    match mood {
        "Indicative" => Some(Mood::Indicative),
        "Subjunctive" => Some(Mood::Subjunctive),
        "Optative" => Some(Mood::Optative),
        "Imperative" => Some(Mood::Imperative),
        "Infinitive" => Some(Mood::Infinitive),
        "Participle" => Some(Mood::Participle),
        "" => None,
        other => {
            eprintln!("Unknown mood: {}", other);
            None
        }
    }
}

const KNOWN_CSV_ERRORS: &[&str] = &[
    "S-1PGPM",   // CSV says Singular but code has P (Plural)
    "S-2PGPM",   // CSV says Singular but code has P (Plural)
    "V-PEP-ASM", // CSV says Dative/Active but code has A (Accusative) and E (Middle/Passive)
    "V-RMM-2P",  // CSV says Indicative but code has M (Imperative)
    "V-RMM-2S",  // CSV says Indicative but code has M (Imperative)
];

#[test]
fn test_all_parsing_codes() {
    let csv_path = concat!(env!("CARGO_MANIFEST_DIR"), "/parsing.csv");
    let file = File::open(csv_path).expect("Failed to open parsing.csv");
    let reader = BufReader::new(file);

    let mut errors: Vec<String> = Vec::new();
    let mut total = 0;
    let mut passed = 0;
    let mut known_csv_errors = 0;

    for (line_num, line) in reader.lines().enumerate() {
        let line = line.expect("Failed to read line");

        // Skip header
        if line_num == 0 {
            continue;
        }

        let row = match CsvRow::from_line(&line) {
            Some(r) => r,
            None => {
                errors.push(format!("Line {}: Failed to parse CSV row", line_num + 1));
                continue;
            }
        };

        total += 1;

        // Skip known CSV data errors
        if KNOWN_CSV_ERRORS.contains(&row.abbreviation.as_str()) {
            known_csv_errors += 1;
            continue;
        }

        // Parse the abbreviation
        let parsing_result: Result<Parsing, _> = row.abbreviation.parse();

        match parsing_result {
            Err(e) => {
                errors.push(format!("{}: Parse failed - {}", row.abbreviation, e));
            }
            Ok(parsing) => {
                let mut row_errors: Vec<String> = Vec::new();

                // Check part of speech
                if let Some(expected) = expected_pos(&row.function) {
                    if parsing.part_of_speech != Some(expected) {
                        row_errors.push(format!(
                            "POS: expected {:?} (from '{}'), got {:?}",
                            expected, row.function, parsing.part_of_speech
                        ));
                    }
                }

                // Check case
                let exp_case = expected_case(&row.case);
                if exp_case.is_some() && parsing.case != exp_case {
                    row_errors.push(format!(
                        "Case: expected {:?}, got {:?}",
                        exp_case, parsing.case
                    ));
                }

                // Check number
                let exp_number = expected_number(&row.number);
                if exp_number.is_some() && parsing.number != exp_number {
                    row_errors.push(format!(
                        "Number: expected {:?}, got {:?}",
                        exp_number, parsing.number
                    ));
                }

                // Check gender
                let exp_gender = expected_gender(&row.gender);
                if exp_gender.is_some() && parsing.gender != exp_gender {
                    row_errors.push(format!(
                        "Gender: expected {:?}, got {:?}",
                        exp_gender, parsing.gender
                    ));
                }

                // Check person
                let exp_person = expected_person(&row.person);
                if exp_person.is_some() && parsing.person != exp_person {
                    row_errors.push(format!(
                        "Person: expected {:?}, got {:?}",
                        exp_person, parsing.person
                    ));
                }

                // Check tense
                let exp_tense = expected_tense(&row.tense, &row.abbreviation);
                if exp_tense.is_some() && parsing.tense != exp_tense {
                    row_errors.push(format!(
                        "Tense: expected {:?}, got {:?}",
                        exp_tense, parsing.tense
                    ));
                }

                // Check voice
                let exp_voice = expected_voice(&row.voice);
                if exp_voice.is_some() && parsing.voice != exp_voice {
                    row_errors.push(format!(
                        "Voice: expected {:?}, got {:?}",
                        exp_voice, parsing.voice
                    ));
                }

                // Check mood
                let exp_mood = expected_mood(&row.mood);
                if exp_mood.is_some() && parsing.mood != exp_mood {
                    row_errors.push(format!(
                        "Mood: expected {:?}, got {:?}",
                        exp_mood, parsing.mood
                    ));
                }

                if row_errors.is_empty() {
                    passed += 1;
                } else {
                    errors.push(format!("{}: {}", row.abbreviation, row_errors.join("; ")));
                }
            }
        }
    }

    // Print summary
    println!("\n=== Parsing Test Results ===");
    println!(
        "Total: {}, Passed: {}, Failed: {}, Known CSV Errors: {}",
        total,
        passed,
        errors.len(),
        known_csv_errors
    );

    if !errors.is_empty() {
        println!("\n=== Errors ({}) ===", errors.len());
        for error in &errors {
            println!("  {}", error);
        }
        panic!(
            "\n{} parsing codes failed out of {} total",
            errors.len(),
            total
        );
    }
}
