"""Service for rendering and caching user documentation.

Follows the R9 BaseService pattern. Provides web-accessible documentation
from markdown files in docs/user_guides/.
"""
from __future__ import annotations

import logging
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional

from code.web.services.base import CachedService, NotFoundError, ServiceError

logger = logging.getLogger(__name__)

# Try importing markdown with extensions
try:
    import markdown
    from markdown.extensions.fenced_code import FencedCodeExtension
    from markdown.extensions.tables import TableExtension
    from markdown.extensions.toc import TocExtension
    MARKDOWN_AVAILABLE = True
except ImportError:
    MARKDOWN_AVAILABLE = False
    logger.warning("markdown library not available - docs service will be limited")


@dataclass
class GuideMetadata:
    """Metadata about a documentation guide."""
    name: str
    title: str
    description: Optional[str]
    file_path: Path


@dataclass
class GuideContent:
    """Rendered guide content with table of contents."""
    html: str
    toc_html: str  # Table of contents HTML


class DocsService(CachedService[str, GuideContent]):
    """Service for rendering user documentation from markdown files.
    
    Provides caching with TTL to avoid re-rendering unchanged files.
    Supports GitHub-flavored markdown with syntax highlighting.
    """
    
    def __init__(
        self,
        docs_dir: Optional[Path] = None,
        ttl_seconds: int = 300,  # 5 minute cache
        max_size: int = 50
    ) -> None:
        """Initialize docs service.
        
        Args:
            docs_dir: Path to documentation directory (defaults to docs/user_guides)
            ttl_seconds: Cache TTL in seconds
            max_size: Maximum number of cached documents
        """
        super().__init__(ttl_seconds=ttl_seconds, max_size=max_size)
        
        if docs_dir is None:
            # Default to docs/user_guides relative to project root
            docs_dir = Path(__file__).parent.parent.parent.parent / "docs" / "user_guides"
        
        self.docs_dir = docs_dir
        self._validate()
    
    def _validate(self) -> None:
        """Validate service configuration.
        
        Raises:
            ServiceError: If docs directory doesn't exist or markdown unavailable
        """
        if not self.docs_dir.exists():
            raise ServiceError(f"Documentation directory not found: {self.docs_dir}")
        
        if not MARKDOWN_AVAILABLE:
            logger.warning("Markdown library not available - install with: pip install markdown")
    
    def _compute_value(self, guide_name: str) -> GuideContent:
        """Render markdown guide to HTML with table of contents.
        
        Args:
            guide_name: Name of guide (without .md extension)
            
        Returns:
            GuideContent with rendered HTML and TOC
            
        Raises:
            NotFoundError: If guide doesn't exist
            ServiceError: If rendering fails
        """
        file_path = self.docs_dir / f"{guide_name}.md"
        
        if not file_path.exists():
            raise NotFoundError(f"Guide not found: {guide_name}")
        
        try:
            # Read markdown content
            content = file_path.read_text(encoding="utf-8")
            
            # Render to HTML with GitHub-flavored extensions
            if MARKDOWN_AVAILABLE:
                md = markdown.Markdown(extensions=[
                    'fenced_code',
                    'tables',
                    'toc',
                    'nl2br',  # Convert newlines to <br>
                    'sane_lists',  # Better list handling
                ])
                html = md.convert(content)
                
                # Rewrite relative .md links to /help/ routes
                # e.g. href="random_build.md" -> href="/help/random_build"
                html = re.sub(
                    r'href="([^":/]+)\.md(?:#([^"]*))?"',
                    lambda m: f'href="/help/{m.group(1)}"' + (f'#{m.group(2)}' if m.group(2) else ''),
                    html
                )
                
                # Strip the first <h1>, optional description <p>, and optional <hr> that follows —
                # the guide template renders both from metadata separately
                html = re.sub(r'^\s*<h1[^>]*>.*?</h1>\s*(?:<p>.*?</p>\s*)?(?:<hr\s*/?>)?\s*', '', html, count=1, flags=re.DOTALL)
                
                # Extract table of contents (generated by toc extension)
                toc_html = getattr(md, 'toc', '')
            else:
                # Fallback: wrap in <pre> tags (shouldn't happen in prod)
                html = f"<pre>{content}</pre>"
                toc_html = ""
                logger.warning(f"Markdown rendering unavailable for {guide_name}")
            
            return GuideContent(html=html, toc_html=toc_html)
            
        except Exception as e:
            logger.error(f"Failed to render guide {guide_name}: {e}")
            raise ServiceError(f"Failed to render guide: {e}")
    
    def get_guide(self, guide_name: str, force_reload: bool = False) -> GuideContent:
        """Get rendered HTML and TOC for a guide.
        
        Args:
            guide_name: Name of guide (without .md extension)
            force_reload: Force re-render even if cached
            
        Returns:
            GuideContent with HTML and TOC
            
        Raises:
            NotFoundError: If guide doesn't exist
            ServiceError: If rendering fails
        """
        return self.get(guide_name, force_recompute=force_reload)
    
    def list_guides(self) -> List[GuideMetadata]:
        """List all available documentation guides.
        
        Returns:
            List of guide metadata sorted by logical workflow order
        """
        # Define logical ordering for deckbuilding workflow
        guide_order = [
            "build_wizard",
            "theme_browser",
            "partner_mechanics",
            "budget_mode",
            "bracket_compliance",
            "owned_cards",
            "include_exclude",
            "multi_copy",
            "land_bases",
            "quick_build_skip_controls",
            "locks_replace_permalinks",
            "random_build",
            "batch_build_compare",
        ]
        
        guides = []
        
        for path in sorted(self.docs_dir.glob("*.md")):
            name = path.stem
            metadata = self._extract_metadata(path)
            guides.append(GuideMetadata(
                name=name,
                title=metadata.get("title", self._title_from_name(name)),
                description=metadata.get("description"),
                file_path=path
            ))
        
        # Sort by defined order (unrecognized guides at end, alphabetically)
        def sort_key(g: GuideMetadata) -> tuple:
            try:
                return (0, guide_order.index(g.name))
            except ValueError:
                return (1, g.title.lower())
        
        return sorted(guides, key=sort_key)
    
    def get_metadata(self, guide_name: str) -> GuideMetadata:
        """Get metadata for a specific guide.
        
        Args:
            guide_name: Name of guide (without .md extension)
            
        Returns:
            Guide metadata
            
        Raises:
            NotFoundError: If guide doesn't exist
        """
        file_path = self.docs_dir / f"{guide_name}.md"
        
        if not file_path.exists():
            raise NotFoundError(f"Guide not found: {guide_name}")
        
        metadata = self._extract_metadata(file_path)
        
        return GuideMetadata(
            name=guide_name,
            title=metadata.get("title", self._title_from_name(guide_name)),
            description=metadata.get("description"),
            file_path=file_path
        )
    
    def _extract_metadata(self, file_path: Path) -> Dict[str, str]:
        """Extract metadata from markdown file.
        
        Looks for:
        - Title: First H1 heading (# Title)
        - Description: First paragraph after title
        
        Args:
            file_path: Path to markdown file
            
        Returns:
            Metadata dictionary
        """
        try:
            content = file_path.read_text(encoding="utf-8")
            metadata = {}
            
            # Extract title from first H1
            title_match = re.search(r'^#\s+(.+?)$', content, re.MULTILINE)
            if title_match:
                metadata["title"] = title_match.group(1).strip()
            
            # Extract description from first paragraph after title
            # Look for non-empty line after title that's not a heading
            lines = content.split('\n')
            found_title = False
            for line in lines:
                line = line.strip()
                if not found_title and line.startswith('# '):
                    found_title = True
                    continue
                if found_title and line and not line.startswith('#'):
                    metadata["description"] = line[:200]  # Limit description length
                    break
            
            return metadata
            
        except Exception as e:
            logger.warning(f"Failed to extract metadata from {file_path}: {e}")
            return {}
    
    @staticmethod
    def _title_from_name(name: str) -> str:
        """Convert file name to readable title.
        
        Args:
            name: File name (without extension)
            
        Returns:
            Human-readable title
        """
        # Replace underscores with spaces and title case
        return name.replace('_', ' ').title()
    
    def invalidate_guide(self, guide_name: Optional[str] = None) -> None:
        """Invalidate cached guide(s).
        
        Args:
            guide_name: Guide to invalidate (None = invalidate all)
        """
        self.invalidate(guide_name)
