use crate::normalize::author_family_names;
use crate::scoring::score_candidate;
use crate::types::{Candidate, EntryResult, MatchConfig, ReferenceFields, ScoreBreakdown};

pub fn match_reference(reference: &ReferenceFields, candidates: &[Candidate], config: Option<&MatchConfig>) -> EntryResult {
    if candidates.is_empty() {
        return EntryResult {
            verdict: "UNVERIFIED".to_string(),
            score: 0.0,
            reasons: vec!["No candidates found.".to_string()],
            best_candidate: None,
            breakdown: None,
        };
    }

    let cfg = config.cloned().unwrap_or_else(MatchConfig::new);
    let mut scored: Vec<(f64, ScoreBreakdown, Candidate)> = Vec::new();

    for candidate in candidates {
        let candidate_fields = ReferenceFields {
            title: candidate.title.clone(),
            authors: candidate.authors.clone(),
            year: candidate.year,
            venue: candidate.venue.clone(),
            ids: candidate.ids.clone(),
        };
        let result = score_candidate(reference, &candidate_fields, Some(&cfg.score_config));
        scored.push((result.score, result.breakdown, candidate.clone()));
    }

    scored.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
    let (best_score, best_breakdown, best_candidate) = scored[0].clone();

    let hard_match = hard_match(reference, &best_candidate);
    let mut verdict = if hard_match.is_some() {
        "VERIFIED"
    } else if best_score >= cfg.verified_threshold {
        "VERIFIED"
    } else if best_score >= cfg.likely_threshold {
        "LIKELY"
    } else {
        "UNVERIFIED"
    }
    .to_string();

    let mut reason = match hard_match {
        Some(kind) => format!("Exact {} match.", kind),
        None if best_score >= cfg.verified_threshold => "Metadata similarity above verified threshold.".to_string(),
        None if best_score >= cfg.likely_threshold => "Metadata similarity above likely threshold.".to_string(),
        _ => "No candidates above likely threshold.".to_string(),
    };

    if has_conflict(&scored, reference, cfg.likely_threshold) {
        verdict = "CONFLICT".to_string();
        reason = "Multiple high-scoring candidates disagree on key fields.".to_string();
    }

    EntryResult {
        verdict,
        score: best_score,
        reasons: vec![reason],
        best_candidate: Some(best_candidate),
        breakdown: Some(best_breakdown),
    }
}

fn hard_match(reference: &ReferenceFields, candidate: &Candidate) -> Option<&'static str> {
    if let (Some(ref doi_a), Some(ref doi_b)) = (&reference.ids.doi, &candidate.ids.doi) {
        if doi_a.eq_ignore_ascii_case(doi_b) {
            return Some("doi");
        }
    }
    if let (Some(ref arxiv_a), Some(ref arxiv_b)) = (&reference.ids.arxiv, &candidate.ids.arxiv) {
        if arxiv_a == arxiv_b {
            return Some("arxiv");
        }
    }
    if let (Some(ref isbn_a), Some(ref isbn_b)) = (&reference.ids.isbn, &candidate.ids.isbn) {
        if isbn_a == isbn_b {
            return Some("isbn");
        }
    }
    None
}

fn has_conflict(scored: &[(f64, ScoreBreakdown, Candidate)], reference: &ReferenceFields, threshold: f64) -> bool {
    let contenders: Vec<_> = scored.iter().filter(|(score, _, _)| *score >= threshold).collect();
    if contenders.len() < 2 {
        return false;
    }
    let (_, _, cand_a) = contenders[0];
    let (_, _, cand_b) = contenders[1];

    if let (Some(year_a), Some(year_b)) = (cand_a.year, cand_b.year) {
        if (year_a - year_b).abs() > 1 {
            return true;
        }
    }

    let fam_a = author_family_names(&cand_a.authors);
    let fam_b = author_family_names(&cand_b.authors);
    if !fam_a.is_empty() && !fam_b.is_empty() && fam_a[0] != fam_b[0] {
        return true;
    }

    if reference.ids.doi.is_some() && cand_a.ids.doi.is_some() && cand_b.ids.doi.is_some() {
        if cand_a.ids.doi != cand_b.ids.doi {
            return true;
        }
    }

    false
}
