"""
Data models for Crossref API responses.
This module provides dataclasses for representing Crossref work metadata.
"""

from contextlib import suppress
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any


@dataclass(frozen=True)
class CrossrefAuthor:
    """Author information from Crossref metadata."""

    given_name: str | None = None
    family_name: str | None = None
    full_name: str | None = None
    affiliation: list[str] = field(default_factory=list)
    sequence: str = "additional"
    orcid: str | None = None

    @classmethod
    def from_crossref(cls, data: dict[str, Any]) -> "CrossrefAuthor":
        """
        Create a CrossrefAuthor from Crossref API response data.

        Args:
            data: Author data from Crossref API.

        Returns:
            CrossrefAuthor instance.
        """
        given = data.get("given")
        family = data.get("family")
        full_name = f"{given} {family}".strip() if given or family else None
        affiliation = [
            aff.get("name", "")
            for aff in data.get("affiliation", [])
            if isinstance(aff, dict)
        ]
        orcid = None
        if isinstance(data.get("ORCID"), str):
            orcid = data["ORCID"]
        elif isinstance(data.get("orcid"), str):
            orcid = data["orcid"]

        return cls(
            given_name=given,
            family_name=family,
            full_name=full_name,
            affiliation=affiliation,
            sequence=data.get("sequence", "additional"),
            orcid=orcid,
        )


@dataclass(frozen=True)
class CrossrefReference:
    """Reference information from Crossref metadata."""

    doi: str | None = None
    key: str | None = None
    unstructured: str | None = None
    article_title: str | None = None
    journal_title: str | None = None
    volume: str | None = None
    first_page: str | None = None
    year: int | None = None
    author: str | None = None

    @classmethod
    def from_crossref(cls, data: dict[str, Any]) -> "CrossrefReference":
        """
        Create a CrossrefReference from Crossref API response data.

        Args:
            data: Reference data from Crossref API.

        Returns:
            CrossrefReference instance.
        """
        doi = None
        doi_asserted_by = data.get("doi-asserted-by")
        if doi_asserted_by == "crossref" or doi_asserted_by == "publisher":
            doi = data.get("DOI")

        year = None
        if "year" in data:
            with suppress(ValueError, TypeError):
                year = int(data["year"])

        return cls(
            doi=doi,
            key=data.get("key"),
            unstructured=data.get("unstructured"),
            article_title=data.get("article-title"),
            journal_title=data.get("journal-title"),
            volume=data.get("volume"),
            first_page=data.get("first-page"),
            year=year,
            author=data.get("author"),
        )


