"""
Document Metadata Extraction

Extracts and manages document metadata for the SAGE knowledge base.
Handles document identification, classification, and structural metadata.
"""

import logging
import re
import uuid
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)


class DocumentType(Enum):
    """Classification of document types."""

    # NRC Documents
    NUREG = "nureg"
    NUREG_CR = "nureg_cr"
    REGULATORY_GUIDE = "regulatory_guide"
    SRP = "srp"  # Standard Review Plan
    GENERIC_LETTER = "generic_letter"
    INFORMATION_NOTICE = "information_notice"

    # DOE/Lab Reports
    LA_REPORT = "la_report"  # Los Alamos
    ORNL_REPORT = "ornl_report"  # Oak Ridge
    SAND_REPORT = "sand_report"  # Sandia
    DOE_STD = "doe_std"  # DOE Standards

    # Standards
    ANSI_ANS = "ansi_ans"  # ANSI/ANS Standards
    ASTM = "astm"
    ISO = "iso"

    # Reference Data
    ICSBEP = "icsbep"  # International Criticality Safety Benchmark
    ENDF = "endf"  # Evaluated Nuclear Data File

    # General
    JOURNAL_ARTICLE = "journal_article"
    CONFERENCE_PAPER = "conference_paper"
    HANDBOOK = "handbook"
    OTHER = "other"


class DocumentSource(Enum):
    """Source of the document."""

    NRC_ADAMS = "nrc_adams"
    OSTI = "osti"
    ANSI = "ansi"
    DOE = "doe"
    ICSBEP = "icsbep"
    MANUAL = "manual"  # Manually added
    OTHER = "other"


@dataclass
class Section:
    """Represents a document section in the hierarchy."""

    title: str
    level: int  # 1=top level, 2=subsection, etc.
    page_start: int | None = None
    page_end: int | None = None
    subsections: list["Section"] = field(default_factory=list)

    def to_dict(self) -> dict:
        """Convert to dictionary for JSON serialization."""
        return {
            "title": self.title,
            "level": self.level,
            "page_start": self.page_start,
            "page_end": self.page_end,
            "subsections": [s.to_dict() for s in self.subsections],
        }


