use crate::{
    api::Colle,
    parser::utils::{hash_text, parse_french_date, with_time},
};
use chrono::{Datelike, NaiveDate};
use reqwest::Client;
use std::{collections::HashMap, io::Write};
use tempfile::NamedTempFile;

// Splits a vector of strings into a HashMap based on dates.
// The keys are the dates, and the values are vectors of strings containing the lines after that
fn split_dates(text: Vec<&str>) -> HashMap<NaiveDate, Vec<String>> {
    let current_year = chrono::Utc::now().year();
    let mut result = HashMap::new();

    let mut current_date: Option<NaiveDate> = None;
    let mut content: Vec<String> = Vec::new();

    for line in text {
        if line.contains(&current_year.to_string()) {
            // Found a potential date line
            match parse_french_date(line) {
                Ok(date) => {
                    // If we already have a date, store its content before switching
                    if let Some(prev_date) = current_date {
                        if !content.is_empty() {
                            result.insert(prev_date, content.clone());
                            content.clear();
                        }
                    }
                    // Update the current date
                    current_date = Some(date);
                }
                Err(_) => {
                    eprintln!("Failed to parse date from line: {}", line);
                }
            }
        } else {
            // Regular line -> belongs to the current date if any
            if current_date.is_some() {
                content.push(line.to_string());
            }
        }
    }

    // Flush the last group
    if let Some(date) = current_date {
        if !content.is_empty() {
            result.insert(date, content);
        }
    }

    result
}

// Check if the line starts with a time format (e.g., "16h20")
fn is_time(line: &str) -> bool {
    let trimmed = line.trim();

    if let Some(h_pos) = trimmed.find('h') {
        // Check if there are only digits before 'h'
        let before_h = &trimmed[..h_pos];
        if !before_h.is_empty() && before_h.chars().all(|c| c.is_digit(10)) {
            // Check that there are exactly 2 digits after 'h'
            let after_h = &trimmed[h_pos + 1..];
            return after_h.len() >= 2 && after_h.chars().take(2).all(|c| c.is_digit(10));
        }
    }

    false
}

// Check if the line starts with a last name (uppercase first word)
fn is_name(line: &str) -> bool {
    let trimmed = line.trim();
    if let Some(first_space) = trimmed.find(' ') {
        let first_word = &trimmed[..first_space];
        return first_word.chars().all(|c| c.is_uppercase() || c == '-');
    }
    false
}

// Extract all names from a line (split on "/" and trim each part)
fn extract_names(line: &str) -> Vec<String> {
    line.split('/')
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty())
        .collect()
}

// Parse header (time, teacher, room)
fn parse_header(line: &str) -> (String, String, String) {
    // First 5 characters are the time
    let time = line.get(0..5).unwrap_or("").to_string();
    // Split the rest on "Salle :"
    let parts: Vec<&str> = line[5..].split("Salle :").collect();
    let teacher = parts.get(0).map_or("", |s| s.trim()).to_string();
    let room = parts.get(1).map_or("", |s| s.trim()).to_string();
    (time, teacher, room)
}

// Parse the PDF text into a vector of Upcoming colles
fn parse_upcoming(text: Vec<&str>) -> Vec<Colle> {
    split_dates(text)
        .into_iter()
        .flat_map(|(date, lines)| {
            let mut current_header = (String::new(), String::new(), String::new());
            let mut current_subject = String::new();

            lines.into_iter().flat_map(move |line| {
                let trimmed = line.trim();
                if trimmed.is_empty() {
                    return Vec::new();
                }

                if is_time(trimmed) {
                    current_header = parse_header(trimmed);
                    Vec::new()
                } else if is_name(trimmed) {
                    extract_names(trimmed)
                        .into_iter()
                        .map(|name| Colle {
                            date: with_time(date, &current_header.0).unwrap_or_default(),
                            examiner: current_header.1.clone(),
                            room: current_header.2.clone(),
                            subject: current_subject.clone(),
                            student: name,
                            bjid: None,
                            bjsecret: None,
                            grade: None,
                            content: None,
                            comment: None,
                            attachments: None,
                        })
                        .collect()
                } else {
                    current_subject = trimmed.to_string();
                    Vec::new()
                }
            })
        })
        .collect()
}

pub async fn fetch(session: &str, last_hash: &str) -> (Vec<Colle>, String) {
    let url = "https://bjcolle.fr/oral_choice_week_billboard_cdt.php";
    // Start date is 3 days ago, end date is 14 days from now (2 weeks)
    let start_date = chrono::Utc::now().date_naive() - chrono::Duration::days(3);
    let end_date = chrono::Utc::now().date_naive() + chrono::Duration::days(14);

    let response = Client::new()
        .post(url)
        .header("Cookie", session)
        .form(&[
            ("datepicker_1", &start_date.format("%d/%m/%Y").to_string()),
            ("datepicker_2", &end_date.format("%d/%m/%Y").to_string()),
            ("VALIDER_SEMAINE", &"Valider".to_string()),
        ])
        .send()
        .await
        .expect("Failed to send request");

    if !response.status().is_success() {
        eprintln!(
            "Failed to fetch upcoming colles: HTTP {}",
            response.status()
        );
        return (Vec::new(), String::new());
    }

    // Save PDF to a temporary file
    let body = response
        .bytes()
        .await
        .expect("Failed to read response body");
    let mut tmpfile = NamedTempFile::new().expect("Failed to create temp file");
    tmpfile
        .write_all(&body)
        .expect("Failed to write PDF to temp file");

    // Extract text using pdf_extract
    let pdf_text = pdf_extract::extract_text(tmpfile.path()).expect("Failed to extract PDF text");

    // Calculate the hash of the PDF file
    let hash = hash_text(&pdf_text);
    // TODO: Fix hash not refetching on error
    // if hash == last_hash || pdf_text.contains("Aucune colle") {
    //     return (Vec::new(), hash);
    // }

    // Split the text into lines and filter out empty lines
    let lines: Vec<&str> = pdf_text
        .lines()
        .filter(|line| !line.trim().is_empty())
        .collect();
    // Remove the first line
    let lines: Vec<&str> = lines.into_iter().skip(1).collect();

    // Parse the lines into Upcoming structs
    let upcoming_colles = parse_upcoming(lines);
    if upcoming_colles.is_empty() {
        eprintln!("No upcoming colles found.");
    } else {
        println!("Found {} upcoming colles.", upcoming_colles.len());
    }
    (upcoming_colles, hash)
}
