"""
Context map generation command.

Orchestrates the generation of Ontos_Context_Map.md using
extracted core and io modules.

Phase 2 Decomposition - Created from Phase2-Implementation-Spec.md Section 4.10
"""

from __future__ import annotations
import json
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any, Tuple

from ontos.core.validation import ValidationOrchestrator
from ontos.core.tokens import estimate_tokens, format_token_count
from ontos.core.types import DocumentData, ValidationResult


@dataclass
class GenerateMapOptions:
    """Configuration options for context map generation."""
    output_path: Optional[Path] = None
    strict: bool = False
    include_staleness: bool = True
    include_timeline: bool = True
    include_lint: bool = False
    max_dependency_depth: int = 5
    dry_run: bool = False
    json_output: bool = False  # Phase 4: JSON output support
    quiet: bool = False  # Phase 4: Quiet mode


def generate_context_map(
    docs: Dict[str, DocumentData],
    config: Dict[str, Any],
    options: GenerateMapOptions = None
) -> Tuple[str, ValidationResult]:
    """Generate the context map markdown content.

    Args:
        docs: Dictionary of parsed documents
        config: Configuration dictionary
        options: Generation options

    Returns:
        Tuple of (content string, validation result)
    """
    options = options or GenerateMapOptions()

    # Run validation
    validator = ValidationOrchestrator(docs, {
        "max_dependency_depth": options.max_dependency_depth,
        "allowed_orphan_types": config.get("allowed_orphan_types", ["atom", "log"]),
    })
    result = validator.validate_all()

    # Generate content sections
    sections = []

    # Header with provenance
    sections.append(_generate_header(config))

    # Document table
    sections.append(_generate_document_table(docs))

    # Validation messages (if any)
    if result.errors or result.warnings:
        sections.append(_generate_validation_section(result))

    # Dependency tree
    sections.append(_generate_dependency_tree(docs))

    # Timeline (if enabled)
    if options.include_timeline:
        sections.append(_generate_timeline(docs))

    # Staleness section (if enabled)
    if options.include_staleness:
        staleness_section = _generate_staleness_section(docs)
        if staleness_section:
            sections.append(staleness_section)

    # Lint section (if enabled)
    if options.include_lint:
        lint_section = _generate_lint_section(docs, result)
        if lint_section:
            sections.append(lint_section)

    # Assemble content
    content = "\n\n".join(filter(None, sections))

    return content, result


def _generate_header(config: Dict[str, Any]) -> str:
    """Generate context map header with provenance and frontmatter."""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    project_name = config.get("project_name", "Project")
    
    version = config.get("version", "unknown")
    
    # v3.0.1: Add YAML frontmatter for self-reference
    frontmatter = f"""---
id: ontos_context_map
type: reference
status: generated
generated_by: ontos map
generated_at: {timestamp}
---"""
    
    return f"""{frontmatter}

# {project_name} Context Map

> Auto-generated by Ontos {version}
> Last updated: {timestamp}

This document provides a navigable index of all tracked documents in the knowledge graph."""


def _escape_markdown_table_cell(value: str) -> str:
    """Escape special characters for markdown table cells.

    Escapes pipe characters and backslashes that would break table formatting.
    """
    if not value:
        return value
    # Escape backslashes first, then pipes
    return value.replace("\\", "\\\\").replace("|", "\\|")


def _generate_document_table(docs: Dict[str, DocumentData]) -> str:
    """Generate document listing table."""
    if not docs:
        return "## Documents\n\nNo documents found."

    lines = [
        "## Documents",
        "",
        "| Path | ID | Type | Status |",
        "|------|-----|------|--------|",
    ]

    # Sort docs by path
    sorted_docs = sorted(docs.values(), key=lambda d: str(d.filepath))

    for doc in sorted_docs:
        doc_type = doc.type.value if hasattr(doc.type, 'value') else str(doc.type)
        doc_status = doc.status.value if hasattr(doc.status, 'value') else str(doc.status)
        # Escape special characters to prevent table breakage
        filepath = _escape_markdown_table_cell(str(doc.filepath))
        doc_id = _escape_markdown_table_cell(doc.id)
        lines.append(f"| {filepath} | {doc_id} | {doc_type} | {doc_status} |")

    return "\n".join(lines)


def _generate_validation_section(result: ValidationResult) -> str:
    """Generate validation messages section."""
    lines = ["## Validation"]
    
    if result.errors:
        lines.append("")
        lines.append("### Errors")
        for error in result.errors:
            lines.append(f"- ❌ **{error.doc_id}**: {error.message}")
    
    if result.warnings:
        lines.append("")
        lines.append("### Warnings")
        for warning in result.warnings:
            lines.append(f"- ⚠️ **{warning.doc_id}**: {warning.message}")
    
    return "\n".join(lines)


def _generate_dependency_tree(docs: Dict[str, DocumentData]) -> str:
    """Generate dependency tree visualization."""
    lines = ["## Dependency Tree"]
    
    if not docs:
        lines.append("\nNo dependencies to display.")
        return "\n".join(lines)
    
    # Find root nodes (no dependents)
    all_deps = set()
    for doc in docs.values():
        all_deps.update(doc.depends_on)
    
    roots = [doc_id for doc_id in docs if doc_id not in all_deps]
    
    if not roots:
        lines.append("\nAll documents have dependencies (possible cycle).")
        return "\n".join(lines)
    
    lines.append("")
    for root in sorted(roots)[:10]:  # Limit for readability
        lines.append(f"- **{root}**")
        if root in docs:
            for dep in docs[root].depends_on[:5]:
                lines.append(f"  - {dep}")
    
    return "\n".join(lines)