@dataclass
class DocumentMetadata:
    """
    Complete metadata for a document in the SAGE knowledge base.

    This class contains all information needed to identify, cite,
    and retrieve a document and its contents.
    """

    # Identity
    id: str = ""  # Internal UUID
    doc_id: str = ""  # Human-readable ID (e.g., "NUREG-1520", "ANSI/ANS-8.1-2014")
    title: str = ""

    # Authorship
    authors: list[str] = field(default_factory=list)
    organization: str = ""

    # Publication info
    publication_date: datetime | None = None
    revision: str = ""
    version: str = ""

    # Classification
    doc_type: DocumentType = DocumentType.OTHER
    source: DocumentSource = DocumentSource.OTHER

    # Structure
    sections: list[Section] = field(default_factory=list)
    total_pages: int = 0

    # File info
    file_path: Path | None = None
    file_hash: str = ""  # SHA-256 for deduplication
    file_size: int = 0

    # Processing info
    extraction_date: datetime | None = None
    extraction_quality: float = 1.0  # 0.0 to 1.0

    # Additional metadata
    abstract: str = ""
    keywords: list[str] = field(default_factory=list)
    references: list[str] = field(default_factory=list)  # Cited documents
    supersedes: str | None = None  # Document this supersedes
    superseded_by: str | None = None  # Document that supersedes this

    # Custom metadata
    extra: dict = field(default_factory=dict)

    def __post_init__(self):
        if not self.id:
            self.id = str(uuid.uuid4())
        if not self.extraction_date:
            self.extraction_date = datetime.now()

    @property
    def citation_string(self) -> str:
        """Generate a citation string for this document."""
        parts = []

        if self.authors:
            if len(self.authors) <= 2:
                parts.append(", ".join(self.authors))
            else:
                parts.append(f"{self.authors[0]} et al.")

        if self.title:
            parts.append(f'"{self.title}"')

        if self.doc_id:
            parts.append(self.doc_id)

        if self.publication_date:
            parts.append(f"({self.publication_date.year})")

        if self.revision:
            parts.append(f"Rev. {self.revision}")

        return ", ".join(parts)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for storage."""
        return {
            "id": self.id,
            "doc_id": self.doc_id,
            "title": self.title,
            "authors": self.authors,
            "organization": self.organization,
            "publication_date": self.publication_date.isoformat() if self.publication_date else None,
            "revision": self.revision,
            "version": self.version,
            "doc_type": self.doc_type.value,
            "source": self.source.value,
            "sections": [s.to_dict() for s in self.sections],
            "total_pages": self.total_pages,
            "file_path": str(self.file_path) if self.file_path else None,
            "file_hash": self.file_hash,
            "file_size": self.file_size,
            "extraction_date": self.extraction_date.isoformat() if self.extraction_date else None,
            "extraction_quality": self.extraction_quality,
            "abstract": self.abstract,
            "keywords": self.keywords,
            "references": self.references,
            "supersedes": self.supersedes,
            "superseded_by": self.superseded_by,
            "extra": self.extra,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "DocumentMetadata":
        """Create from dictionary."""
        # Parse dates
        pub_date = None
        if data.get("publication_date"):
            pub_date = datetime.fromisoformat(data["publication_date"])

        extract_date = None
        if data.get("extraction_date"):
            extract_date = datetime.fromisoformat(data["extraction_date"])

        # Parse enums
        doc_type = DocumentType(data.get("doc_type", "other"))
        source = DocumentSource(data.get("source", "other"))

        # Parse sections recursively
        def parse_section(s: dict) -> Section:
            return Section(
                title=s.get("title", ""),
                level=s.get("level", 1),
                page_start=s.get("page_start"),
                page_end=s.get("page_end"),
                subsections=[parse_section(sub) for sub in s.get("subsections", [])],
            )

        sections = [parse_section(s) for s in data.get("sections", [])]

        return cls(
            id=data.get("id", ""),
            doc_id=data.get("doc_id", ""),
            title=data.get("title", ""),
            authors=data.get("authors", []),
            organization=data.get("organization", ""),
            publication_date=pub_date,
            revision=data.get("revision", ""),
            version=data.get("version", ""),
            doc_type=doc_type,
            source=source,
            sections=sections,
            total_pages=data.get("total_pages", 0),
            file_path=Path(data["file_path"]) if data.get("file_path") else None,
            file_hash=data.get("file_hash", ""),
            file_size=data.get("file_size", 0),
            extraction_date=extract_date,
            extraction_quality=data.get("extraction_quality", 1.0),
            abstract=data.get("abstract", ""),
            keywords=data.get("keywords", []),
            references=data.get("references", []),
            supersedes=data.get("supersedes"),
            superseded_by=data.get("superseded_by"),
            extra=data.get("extra", {}),
        )


class MetadataExtractor:
    """
    Extracts metadata from documents.

    Attempts to automatically detect document type and extract
    relevant metadata from the content.
    """

    # Patterns for document ID detection
    DOC_ID_PATTERNS = [
        # NUREGs
        (r"NUREG[/-]CR[/-](\d+)", DocumentType.NUREG_CR),
        (r"NUREG[/-](\d+)", DocumentType.NUREG),

        # DOE Reports
        (r"LA[/-](\d+)(?:[/-]MS)?", DocumentType.LA_REPORT),
        (r"ORNL[/-]TM[/-](\d+)", DocumentType.ORNL_REPORT),
        (r"SAND(\d+)[/-](\d+)", DocumentType.SAND_REPORT),
        (r"DOE[/-]STD[/-](\d+)", DocumentType.DOE_STD),

        # Standards
        (r"ANSI/ANS[/-](\d+\.\d+)[/-](\d{4})", DocumentType.ANSI_ANS),
        (r"ANS[/-](\d+\.\d+)[/-](\d{4})", DocumentType.ANSI_ANS),

        # Regulatory Guides
        (r"Regulatory Guide (\d+\.\d+)", DocumentType.REGULATORY_GUIDE),
        (r"RG (\d+\.\d+)", DocumentType.REGULATORY_GUIDE),
    ]

    def __init__(self):
        """Initialize the metadata extractor."""
        self._compiled_patterns = [
            (re.compile(pattern, re.IGNORECASE), doc_type)
            for pattern, doc_type in self.DOC_ID_PATTERNS
        ]

    def extract(
        self,
        content: str,
        file_path: Path | None = None,
        source: DocumentSource = DocumentSource.OTHER,
        existing_metadata: dict | None = None,
    ) -> DocumentMetadata:
        """
        Extract metadata from document content.

        Args:
            content: Document text content
            file_path: Path to the document file
            source: Known document source
            existing_metadata: Any existing metadata to incorporate

        Returns:
            DocumentMetadata object
        """
        existing_metadata = existing_metadata or {}

        # Detect document ID and type
        doc_id, doc_type = self._detect_document_id(content)
        if existing_metadata.get("doc_id"):
            doc_id = existing_metadata["doc_id"]
        if existing_metadata.get("doc_type"):
            doc_type = DocumentType(existing_metadata["doc_type"])

        # Extract title
        title = self._extract_title(content)
        if existing_metadata.get("title"):
            title = existing_metadata["title"]

        # Extract authors
        authors = self._extract_authors(content)
        if existing_metadata.get("authors"):
            authors = existing_metadata["authors"]

        # Extract date
        pub_date = self._extract_date(content)
        if existing_metadata.get("publication_date"):
            pub_date = datetime.fromisoformat(existing_metadata["publication_date"])

        # Extract sections
        sections = self._extract_sections(content)

        # Calculate file hash if path provided
        file_hash = ""
        file_size = 0
        if file_path and file_path.exists():
            import hashlib
            file_size = file_path.stat().st_size
            with open(file_path, "rb") as f:
                file_hash = hashlib.sha256(f.read()).hexdigest()

        # Extract references
        references = self._extract_references(content)

        return DocumentMetadata(
            doc_id=doc_id,
            title=title,
            authors=authors,
            publication_date=pub_date,
            doc_type=doc_type,
            source=source,
            sections=sections,
            file_path=file_path,
            file_hash=file_hash,
            file_size=file_size,
            references=references,
            extra=existing_metadata.get("extra", {}),
        )

    def _detect_document_id(self, content: str) -> tuple[str, DocumentType]:
        """Detect document ID from content."""
        # Check first few pages worth of content
        search_content = content[:5000]

        for pattern, doc_type in self._compiled_patterns:
            match = pattern.search(search_content)
            if match:
                doc_id = match.group(0)
                return doc_id, doc_type

        return "", DocumentType.OTHER

    def _extract_title(self, content: str) -> str:
        """Extract document title from content."""
        # Look for title in first part of document
        first_part = content[:2000]

        # Try to find title-like text (all caps or after "Title:")
        title_patterns = [
            r"^Title:\s*(.+)$",
            r"^TITLE:\s*(.+)$",
            r"^(.{10,100})$",  # First substantial line
        ]

        lines = first_part.split("\n")
        for line in lines[:20]:  # Check first 20 lines
            line = line.strip()
            if not line or len(line) < 10:
                continue

            for pattern in title_patterns:
                match = re.match(pattern, line, re.MULTILINE)
                if match:
                    return match.group(1).strip()

        return ""

    def _extract_authors(self, content: str) -> list[str]:
        """Extract author names from content."""
        authors = []

        # Look for author section
        author_patterns = [
            r"Author[s]?:\s*(.+?)(?:\n\n|\Z)",
            r"Prepared by[:]?\s*(.+?)(?:\n\n|\Z)",
            r"Written by[:]?\s*(.+?)(?:\n\n|\Z)",
        ]

        first_part = content[:3000]

        for pattern in author_patterns:
            match = re.search(pattern, first_part, re.IGNORECASE | re.DOTALL)
            if match:
                author_text = match.group(1)
                # Split by common separators
                potential_authors = re.split(r"[,;]|\band\b", author_text)
                for author in potential_authors:
                    author = author.strip()
                    # Basic validation: should look like a name
                    if author and len(author) < 100 and not author.isnumeric():
                        authors.append(author)
                break

        return authors

    def _extract_date(self, content: str) -> datetime | None:
        """Extract publication date from content."""
        first_part = content[:3000]

        # Common date patterns
        date_patterns = [
            (r"(\w+)\s+(\d{4})", "%B %Y"),  # January 2020
            (r"(\d{1,2})/(\d{1,2})/(\d{4})", "%m/%d/%Y"),  # 01/15/2020
            (r"(\d{4})-(\d{2})-(\d{2})", "%Y-%m-%d"),  # 2020-01-15
        ]

        for pattern, date_format in date_patterns:
            match = re.search(pattern, first_part)
            if match:
                try:
                    date_str = match.group(0)
                    return datetime.strptime(date_str, date_format)
                except ValueError:
                    continue

        return None

    def _extract_sections(self, content: str) -> list[Section]:
        """Extract document sections from content."""
        sections = []

        # Look for section patterns
        section_patterns = [
            (r"^(#{1,6})\s+(.+)$", lambda m: len(m.group(1))),  # Markdown
            (r"^(\d+(?:\.\d+)*)\s+(.+)$", lambda m: m.group(1).count(".") + 1),  # Numbered
            (r"^(CHAPTER|SECTION)\s+(\d+)[.:]\s*(.+)$", lambda m: 1),  # Chapter/Section
        ]

        for line in content.split("\n"):
            line = line.strip()

            for pattern, level_func in section_patterns:
                match = re.match(pattern, line, re.IGNORECASE)
                if match:
                    level = level_func(match)
                    title = match.group(2) if len(match.groups()) >= 2 else match.group(1)
                    sections.append(Section(title=title.strip(), level=level))
                    break

        return sections

    def _extract_references(self, content: str) -> list[str]:
        """Extract document references from content."""
        references = []

        # Look for references section
        ref_section_match = re.search(
            r"(?:REFERENCES|BIBLIOGRAPHY|CITATIONS)\s*\n(.*?)(?:\n\n\n|\Z)",
            content,
            re.IGNORECASE | re.DOTALL,
        )

        if ref_section_match:
            ref_text = ref_section_match.group(1)

            # Look for document IDs in references
            for pattern, _ in self._compiled_patterns:
                for match in pattern.finditer(ref_text):
                    ref_id = match.group(0)
                    if ref_id not in references:
                        references.append(ref_id)

        return references


def extract_metadata(
    content: str,
    **kwargs,
) -> DocumentMetadata:
    """
    Convenience function to extract metadata from document content.

    Args:
        content: Document text content
        **kwargs: Arguments passed to MetadataExtractor.extract()

    Returns:
        DocumentMetadata object
    """
    extractor = MetadataExtractor()
    return extractor.extract(content, **kwargs)
