"""
Smart YAML merging for sync operations.

This module provides intelligent merging of YAML files, similar to how
settings.json is merged. It preserves user customizations while updating
template sections.

The merge strategy uses section markers to identify template vs custom sections:
- Sections marked with `# persuade-managed` are overwritten from source
- Sections without markers are preserved from target (user customizations)
- New sections from source are added to target

This enables workflows and other YAML files to receive template updates
while preserving project-specific customizations.
"""

import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Final

from persuade.logging import get_logger

logger = get_logger()

# Marker comments for managed sections
MANAGED_MARKER: Final[str] = "# persuade-managed"
MANAGED_START_MARKER: Final[str] = "# persuade-managed-start"
MANAGED_END_MARKER: Final[str] = "# persuade-managed-end"


@dataclass
class YAMLMergeResult:
    """Result of a YAML merge operation."""

    merged_content: str
    """The merged YAML content."""

    sections_updated: list[str]
    """Names of sections that were updated from source."""

    sections_preserved: list[str]
    """Names of sections that were preserved from target."""

    sections_added: list[str]
    """Names of new sections added from source."""


def _load_yaml_safe(content: str) -> dict[str, Any]:
    """Load YAML content safely.

    Args:
        content: YAML content string

    Returns:
        Parsed YAML as dictionary

    Raises:
        ImportError: If PyYAML is not installed
        yaml.YAMLError: If content is not valid YAML
    """
    try:
        import yaml
    except ImportError as e:
        raise ImportError(
            "PyYAML is required for YAML merging. Install with: pip install pyyaml"
        ) from e

    result = yaml.safe_load(content)
    return result if isinstance(result, dict) else {}


def _dump_yaml_safe(data: dict[str, Any]) -> str:
    """Dump dictionary to YAML string.

    Args:
        data: Dictionary to serialize

    Returns:
        YAML string representation

    Raises:
        ImportError: If PyYAML is not installed
    """
    try:
        import yaml
    except ImportError as e:
        raise ImportError(
            "PyYAML is required for YAML merging. Install with: pip install pyyaml"
        ) from e

    result: str = yaml.dump(
        data,
        default_flow_style=False,
        sort_keys=False,
        allow_unicode=True,
        width=80,
    )
    return result


def is_section_managed(content: str, section_name: str) -> bool:
    """Check if a YAML section is marked as managed by persuade.

    Looks for the managed marker in comments before or on the same line as
    the section key.

    Args:
        content: Full YAML content
        section_name: Name of the section to check

    Returns:
        True if section is marked as managed
    """
    # Look for patterns like:
    # # persuade-managed
    # section_name:
    # OR
    # section_name:  # persuade-managed

    # Pattern 1: Marker on line before section
    pattern1 = rf"^\s*{re.escape(MANAGED_MARKER)}\s*\n\s*{re.escape(section_name)}:"
    # Pattern 2: Marker on same line as section
    pattern2 = rf"^\s*{re.escape(section_name)}:.*{re.escape(MANAGED_MARKER)}"

    return bool(re.search(pattern1, content, re.MULTILINE)) or bool(
        re.search(pattern2, content, re.MULTILINE)
    )


def extract_managed_blocks(content: str) -> dict[str, str]:
    """Extract content blocks marked with persuade-managed-start/end markers.

    Args:
        content: Full YAML content

    Returns:
        Dictionary mapping block names to their content
    """
    blocks: dict[str, str] = {}

    # Pattern to match managed blocks
    # # persuade-managed-start: block_name
    # ... content ...
    # # persuade-managed-end: block_name
    start = re.escape(MANAGED_START_MARKER)
    end = re.escape(MANAGED_END_MARKER)
    pattern = rf"{start}:\s*(\w+)\s*\n(.*?)\n\s*{end}:\s*\1"

    for match in re.finditer(pattern, content, re.DOTALL):
        block_name = match.group(1)
        block_content = match.group(2)
        blocks[block_name] = block_content

    return blocks