def _generate_timeline(docs: Dict[str, DocumentData]) -> str:
    """Generate activity timeline."""
    lines = ["## Recent Activity"]
    
    # Filter to logs only
    logs = [doc for doc in docs.values() 
            if (doc.type.value if hasattr(doc.type, 'value') else str(doc.type)) == "log"]
    
    if not logs:
        lines.append("\nNo session logs found.")
        return "\n".join(lines)
    
    # Sort by date (extract from ID if possible)
    sorted_logs = sorted(logs, key=lambda d: d.id, reverse=True)[:10]
    
    lines.append("")
    for log in sorted_logs:
        lines.append(f"- `{log.id}`")
    
    return "\n".join(lines)


def _generate_staleness_section(docs: Dict[str, DocumentData]) -> Optional[str]:
    """Generate staleness detection section."""
    # Find documents with describes field that might be stale
    stale_candidates = []
    
    for doc in docs.values():
        # Check if doc has describes field in frontmatter
        describes = doc.frontmatter.get("describes", [])
        if describes:
            stale_candidates.append(doc)
    
    if not stale_candidates:
        return None
    
    lines = ["## Staleness Check"]
    lines.append("")
    lines.append(f"Found {len(stale_candidates)} documents with `describes` field:")
    lines.append("")
    
    for doc in stale_candidates[:10]:  # Limit display
        describes = doc.frontmatter.get("describes", [])
        verified = doc.frontmatter.get("describes_verified", "never")
        lines.append(f"- **{doc.id}**: describes {describes}, verified: {verified}")
    
    return "\n".join(lines)


def _generate_lint_section(docs: Dict[str, DocumentData], result: ValidationResult) -> Optional[str]:
    """Generate lint warnings section."""
    lint_issues = []

    for doc in docs.values():
        doc_type = doc.type.value if hasattr(doc.type, 'value') else str(doc.type)

        # Check for empty impacts on logs
        if doc_type == "log":
            impacts = doc.frontmatter.get("impacts", [])
            if not impacts:
                lint_issues.append(f"**{doc.id}**: Empty impacts on log")

        # Check for too many concepts
        concepts = doc.frontmatter.get("concepts", [])
        if len(concepts) > 6:
            lint_issues.append(f"**{doc.id}**: {len(concepts)} concepts (recommended: 2-4)")

    if not lint_issues and not result.warnings:
        return None

    lines = ["## Lint Warnings"]
    lines.append("")

    for issue in lint_issues[:20]:  # Limit display
        lines.append(f"- ⚠️ {issue}")

    return "\n".join(lines)


@dataclass
class MapOptions:
    """CLI-level options for map command."""
    output: Optional[Path] = None
    strict: bool = False
    json_output: bool = False
    quiet: bool = False


def map_command(options: MapOptions) -> int:
    """Execute map command from CLI.

    Orchestrates document scanning, loading, map generation, and file writing.

    Args:
        options: CLI-level map options

    Returns:
        Exit code (0 for success, 1 for errors, 2 for warnings in strict mode)
    """
    from ontos.io.files import find_project_root, scan_documents, load_document
    from ontos.io.config import load_project_config
    from ontos.io.yaml import parse_frontmatter_content

    # Find project root
    try:
        project_root = find_project_root()
    except FileNotFoundError as e:
        if options.json_output:
            print(json.dumps({"status": "error", "message": str(e)}))
        elif not options.quiet:
            print(f"Error: {e}")
        return 1

    # Load config
    try:
        config = load_project_config(repo_root=project_root)
    except Exception as e:
        if options.json_output:
            print(json.dumps({"status": "error", "message": f"Config error: {e}"}))
        elif not options.quiet:
            print(f"Config error: {e}")
        return 1

    # Determine paths
    docs_dir = project_root / config.paths.docs_dir
    output_path = options.output or (project_root / config.paths.context_map)

    # Scan for documents
    doc_paths = scan_documents(
        [docs_dir, project_root],
        skip_patterns=config.scanning.skip_patterns
    )

    # Load documents
    docs: Dict[str, DocumentData] = {}
    for path in doc_paths:
        try:
            doc = load_document(path, parse_frontmatter_content)
            docs[doc.id] = doc
        except Exception as e:
            if not options.quiet:
                print(f"Warning: Failed to load {path}: {e}")

    # Build config dict for generation
    gen_config = {
        "project_name": project_root.name,
        "version": config.ontos.version,
        "allowed_orphan_types": config.validation.allowed_orphan_types,
    }

    # Generate context map
    gen_options = GenerateMapOptions(
        output_path=output_path,
        strict=options.strict,
        max_dependency_depth=config.validation.max_dependency_depth,
    )

    content, result = generate_context_map(docs, gen_config, gen_options)

    # Write output
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(content, encoding="utf-8")

    # Determine exit code
    exit_code = 0
    if result.errors:
        exit_code = 1
    elif options.strict and result.warnings:
        exit_code = 2

    # Output result
    if options.json_output:
        print(json.dumps({
            "status": "success" if exit_code == 0 else "error",
            "path": str(output_path),
            "documents": len(docs),
            "errors": len(result.errors),
            "warnings": len(result.warnings),
        }))
    elif not options.quiet:
        print(f"Context map generated: {output_path}")
        print(f"  Documents: {len(docs)}")
        if result.errors:
            print(f"  Errors: {len(result.errors)}")
        if result.warnings:
            print(f"  Warnings: {len(result.warnings)}")

    return exit_code

