use std::cmp::Ordering;
use std::collections::HashSet;

use chrono::NaiveDate;

use crate::terminal::{FilingRef, Frequency};

use super::super::client::SecEdgarClient;
use super::super::service::EdgarLookupError;
use super::super::types::CompanySubmissions;

const ANNUAL_FORMS: &[&str] = &["10-K", "10-K/A", "20-F", "20-F/A", "40-F", "40-F/A"];
const QUARTERLY_FORMS: &[&str] = &["10-Q", "10-Q/A", "6-K", "6-K/A"];

pub(crate) async fn load_candidate_filings(
    client: &SecEdgarClient,
    _cik: &str,
    submissions: &CompanySubmissions,
    frequency: Frequency,
) -> Result<Vec<FilingRef>, EdgarLookupError> {
    let mut filings = submissions.filings.recent.rows();

    for file in &submissions.filings.files {
        let supplemental = client.load_submissions_file(&file.name).await?;
        filings.extend(supplemental.rows());
    }

    filings.retain(|filing| matches_frequency_form(&filing.form, frequency));
    filings.sort_by(compare_filing_priority);

    let mut seen_accessions = HashSet::new();
    filings.retain(|filing| seen_accessions.insert(filing.accession_number.clone()));

    Ok(filings)
}

pub(crate) fn matches_frequency_form(form: &str, frequency: Frequency) -> bool {
    match frequency {
        Frequency::Annual => ANNUAL_FORMS.contains(&form),
        Frequency::Quarterly => QUARTERLY_FORMS.contains(&form),
    }
}

fn compare_filing_priority(left: &FilingRef, right: &FilingRef) -> Ordering {
    compare_date_strings(
        left.report_date.as_deref(),
        right.report_date.as_deref(),
        false,
    )
    .then_with(|| compare_date_strings(Some(&left.filing_date), Some(&right.filing_date), false))
    .then_with(|| amendment_priority(&right.form).cmp(&amendment_priority(&left.form)))
    .then_with(|| right.accession_number.cmp(&left.accession_number))
}

fn compare_date_strings(left: Option<&str>, right: Option<&str>, ascending: bool) -> Ordering {
    let left_date = left.and_then(parse_date);
    let right_date = right.and_then(parse_date);

    let ordering = left_date.cmp(&right_date);
    if ascending {
        ordering
    } else {
        ordering.reverse()
    }
}

fn parse_date(value: &str) -> Option<NaiveDate> {
    NaiveDate::parse_from_str(value, "%Y-%m-%d").ok()
}

fn amendment_priority(form: &str) -> u8 {
    u8::from(form.ends_with("/A"))
}

#[cfg(test)]
mod tests {
    use crate::terminal::FilingRef;

    use super::*;

    #[test]
    fn compare_filing_priority_should_prefer_newer_report_dates() {
        let newer = FilingRef {
            accession_number: "0001".to_string(),
            filing_date: "2025-02-01".to_string(),
            report_date: Some("2024-12-31".to_string()),
            form: "10-K".to_string(),
            primary_document: None,
        };
        let older = FilingRef {
            accession_number: "0002".to_string(),
            filing_date: "2024-02-01".to_string(),
            report_date: Some("2023-12-31".to_string()),
            form: "10-K".to_string(),
            primary_document: None,
        };

        assert_eq!(compare_filing_priority(&newer, &older), Ordering::Less);
    }
}
