"""TokenMoulds MCP Server - Full Pipeline Document Generation.

Provides Model Context Protocol server for AI-powered Office document generation.
All documents are generated through the complete TokenMoulds pipeline with:
- Font embedding (always enabled)
- ISO 29500 / ODF compliance
- WCAG accessibility
- Full style coverage (276 Word styles, etc.)
- Template caching and persistence

No bundled fallbacks - every document flows from TokenMoulds-generated templates.
"""

from __future__ import annotations

import asyncio
import json
import subprocess
import os
from pathlib import Path
from typing import Any, Dict, List, Optional

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

from tokenmoulds.mcp.cache import TemplateCacheManager
from tokenmoulds.mcp.tools.user_context import (
    infer_brand_from_email,
    set_user_context,
    get_user_context,
    INFER_BRAND_SCHEMA,
    SET_USER_CONTEXT_SCHEMA,
    GET_USER_CONTEXT_SCHEMA,
)

# Brand management tools
from tokenmoulds.mcp.tools.brand_manager import (
    extract_brand_from_url,
    build_recipe_from_inputs,
    load_brand,
    list_brands,
    set_active_brand,
    EXTRACT_BRAND_SCHEMA,
    BUILD_RECIPE_SCHEMA,
    LOAD_BRAND_SCHEMA,
    LIST_BRANDS_SCHEMA,
    SET_ACTIVE_BRAND_SCHEMA,
)

# Template generation tools
from tokenmoulds.mcp.tools.template_generator import (
    generate_word_template,
    generate_powerpoint_template,
    generate_excel_template,
    generate_writer_template,
    generate_impress_template,
    generate_google_docs_template,
    generate_theme,
    generate_all_templates,
    GENERATE_WORD_TEMPLATE_SCHEMA,
    GENERATE_POWERPOINT_TEMPLATE_SCHEMA,
    GENERATE_EXCEL_TEMPLATE_SCHEMA,
    GENERATE_WRITER_TEMPLATE_SCHEMA,
    GENERATE_IMPRESS_TEMPLATE_SCHEMA,
    GENERATE_GOOGLE_DOCS_TEMPLATE_SCHEMA,
    GENERATE_THEME_SCHEMA,
    GENERATE_ALL_TEMPLATES_SCHEMA,
)

# Document creation tools (full pipeline - no bundled fallbacks)
from tokenmoulds.mcp.tools.document_creator import (
    create_word_document,
    create_powerpoint_document,
    create_excel_document,
    create_writer_document,
    create_impress_document,
    CREATE_WORD_DOCUMENT_SCHEMA,
    CREATE_POWERPOINT_DOCUMENT_SCHEMA,
    CREATE_EXCEL_DOCUMENT_SCHEMA,
    CREATE_WRITER_DOCUMENT_SCHEMA,
    CREATE_IMPRESS_DOCUMENT_SCHEMA,
)

# Add-in and macro tools
from tokenmoulds.mcp.tools.addin_signer import (
    create_vba_addin,
    create_web_addin,
    generate_self_signed_certificate,
    list_certificates,
    CREATE_VBA_ADDIN_SCHEMA,
    CREATE_WEB_ADDIN_SCHEMA,
    GENERATE_CERTIFICATE_SCHEMA,
    LIST_CERTIFICATES_SCHEMA,
)

# Validation
from tokenmoulds.mcp.validation import validate_template as do_validate_template


# Path to vendored fonts (None — must be configured explicitly)
VENDORED_FONTS_PATH: Path | None = None