def merge_yaml_files(source_path: Path, target_path: Path) -> YAMLMergeResult:
    """Merge source YAML into target YAML, preserving customizations.

    The merge strategy:
    1. Sections marked with `# persuade-managed` are overwritten from source
    2. Sections in source but not target are added
    3. Sections in target but not source are preserved
    4. Unmarked sections in both are preserved from target

    Args:
        source_path: Path to source (template) YAML file
        target_path: Path to target (project) YAML file

    Returns:
        YAMLMergeResult with merged content and change details
    """
    source_content = source_path.read_text()
    target_content = target_path.read_text()

    return merge_yaml_content(source_content, target_content)


def merge_yaml_content(source_content: str, target_content: str) -> YAMLMergeResult:
    """Merge source YAML content into target YAML content.

    Args:
        source_content: Source (template) YAML content
        target_content: Target (project) YAML content

    Returns:
        YAMLMergeResult with merged content and change details
    """
    source_data = _load_yaml_safe(source_content)
    target_data = _load_yaml_safe(target_content)

    sections_updated: list[str] = []
    sections_preserved: list[str] = []
    sections_added: list[str] = []

    # Process each section
    merged_data: dict[str, Any] = {}

    # First, add all target sections (preserving order and customizations)
    for key, value in target_data.items():
        if key in source_data:
            # Section exists in both - check if managed
            if is_section_managed(target_content, key) or is_section_managed(source_content, key):
                # Managed section - use source value
                merged_data[key] = source_data[key]
                sections_updated.append(key)
                logger.debug(
                    "yaml_section_updated",
                    section=key,
                    reason="managed marker found",
                )
            else:
                # Not managed - preserve target value
                merged_data[key] = value
                sections_preserved.append(key)
                logger.debug(
                    "yaml_section_preserved",
                    section=key,
                    reason="no managed marker",
                )
        else:
            # Only in target - preserve
            merged_data[key] = value
            sections_preserved.append(key)
            logger.debug(
                "yaml_section_preserved",
                section=key,
                reason="not in source",
            )

    # Then add any new sections from source
    for key, value in source_data.items():
        if key not in merged_data:
            merged_data[key] = value
            sections_added.append(key)
            logger.debug(
                "yaml_section_added",
                section=key,
            )

    # Convert back to YAML
    merged_content = _dump_yaml_safe(merged_data)

    # TODO: Preserve managed block markers from source
    # Currently, yaml.dump() strips all comments, so managed block markers
    # (persuade-managed-start/end) are lost during the merge. A future enhancement
    # could use a comment-preserving YAML library (ruamel.yaml) or manually
    # reconstruct the comments after dumping.

    return YAMLMergeResult(
        merged_content=merged_content,
        sections_updated=sections_updated,
        sections_preserved=sections_preserved,
        sections_added=sections_added,
    )


def compute_yaml_diff(source_content: str, target_content: str) -> dict[str, Any]:
    """Compute a diff between source and target YAML content.

    Args:
        source_content: Source (template) YAML content
        target_content: Target (project) YAML content

    Returns:
        Dictionary with diff information:
        - sections_to_update: List of sections that would be updated
        - sections_to_add: List of sections that would be added
        - sections_unchanged: List of sections that would remain unchanged
    """
    source_data = _load_yaml_safe(source_content)
    target_data = _load_yaml_safe(target_content)

    sections_to_update: list[str] = []
    sections_to_add: list[str] = []
    sections_unchanged: list[str] = []

    # Check each source section
    for key in source_data:
        if key not in target_data:
            sections_to_add.append(key)
        elif is_section_managed(target_content, key) or is_section_managed(source_content, key):
            if source_data[key] != target_data[key]:
                sections_to_update.append(key)
            else:
                sections_unchanged.append(key)
        else:
            sections_unchanged.append(key)

    return {
        "sections_to_update": sections_to_update,
        "sections_to_add": sections_to_add,
        "sections_unchanged": sections_unchanged,
    }
