use crate::ids::ReferenceId;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
use std::path::PathBuf;

/// A person (author, editor, translator, etc.).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Person {
    pub family: String,
    pub given: Option<String>,
    /// Name prefix: "von", "de", "van der", etc.
    pub prefix: Option<String>,
    /// Name suffix: "Jr.", "III", etc.
    pub suffix: Option<String>,
}

impl Person {
    pub fn new(family: impl Into<String>) -> Self {
        Self {
            family: family.into(),
            given: None,
            prefix: None,
            suffix: None,
        }
    }

    /// Format for display: "Given prefix Family, Suffix" — natural reading order.
    pub fn display_name(&self) -> String {
        let mut parts = Vec::new();
        if let Some(given) = &self.given {
            parts.push(given.as_str());
        }
        if let Some(prefix) = &self.prefix {
            parts.push(prefix.as_str());
        }
        parts.push(self.family.as_str());
        let mut name = parts.join(" ");
        if let Some(suffix) = &self.suffix {
            name.push_str(", ");
            name.push_str(suffix);
        }
        name
    }

    /// Format as BibTeX expects: "{prefix} {family}, {suffix}, {given}".
    /// Falls back gracefully when optional parts are absent.
    pub fn to_bibtex(&self) -> String {
        let mut family_part = String::new();
        if let Some(prefix) = &self.prefix {
            family_part.push_str(prefix);
            family_part.push(' ');
        }
        family_part.push_str(&self.family);

        match (&self.suffix, &self.given) {
            (Some(suffix), Some(given)) => {
                format!("{family_part}, {suffix}, {given}")
            }
            (Some(suffix), None) => format!("{family_part}, {suffix}"),
            (None, Some(given)) => format!("{family_part}, {given}"),
            (None, None) => family_part,
        }
    }
}

impl fmt::Display for Person {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.display_name())
    }
}

/// Standard BibTeX and common BibLaTeX entry types.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EntryType {
    Article,
    Book,
    Booklet,
    InBook,
    InCollection,
    InProceedings,
    Manual,
    MastersThesis,
    Misc,
    PhdThesis,
    Proceedings,
    TechReport,
    Unpublished,
    Online,
}

impl EntryType {
    /// The BibTeX entry type name as it appears in `.bib` files.
    pub fn bibtex_name(&self) -> &'static str {
        match self {
            EntryType::Article => "article",
            EntryType::Book => "book",
            EntryType::Booklet => "booklet",
            EntryType::InBook => "inbook",
            EntryType::InCollection => "incollection",
            EntryType::InProceedings => "inproceedings",
            EntryType::Manual => "manual",
            EntryType::MastersThesis => "mastersthesis",
            EntryType::Misc => "misc",
            EntryType::PhdThesis => "phdthesis",
            EntryType::Proceedings => "proceedings",
            EntryType::TechReport => "techreport",
            EntryType::Unpublished => "unpublished",
            EntryType::Online => "online",
        }
    }
}

impl fmt::Display for EntryType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.bibtex_name())
    }
}

/// A PDF file stored inside the Brittle repository.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PdfAttachment {
    /// Path relative to the repository root (e.g., `"pdfs/550e8400-....pdf"`).
    pub stored_path: PathBuf,
    /// SHA-256 hex digest of the file contents for integrity verification.
    pub content_hash: String,
}

/// A citable work. The core entity of Brittle.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Reference {
    pub id: ReferenceId,
    /// The BibTeX cite key (e.g., `"knuth1984texbook"`). User-facing and mutable.
    pub cite_key: String,
    pub entry_type: EntryType,
    /// Authors listed in order.
    pub authors: Vec<Person>,
    /// Editors (for edited books, proceedings, etc.).
    pub editors: Vec<Person>,
    /// All other fields (title, year, journal, volume, etc.) as plain strings.
    /// BTreeMap for deterministic serialization order (important for git diffs).
    pub fields: BTreeMap<String, String>,
    pub pdf: Option<PdfAttachment>,
    pub created_at: DateTime<Utc>,
    pub modified_at: DateTime<Utc>,
}

impl Reference {
    pub fn new(cite_key: impl Into<String>, entry_type: EntryType) -> Self {
        let now = Utc::now();
        Self {
            id: ReferenceId::new(),
            cite_key: cite_key.into(),
            entry_type,
            authors: Vec::new(),
            editors: Vec::new(),
            fields: BTreeMap::new(),
            pdf: None,
            created_at: now,
            modified_at: now,
        }
    }

    /// Returns the value of the `title` field, if present.
    pub fn title(&self) -> Option<&str> {
        self.fields.get("title").map(String::as_str)
    }

    /// Returns the value of the `year` field, if present.
    pub fn year(&self) -> Option<&str> {
        self.fields.get("year").map(String::as_str)
    }
}

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

    #[test]
    fn person_bibtex_full() {
        let p = Person {
            family: "Dijkstra".into(),
            given: Some("Edsger W.".into()),
            prefix: None,
            suffix: None,
        };
        assert_eq!(p.to_bibtex(), "Dijkstra, Edsger W.");
    }

    #[test]
    fn person_bibtex_with_prefix() {
        let p = Person {
            family: "Beethoven".into(),
            given: Some("Ludwig".into()),
            prefix: Some("van".into()),
            suffix: None,
        };
        assert_eq!(p.to_bibtex(), "van Beethoven, Ludwig");
    }

    #[test]
    fn person_bibtex_with_suffix() {
        let p = Person {
            family: "King".into(),
            given: Some("Martin Luther".into()),
            prefix: None,
            suffix: Some("Jr.".into()),
        };
        assert_eq!(p.to_bibtex(), "King, Jr., Martin Luther");
    }

    #[test]
    fn person_bibtex_family_only() {
        let p = Person::new("Aristotle");
        assert_eq!(p.to_bibtex(), "Aristotle");
    }

    #[test]
    fn person_display_name_full() {
        let p = Person {
            family: "Turing".into(),
            given: Some("Alan".into()),
            prefix: None,
            suffix: None,
        };
        assert_eq!(p.display_name(), "Alan Turing");
    }

    #[test]
    fn person_display_name_with_prefix_and_suffix() {
        let p = Person {
            family: "King".into(),
            given: Some("Martin Luther".into()),
            prefix: None,
            suffix: Some("Jr.".into()),
        };
        assert_eq!(p.display_name(), "Martin Luther King, Jr.");
    }

    #[test]
    fn reference_serde_round_trip() {
        let mut r = Reference::new("doe2024", EntryType::Article);
        r.authors.push(Person {
            family: "Doe".into(),
            given: Some("Jane".into()),
            prefix: None,
            suffix: None,
        });
        r.fields.insert("title".into(), "A Great Paper".into());
        r.fields.insert("year".into(), "2024".into());

        let json = serde_json::to_string(&r).expect("serialize to JSON");
        let r2: Reference = serde_json::from_str(&json).expect("deserialize from JSON");

        assert_eq!(r.id, r2.id);
        assert_eq!(r.cite_key, r2.cite_key);
        assert_eq!(r.authors, r2.authors);
        assert_eq!(r.fields, r2.fields);
    }

    #[test]
    fn entry_type_bibtex_names() {
        assert_eq!(EntryType::Article.bibtex_name(), "article");
        assert_eq!(EntryType::InProceedings.bibtex_name(), "inproceedings");
        assert_eq!(EntryType::PhdThesis.bibtex_name(), "phdthesis");
    }

    #[test]
    fn entry_type_display() {
        assert_eq!(EntryType::Article.to_string(), "article");
        assert_eq!(EntryType::InProceedings.to_string(), "inproceedings");
    }
}