class TokenMouldsServer:
    """MCP Server for TokenMoulds document generation.

    Full pipeline server with:
    - Brand extraction from URL (CSS → Design Tokens)
    - Recipe building from minimal inputs
    - Template generation for all formats (Word, PowerPoint, Excel, LibreOffice, Google Docs, Themes)
    - Document creation with content injection
    - Template caching and persistence
    - ISO 29500 / ODF validation
    - Font embedding (always enabled)
    """

    def __init__(
        self,
        tokens_path: Optional[Path] = None,
        brands_path: Optional[Path] = None,
        output_path: Path = Path("./generated"),
        cache_path: Optional[Path] = None,
        fonts_dir: Optional[Path] = None,
    ) -> None:
        """Initialize the TokenMoulds MCP server.

        Args:
            tokens_path: Path to design tokens directory
            brands_path: Path to multi-brand directory (alternative to tokens_path)
            output_path: Directory for generated documents
            cache_path: Directory for template cache (default: ~/.tokenmoulds/cache)
            fonts_dir: Directory for vendored fonts
        """
        self.tokens_path = tokens_path
        self.brands_path = brands_path
        self.output_path = Path(output_path)
        self.output_path.mkdir(parents=True, exist_ok=True)

        # Font directory
        self.fonts_dir = fonts_dir or VENDORED_FONTS_PATH

        # Template cache manager
        self.cache_manager = TemplateCacheManager(cache_path)

        # Active brand configuration
        self.active_brand: Optional[str] = None
        self.loaded_tokens: Dict[str, Any] = {}

        # User context (email, domain, inferred brand)
        self.user_context: Optional[Dict[str, Any]] = None

        # Auto-detect user from environment
        self._auto_detected_email: Optional[str] = None
        self._auto_init_task: Optional[asyncio.Task] = None

        # Load initial tokens if provided
        if tokens_path and tokens_path.exists():
            self._load_tokens_from_directory(tokens_path, "default")

        # Load brands if multi-brand directory provided
        if brands_path and brands_path.exists():
            self._load_brands_from_directory(brands_path)

        # Create MCP server
        self.server = Server("tokenmoulds")
        self._register_tools()
        self._register_handlers()

    def _load_tokens_from_directory(self, path: Path, brand_name: str) -> None:
        """Load tokens from a directory."""
        # Look for .tokens.json files (DTCG format)
        token_files = list(path.glob("*.tokens.json"))
        if token_files:
            token_file = token_files[0]
            with open(token_file, "r", encoding="utf-8") as f:
                self.loaded_tokens[brand_name] = json.load(f)
            if self.active_brand is None:
                self.active_brand = brand_name
            return

        # Fall back to legacy JSON
        json_files = list(path.glob("*-design-tokens.json"))
        if json_files:
            token_file = json_files[0]
            with open(token_file, "r", encoding="utf-8") as f:
                self.loaded_tokens[brand_name] = json.load(f)
            if self.active_brand is None:
                self.active_brand = brand_name

    def _load_brands_from_directory(self, path: Path) -> None:
        """Load multiple brands from a directory structure."""
        for brand_dir in path.iterdir():
            if brand_dir.is_dir():
                self._load_tokens_from_directory(brand_dir, brand_dir.name)

    def _detect_user_email(self) -> Optional[str]:
        """Detect user email from environment.

        Checks in order:
        1. Git global config (git config --global user.email)
        2. Git local config (git config user.email)
        3. Environment variables (EMAIL, GIT_AUTHOR_EMAIL)

        Returns:
            Email address if found, None otherwise
        """
        # Try git global config first
        try:
            result = subprocess.run(
                ["git", "config", "--global", "user.email"],
                capture_output=True,
                text=True,
                timeout=2,
            )
            if result.returncode == 0 and result.stdout.strip():
                return result.stdout.strip()
        except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
            pass

        # Try git local config
        try:
            result = subprocess.run(
                ["git", "config", "user.email"],
                capture_output=True,
                text=True,
                timeout=2,
            )
            if result.returncode == 0 and result.stdout.strip():
                return result.stdout.strip()
        except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
            pass

        # Try environment variables
        for var in ("EMAIL", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_EMAIL"):
            email = os.environ.get(var)
            if email and "@" in email:
                return email

        return None

    async def _auto_initialize_brand(self) -> None:
        """Auto-initialize brand from detected user email.

        Runs in background during server startup. Non-blocking.
        """
        email = self._detect_user_email()
        if not email:
            return

        self._auto_detected_email = email

        # Import here to avoid circular imports
        from tokenmoulds.mcp.tools.user_context import (
            extract_domain_from_email,
            extract_brand_name_from_domain,
        )

        domain = extract_domain_from_email(email)
        if not domain:
            return

        brand_name = extract_brand_name_from_domain(domain)

        # Set initial user context
        self.user_context = {
            "email": email,
            "domain": domain,
            "organization": brand_name,
            "brand_name": brand_name,
            "auto_detected": True,
        }

        # Try to infer brand from website (this may fail for some domains)
        try:
            result = await infer_brand_from_email({"domain": domain}, self)
            if result.get("success"):
                self.user_context["brand_loaded"] = True
                self.user_context["brand_source"] = "website"
        except Exception:
            # Brand inference failed - that's OK, user can manually set up
            self.user_context["brand_loaded"] = False
            self.user_context["brand_source"] = None

    def _register_tools(self) -> None:
        """Register all MCP tools."""

        @self.server.list_tools()
        async def list_tools() -> List[Tool]:
            """List all available tools."""
            return [
                # ============================================================
                # USER CONTEXT TOOLS
                # ============================================================
                Tool(
                    name="set_user_context",
                    description="Set user context from email. Automatically extracts domain, infers brand name, and fetches brand from organization's website CSS.",
                    inputSchema=SET_USER_CONTEXT_SCHEMA,
                ),
                Tool(
                    name="infer_brand_from_email",
                    description="Infer brand by fetching website CSS from email domain. user@democorp.com → https://democorp.com → extract colors/fonts → brand tokens ready.",
                    inputSchema=INFER_BRAND_SCHEMA,
                ),
                Tool(
                    name="get_user_context",
                    description="Get current user context including email, domain, and active brand.",
                    inputSchema=GET_USER_CONTEXT_SCHEMA,
                ),
                # ============================================================
                # BRAND MANAGEMENT TOOLS
                # ============================================================
                Tool(
                    name="extract_brand_from_url",
                    description="Extract brand colors, fonts, and styling from a website URL. Generates complete DTCG design tokens with derivation formulas. Full pipeline: URL → CSS → Brand → Recipe → Tokens.",
                    inputSchema=EXTRACT_BRAND_SCHEMA,
                ),
                Tool(
                    name="build_recipe_from_inputs",
                    description="Build DTCG design tokens from minimal inputs (colors, fonts, parameters). Creates a complete token system with formulas, page layouts, and 100+ paragraph/character styles.",
                    inputSchema=BUILD_RECIPE_SCHEMA,
                ),
                Tool(
                    name="load_brand",
                    description="Load existing design tokens from a directory. Supports DTCG (*.tokens.json) and legacy (*-design-tokens.json) formats.",
                    inputSchema=LOAD_BRAND_SCHEMA,
                ),
                Tool(
                    name="list_brands",
                    description="List all loaded and cached brands with their status and available templates.",
                    inputSchema=LIST_BRANDS_SCHEMA,
                ),
                Tool(
                    name="set_active_brand",
                    description="Set the active brand for template and document generation.",
                    inputSchema=SET_ACTIVE_BRAND_SCHEMA,
                ),
                # ============================================================
                # TEMPLATE GENERATION TOOLS
                # ============================================================
                Tool(
                    name="generate_word_template",
                    description="Generate Word template (.dotx) with 276 themed styles. Full TokenMoulds pipeline with font embedding, ISO 29500 compliance, zero Office defaults.",
                    inputSchema=GENERATE_WORD_TEMPLATE_SCHEMA,
                ),
                Tool(
                    name="generate_powerpoint_template",
                    description="Generate PowerPoint template (.potx) with professional table styles, theme colors, and font embedding. ISO 29500 compliant.",
                    inputSchema=GENERATE_POWERPOINT_TEMPLATE_SCHEMA,
                ),
                Tool(
                    name="generate_excel_template",
                    description="Generate Excel template (.xltx) with themed cell styles and colors.",
                    inputSchema=GENERATE_EXCEL_TEMPLATE_SCHEMA,
                ),
                Tool(
                    name="generate_writer_template",
                    description="Generate LibreOffice Writer template (.ott) with ODF-compliant styles.",
                    inputSchema=GENERATE_WRITER_TEMPLATE_SCHEMA,
                ),
                Tool(
                    name="generate_impress_template",
                    description="Generate LibreOffice Impress template (.otp) with ODF-compliant slides.",
                    inputSchema=GENERATE_IMPRESS_TEMPLATE_SCHEMA,
                ),
                Tool(
                    name="generate_google_docs_template",
                    description="Generate Google Docs-compatible template (.dotx optimized for Google Workspace import).",
                    inputSchema=GENERATE_GOOGLE_DOCS_TEMPLATE_SCHEMA,
                ),
                Tool(
                    name="generate_theme",
                    description="Generate standalone Office theme (.thmx) that can be applied to any Office document.",
                    inputSchema=GENERATE_THEME_SCHEMA,
                ),
                Tool(
                    name="generate_all_templates",
                    description="Generate all template formats for a brand in one call (Word, PowerPoint, Excel, Writer, Impress, Google Docs, Theme).",
                    inputSchema=GENERATE_ALL_TEMPLATES_SCHEMA,
                ),
                # ============================================================
                # DOCUMENT CREATION TOOLS
                # All documents from TokenMoulds templates (with generic fallback)
                # ============================================================
                Tool(
                    name="create_word_document",
                    description="Create a professionally styled Word document (.docx) with content. Full TokenMoulds pipeline with 276 themed styles. Uses brand template or generic TokenMoulds template.",
                    inputSchema=CREATE_WORD_DOCUMENT_SCHEMA,
                ),
                Tool(
                    name="create_powerpoint_document",
                    description="Create a styled PowerPoint presentation (.pptx) with slides. Uses TokenMoulds template for consistent branding.",
                    inputSchema=CREATE_POWERPOINT_DOCUMENT_SCHEMA,
                ),
                Tool(
                    name="create_excel_document",
                    description="Create a styled Excel workbook (.xlsx) with sheets. Uses TokenMoulds template for themed cell styles.",
                    inputSchema=CREATE_EXCEL_DOCUMENT_SCHEMA,
                ),
                Tool(
                    name="create_writer_document",
                    description="Create a LibreOffice Writer document (.odt) with ODF-compliant styling.",
                    inputSchema=CREATE_WRITER_DOCUMENT_SCHEMA,
                ),
                Tool(
                    name="create_impress_document",
                    description="Create a LibreOffice Impress presentation (.odp) with ODF-compliant styling.",
                    inputSchema=CREATE_IMPRESS_DOCUMENT_SCHEMA,
                ),
                # ============================================================
                # ADD-IN AND MACRO TOOLS
                # Macros are codebases - they deserve the same rigor
                # ============================================================
                Tool(
                    name="create_vba_addin",
                    description="Create a VBA add-in (.docm/.xlam/.ppam) with optional code signing. Macros are codebases - treated with same rigor.",
                    inputSchema=CREATE_VBA_ADDIN_SCHEMA,
                ),
                Tool(
                    name="create_web_addin",
                    description="Create an Office Web Add-in manifest and project structure. For modern Office.js add-ins.",
                    inputSchema=CREATE_WEB_ADDIN_SCHEMA,
                ),
                Tool(
                    name="generate_certificate",
                    description="Generate a self-signed code signing certificate for development. Use organizational certificates for production.",
                    inputSchema=GENERATE_CERTIFICATE_SCHEMA,
                ),
                Tool(
                    name="list_certificates",
                    description="List available code signing certificates.",
                    inputSchema=LIST_CERTIFICATES_SCHEMA,
                ),
                # ============================================================
                # CACHE & VALIDATION TOOLS
                # ============================================================
                Tool(
                    name="validate_template",
                    description="Validate a template for ISO 29500 (OOXML) or ODF compliance. Checks structure, content types, relationships.",
                    inputSchema={
                        "type": "object",
                        "properties": {
                            "file_path": {
                                "type": "string",
                                "description": "Path to template file to validate",
                            },
                            "strict_mode": {
                                "type": "boolean",
                                "default": True,
                                "description": "Use strict ISO 29500 validation",
                            },
                            "extract_structure": {
                                "type": "boolean",
                                "default": False,
                                "description": "Return template structure details",
                            },
                        },
                        "required": ["file_path"],
                    },
                ),
                Tool(
                    name="clear_template_cache",
                    description="Clear cached templates for a brand or all brands.",
                    inputSchema={
                        "type": "object",
                        "properties": {
                            "brand": {
                                "type": "string",
                                "description": "Brand to clear (optional, clears all if not specified)",
                            },
                        },
                    },
                ),
                Tool(
                    name="list_cached_templates",
                    description="List all cached templates with metadata (size, fonts, validation status).",
                    inputSchema={
                        "type": "object",
                        "properties": {
                            "brand": {
                                "type": "string",
                                "description": "Filter by brand (optional)",
                            },
                        },
                    },
                ),
            ]

    def _register_handlers(self) -> None:
        """Register tool call handlers."""

        @self.server.call_tool()
        async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
            """Handle tool calls."""
            try:
                result = await self._dispatch_tool(name, arguments)
                return [TextContent(type="text", text=json.dumps(result, indent=2))]

            except Exception as e:
                error_result = {"error": str(e), "tool": name}
                return [
                    TextContent(type="text", text=json.dumps(error_result, indent=2))
                ]

    async def _dispatch_tool(
        self, name: str, arguments: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Dispatch tool call to appropriate handler."""

        # User context
        if name == "set_user_context":
            return await set_user_context(arguments, self)
        elif name == "infer_brand_from_email":
            return await infer_brand_from_email(arguments, self)
        elif name == "get_user_context":
            return await get_user_context(arguments, self)

        # Brand management
        elif name == "extract_brand_from_url":
            return await extract_brand_from_url(arguments, self)
        elif name == "build_recipe_from_inputs":
            return await build_recipe_from_inputs(arguments, self)
        elif name == "load_brand":
            return await load_brand(arguments, self)
        elif name == "list_brands":
            return await list_brands(arguments, self)
        elif name == "set_active_brand":
            return await set_active_brand(arguments, self)

        # Template generation
        elif name == "generate_word_template":
            return await generate_word_template(arguments, self)
        elif name == "generate_powerpoint_template":
            return await generate_powerpoint_template(arguments, self)
        elif name == "generate_excel_template":
            return await generate_excel_template(arguments, self)
        elif name == "generate_writer_template":
            return await generate_writer_template(arguments, self)
        elif name == "generate_impress_template":
            return await generate_impress_template(arguments, self)
        elif name == "generate_google_docs_template":
            return await generate_google_docs_template(arguments, self)
        elif name == "generate_theme":
            return await generate_theme(arguments, self)
        elif name == "generate_all_templates":
            return await generate_all_templates(arguments, self)

        # Document creation (full pipeline - no bundled fallbacks)
        elif name == "create_word_document":
            return await create_word_document(arguments, self)
        elif name == "create_powerpoint_document":
            return await create_powerpoint_document(arguments, self)
        elif name == "create_excel_document":
            return await create_excel_document(arguments, self)
        elif name == "create_writer_document":
            return await create_writer_document(arguments, self)
        elif name == "create_impress_document":
            return await create_impress_document(arguments, self)

        # Add-in and macro tools
        elif name == "create_vba_addin":
            return await create_vba_addin(arguments, self)
        elif name == "create_web_addin":
            return await create_web_addin(arguments, self)
        elif name == "generate_certificate":
            return await generate_self_signed_certificate(arguments, self)
        elif name == "list_certificates":
            return await list_certificates(arguments, self)

        # Cache & Validation
        elif name == "validate_template":
            return await self._validate_template(arguments)
        elif name == "clear_template_cache":
            return await self._clear_cache(arguments)
        elif name == "list_cached_templates":
            return await self._list_cache(arguments)

        else:
            return {"error": f"Unknown tool: {name}"}

    async def _validate_template(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
        """Validate a template file."""
        file_path = arguments.get("file_path")
        if not file_path:
            return {"error": "Missing required 'file_path' parameter"}

        path = Path(file_path)
        if not path.exists():
            return {"error": f"File not found: {file_path}"}

        try:
            template_data = path.read_bytes()
            strict = arguments.get("strict_mode", True)
            extract = arguments.get("extract_structure", False)

            result = do_validate_template(
                template_data,
                strict_mode=strict,
                extract_structure=extract,
            )

            return {
                "success": True,
                "action": "validate_template",
                "file_path": file_path,
                "valid": result.valid,
                "format": result.format,
                "iso_compliant": result.iso_compliant,
                "errors": result.errors if result.errors else None,
                "warnings": result.warnings if result.warnings else None,
                "info": result.info if result.info else None,
                "structure": result.structure if extract else None,
            }

        except Exception as e:
            return {"error": f"Validation failed: {str(e)}"}

    async def _clear_cache(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
        """Clear template cache."""
        brand = arguments.get("brand")

        if brand:
            cleared = self.cache_manager.invalidate(brand)
            return {
                "success": True,
                "action": "clear_template_cache",
                "brand": brand,
                "cleared": cleared,
                "message": f"Cache cleared for brand '{brand}'"
                if cleared
                else f"No cache found for brand '{brand}'",
            }
        else:
            count = self.cache_manager.clear()
            return {
                "success": True,
                "action": "clear_template_cache",
                "brands_cleared": count,
                "message": f"Cleared cache for {count} brands",
            }

    async def _list_cache(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
        """List cached templates."""
        brand = arguments.get("brand")

        if brand:
            info = self.cache_manager.get_brand_info(brand)
            if info:
                return {
                    "success": True,
                    "action": "list_cached_templates",
                    "brand": brand,
                    "cache_info": info,
                }
            else:
                return {
                    "success": True,
                    "action": "list_cached_templates",
                    "brand": brand,
                    "message": f"No cache found for brand '{brand}'",
                }
        else:
            cache_info = self.cache_manager.get_cache_info()
            brands = self.cache_manager.list_brands()

            brands_detail = {}
            for b in brands:
                brands_detail[b] = self.cache_manager.get_brand_info(b)

            return {
                "success": True,
                "action": "list_cached_templates",
                "cache_summary": cache_info,
                "brands": brands_detail,
            }

    async def run(self) -> None:
        """Run the MCP server with stdio transport."""
        # Start brand auto-initialization in background (non-blocking)
        self._auto_init_task = asyncio.create_task(self._auto_initialize_brand())

        async with stdio_server() as (read_stream, write_stream):
            await self.server.run(
                read_stream,
                write_stream,
                self.server.create_initialization_options(),
            )


def create_server(
    tokens_path: Optional[Path] = None,
    brands_path: Optional[Path] = None,
    output_path: Path = Path("./generated"),
    cache_path: Optional[Path] = None,
    fonts_dir: Optional[Path] = None,
) -> TokenMouldsServer:
    """Create a TokenMoulds MCP server instance.

    Args:
        tokens_path: Path to design tokens directory
        brands_path: Path to multi-brand directory
        output_path: Directory for generated documents
        cache_path: Directory for template cache
        fonts_dir: Directory for vendored fonts

    Returns:
        Configured TokenMouldsServer instance
    """
    return TokenMouldsServer(
        tokens_path=tokens_path,
        brands_path=brands_path,
        output_path=output_path,
        cache_path=cache_path,
        fonts_dir=fonts_dir,
    )


def run_server(
    tokens_path: Optional[Path] = None,
    brands_path: Optional[Path] = None,
    output_path: Path = Path("./generated"),
    cache_path: Optional[Path] = None,
    fonts_dir: Optional[Path] = None,
) -> None:
    """Run the TokenMoulds MCP server.

    Args:
        tokens_path: Path to design tokens directory
        brands_path: Path to multi-brand directory
        output_path: Directory for generated documents
        cache_path: Directory for template cache
        fonts_dir: Directory for vendored fonts
    """
    server = create_server(
        tokens_path=tokens_path,
        brands_path=brands_path,
        output_path=output_path,
        cache_path=cache_path,
        fonts_dir=fonts_dir,
    )
    asyncio.run(server.run())
