"""
Manifest manager for explode-implode operations.

Handles creation, parsing, and validation of manifest.md files that preserve
the structure and metadata needed for reversible operations.
"""

import yaml
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional
from dataclasses import dataclass, asdict

from .enums import ExplodeVariant, ManifestVersion


@dataclass
class StructureEntry:
    """Entry in the manifest structure describing a heading/content mapping."""

    type: str  # h1, h2, h3, etc.
    title: str
    path: str
    order: int
    parent: Optional[str] = None
    level: int = 1
    original_line: Optional[int] = None


@dataclass
class ManifestData:
    """Complete manifest data structure."""

    explosion_type: str
    original_file: str
    created: str
    markitect_version: str
    manifest_version: str = ManifestVersion.V1_0.value
    preservation: Optional[Dict[str, bool]] = None
    structure: Optional[List[StructureEntry]] = None
    metadata: Optional[Dict[str, Any]] = None


class ManifestManager:
    """
    Manages manifest.md files for explode-implode operations.

    The manifest system ensures complete reversibility by preserving:
    - Original file structure and ordering
    - Heading hierarchy and relationships
    - Metadata and configuration options
    - Variant-specific information
    """

    MANIFEST_FILENAME = "manifest.md"

    def __init__(self, markitect_version: str = "0.1.0"):
        """
        Initialize the manifest manager.

        Args:
            markitect_version: Version of MarkiTect creating the manifest
        """
        self.markitect_version = markitect_version

    def create_manifest(
        self,
        output_dir: Path,
        original_file: Path,
        variant: ExplodeVariant,
        structure: List[StructureEntry],
        preservation_options: Optional[Dict[str, bool]] = None,
        metadata: Optional[Dict[str, Any]] = None
    ) -> Path:
        """
        Create a manifest.md file in the output directory.

        Args:
            output_dir: Directory where manifest should be created
            original_file: Path to the original markdown file
            variant: Variant used for explosion
            structure: List of structure entries describing the explosion
            preservation_options: Options for what was preserved
            metadata: Additional metadata to include

        Returns:
            Path to the created manifest file

        Raises:
            PermissionError: If unable to write manifest file
            ValueError: If invalid data provided
        """
        if preservation_options is None:
            preservation_options = {
                "front_matter": True,
                "section_order": True,
                "heading_levels": True
            }

        manifest_data = ManifestData(
            explosion_type=variant.value,
            original_file=str(original_file.name),
            created=datetime.now().isoformat(),
            markitect_version=self.markitect_version,
            preservation=preservation_options,
            structure=structure,
            metadata=metadata or {}
        )

        manifest_path = output_dir / self.MANIFEST_FILENAME
        content = self._generate_manifest_content(manifest_data)

        try:
            manifest_path.write_text(content, encoding='utf-8')
        except Exception as e:
            raise PermissionError(f"Unable to write manifest file: {e}")

        return manifest_path

    def read_manifest(self, directory: Path) -> Optional[ManifestData]:
        """
        Read and parse a manifest.md file from a directory.

        Args:
            directory: Directory containing the manifest file

        Returns:
            Parsed manifest data, or None if no valid manifest found
        """
        manifest_path = directory / self.MANIFEST_FILENAME

        if not manifest_path.exists():
            return None

        try:
            content = manifest_path.read_text(encoding='utf-8')
            return self._parse_manifest_content(content)
        except Exception:
            # Return None for any parsing errors - let caller handle
            return None

    def validate_manifest(self, manifest_data: ManifestData) -> List[str]:
        """
        Validate manifest data for completeness and consistency.

        Args:
            manifest_data: Manifest data to validate

        Returns:
            List of validation errors (empty if valid)
        """
        errors = []

        # Required fields
        if not manifest_data.explosion_type:
            errors.append("Missing explosion_type")

        if not manifest_data.original_file:
            errors.append("Missing original_file")

        if not manifest_data.created:
            errors.append("Missing created timestamp")

        # Validate explosion type
        try:
            ExplodeVariant(manifest_data.explosion_type)
        except ValueError:
            errors.append(f"Invalid explosion_type: {manifest_data.explosion_type}")

        # Validate structure if present
        if manifest_data.structure:
            for i, entry in enumerate(manifest_data.structure):
                if not entry.type:
                    errors.append(f"Structure entry {i}: missing type")
                if not entry.title:
                    errors.append(f"Structure entry {i}: missing title")
                if not entry.path:
                    errors.append(f"Structure entry {i}: missing path")
                if entry.order < 0:
                    errors.append(f"Structure entry {i}: invalid order {entry.order}")

        return errors

    def update_manifest(
        self,
        directory: Path,
        updates: Dict[str, Any]
    ) -> bool:
        """
        Update an existing manifest with new data.

        Args:
            directory: Directory containing the manifest
            updates: Dictionary of updates to apply

        Returns:
            True if update successful, False otherwise
        """
        manifest_data = self.read_manifest(directory)
        if not manifest_data:
            return False

        try:
            # Apply updates
            for key, value in updates.items():
                if hasattr(manifest_data, key):
                    setattr(manifest_data, key, value)

            # Recreate manifest
            manifest_path = directory / self.MANIFEST_FILENAME
            content = self._generate_manifest_content(manifest_data)
            manifest_path.write_text(content, encoding='utf-8')

            return True
        except Exception:
            return False

    def _generate_manifest_content(self, manifest_data: ManifestData) -> str:
        """
        Generate the complete manifest.md content.

        Args:
            manifest_data: Manifest data to serialize

        Returns:
            Complete manifest file content
        """
        # Convert dataclasses to dictionaries for YAML serialization
        yaml_data = {}

        # Basic metadata
        yaml_data['explosion_type'] = manifest_data.explosion_type
        yaml_data['original_file'] = manifest_data.original_file
        yaml_data['created'] = manifest_data.created
        yaml_data['markitect_version'] = manifest_data.markitect_version
        yaml_data['manifest_version'] = manifest_data.manifest_version

        # Optional sections
        if manifest_data.preservation:
            yaml_data['preservation'] = manifest_data.preservation

        if manifest_data.structure:
            yaml_data['structure'] = [
                {
                    'type': entry.type,
                    'title': entry.title,
                    'path': entry.path,
                    'order': entry.order,
                    'parent': entry.parent,
                    'level': entry.level,
                    'original_line': entry.original_line
                }
                for entry in manifest_data.structure
            ]

        if manifest_data.metadata:
            yaml_data['metadata'] = manifest_data.metadata

        # Generate YAML front matter
        yaml_content = yaml.dump(yaml_data, default_flow_style=False, sort_keys=False)

        # Generate complete manifest
        content = f"""---
{yaml_content}---

# Explosion Manifest

This directory was created by exploding `{manifest_data.original_file}` using the **{manifest_data.explosion_type}** structure variant.

## Structure Overview

The original markdown file has been exploded into a directory structure that preserves all content and structural information. This manifest file ensures the explosion is completely reversible.

## Reconstruction

To reconstruct the original file, use:

```bash
markitect md-implode {Path('.').name}/
```

The implode operation will automatically detect the variant type from this manifest and reconstruct the original structure.

## Preservation Details

{self._generate_preservation_details(manifest_data.preservation or {})}

---
*Generated by MarkiTect {manifest_data.markitect_version} on {manifest_data.created}*
"""
        return content

    def _parse_manifest_content(self, content: str) -> ManifestData:
        """
        Parse manifest content into structured data.

        Args:
            content: Raw manifest file content

        Returns:
            Parsed manifest data

        Raises:
            ValueError: If content cannot be parsed
        """
        try:
            # Extract YAML front matter
            if not content.startswith('---'):
                raise ValueError("Manifest does not start with YAML front matter")

            # Find the end of front matter
            lines = content.split('\n')
            yaml_end = -1
            for i, line in enumerate(lines[1:], 1):
                if line.strip() == '---':
                    yaml_end = i
                    break

            if yaml_end == -1:
                raise ValueError("YAML front matter not properly closed")

            # Parse YAML
            yaml_content = '\n'.join(lines[1:yaml_end])
            yaml_data = yaml.safe_load(yaml_content)

            # Convert structure entries
            structure = None
            if 'structure' in yaml_data and yaml_data['structure']:
                structure = [
                    StructureEntry(
                        type=entry['type'],
                        title=entry['title'],
                        path=entry['path'],
                        order=entry['order'],
                        parent=entry.get('parent'),
                        level=entry.get('level', 1),
                        original_line=entry.get('original_line')
                    )
                    for entry in yaml_data['structure']
                ]

            return ManifestData(
                explosion_type=yaml_data['explosion_type'],
                original_file=yaml_data['original_file'],
                created=yaml_data['created'],
                markitect_version=yaml_data['markitect_version'],
                manifest_version=yaml_data.get('manifest_version', ManifestVersion.V1_0.value),
                preservation=yaml_data.get('preservation'),
                structure=structure,
                metadata=yaml_data.get('metadata')
            )

        except Exception as e:
            raise ValueError(f"Error parsing manifest content: {e}")

    def _generate_preservation_details(self, preservation: Dict[str, bool]) -> str:
        """Generate human-readable preservation details."""
        if not preservation:
            return "No specific preservation options recorded."

        details = []
        for option, enabled in preservation.items():
            status = "✅ Preserved" if enabled else "❌ Not preserved"
            option_name = option.replace('_', ' ').title()
            details.append(f"- **{option_name}**: {status}")

        return '\n'.join(details)