@dataclass(frozen=True)
class CrossrefWork:
    """
    Complete Crossref work metadata for an academic publication.
    This dataclass represents all available metadata for a work
    retrieved from the Crossref API.
    """

    doi: str
    title: list[str]
    abstract: str | None = None
    journal_title: str | None = None
    journal_short_title: str | None = None
    publisher: str | None = None
    type: str = "journal-article"
    published_date: datetime | None = None
    published_print_date: datetime | None = None
    published_online_date: datetime | None = None
    created_date: datetime | None = None
    indexed_date: datetime | None = None
    volume: str | None = None
    issue: str | None = None
    page: str | None = None
    issn: list[str] = field(default_factory=list)
    issn_type: list[str] = field(default_factory=list)
    url: str | None = None
    resource_url: str | None = None
    subjects: list[str] = field(default_factory=list)
    reference_count: int = 0
    is_referenced_by_count: int = 0
    authors: list[CrossrefAuthor] = field(default_factory=list)
    references: list[CrossrefReference] = field(default_factory=list)
    license_url: str | None = None
    license_delay: int = 0

    @classmethod
    def _parse_date(cls, date_data: dict[str, Any] | None) -> datetime | None:
        """
        Parse date from Crossref API date format.

        Args:
            date_data: Date data from Crossref API.

        Returns:
            Parsed datetime or None.
        """
        if not date_data:
            return None
        date_parts = date_data.get("date-parts")
        if date_parts and isinstance(date_parts, list) and len(date_parts) > 0:
            try:
                parts = date_parts[0]
                if len(parts) >= 1:
                    year = parts[0]
                    month = parts[1] if len(parts) > 1 else 1
                    day = parts[2] if len(parts) > 2 else 1
                    return datetime(year, month, day)
            except (IndexError, ValueError, TypeError):
                pass
        return None

    @classmethod
    def from_crossref(cls, data: dict[str, Any]) -> "CrossrefWork":
        """
        Create a CrossrefWork from Crossref API response data.

        Args:
            data: Work data from Crossref API.

        Returns:
            CrossrefWork instance.
        """
        message = data.get("message", data)

        # Parse authors
        authors = [
            CrossrefAuthor.from_crossref(author)
            for author in message.get("author", [])
            if isinstance(author, dict)
        ]

        # Parse references
        references = [
            CrossrefReference.from_crossref(ref)
            for ref in message.get("reference", [])
            if isinstance(ref, dict)
        ]

        # Parse ISSNs
        issn_list = []
        issn_type_list = []
        for issn_info in message.get("ISSN", []):
            if isinstance(issn_info, str):
                issn_list.append(issn_info)
            elif isinstance(issn_info, dict):
                issn_list.append(issn_info.get("", ""))
                issn_type_list.append(issn_info.get("type", ""))

        # Parse license
        license_url = None
        license_delay = 0
        for license_info in message.get("license", []):
            if isinstance(license_info, dict):
                license_url = license_info.get("URL")
                license_delay = license_info.get("delay-in-days", 0)
                if license_url:
                    break

        # Parse resource URL
        resource_url = None
        resource = message.get("resource", {})
        if isinstance(resource, dict):
            resource_url = resource.get("primary", {}).get("URL")

        return cls(
            doi=message.get("DOI", ""),
            title=message.get("title", []),
            abstract=message.get("abstract"),
            journal_title=message.get("container-title"),
            journal_short_title=message.get("short-container-title"),
            publisher=message.get("publisher"),
            type=message.get("type", "journal-article"),
            published_date=cls._parse_date(message.get("published")),
            published_print_date=cls._parse_date(message.get("published-print")),
            published_online_date=cls._parse_date(message.get("published-online")),
            created_date=cls._parse_date(message.get("created")),
            indexed_date=cls._parse_date(message.get("indexed")),
            volume=message.get("volume"),
            issue=message.get("issue"),
            page=message.get("page"),
            issn=issn_list,
            issn_type=issn_type_list,
            url=f"https://doi.org/{message.get('DOI', '')}"
            if message.get("DOI")
            else None,
            resource_url=resource_url,
            subjects=message.get("subject", []),
            reference_count=message.get("reference-count", 0),
            is_referenced_by_count=message.get("is-referenced-by-count", 0),
            authors=authors,
            references=references,
            license_url=license_url,
            license_delay=license_delay,
        )

    def to_dict(self) -> dict[str, Any]:
        """
        Convert CrossrefWork to a dictionary.

        Returns:
            Dictionary representation of the work.
        """
        return {
            "doi": self.doi,
            "title": self.title,
            "abstract": self.abstract,
            "journal_title": self.journal_title,
            "journal_short_title": self.journal_short_title,
            "publisher": self.publisher,
            "type": self.type,
            "published_date": self.published_date.isoformat()
            if self.published_date
            else None,
            "published_print_date": self.published_print_date.isoformat()
            if self.published_print_date
            else None,
            "published_online_date": self.published_online_date.isoformat()
            if self.published_online_date
            else None,
            "created_date": self.created_date.isoformat()
            if self.created_date
            else None,
            "indexed_date": self.indexed_date.isoformat()
            if self.indexed_date
            else None,
            "volume": self.volume,
            "issue": self.issue,
            "page": self.page,
            "issn": self.issn,
            "issn_type": self.issn_type,
            "url": self.url,
            "resource_url": self.resource_url,
            "subjects": self.subjects,
            "reference_count": self.reference_count,
            "is_referenced_by_count": self.is_referenced_by_count,
            "authors": [
                {
                    "given_name": a.given_name,
                    "family_name": a.family_name,
                    "full_name": a.full_name,
                    "affiliation": a.affiliation,
                    "orcid": a.orcid,
                }
                for a in self.authors
            ],
            "references": [
                {
                    "doi": r.doi,
                    "key": r.key,
                    "unstructured": r.unstructured,
                    "article_title": r.article_title,
                    "journal_title": r.journal_title,
                    "volume": r.volume,
                    "first_page": r.first_page,
                    "year": r.year,
                    "author": r.author,
                }
                for r in self.references
            ],
            "license_url": self.license_url,
            "license_delay": self.license_delay,
        }

    def get_reference_dois(self) -> list[str]:
        """
        Extract all DOIs from the references.

        Returns:
            List of DOI strings from the references. Empty DOIs are excluded.
        """
        return [ref.doi for ref in self.references if ref.doi]

    def get_reference_dois_with_info(self) -> list[dict[str, Any]]:
        """
        Extract reference DOIs with additional metadata.

        Returns:
            List of dictionaries containing DOI and reference metadata.
        """
        return [
            {
                "doi": ref.doi,
                "title": ref.article_title,
                "journal": ref.journal_title,
                "year": ref.year,
                "author": ref.author,
                "volume": ref.volume,
                "first_page": ref.first_page,
            }
            for ref in self.references
            if ref.doi
        ]

    def get_reference_doi_count(self) -> dict[str, int]:
        """
        Get statistics about reference DOIs.

        Returns:
            Dictionary with counts of references with and without DOIs.
        """
        total = len(self.references)
        with_doi = sum(1 for ref in self.references if ref.doi)
        without_doi = total - with_doi
        return {
            "total_references": total,
            "with_doi": with_doi,
            "without_doi": without_doi,
            "doi_percentage": round(with_doi / total * 100, 1) if total > 0 else 0.0,
        }
