"""Signed add-in and macro generation for TokenMoulds MCP.

Office add-ins and macros are codebases themselves - they deserve
the same rigor as any other code:
- Code signing for security
- Proper manifest generation
- Version control ready structure
- ISO security compliance

Supported formats:
- Word Add-in (.docm with signed VBA)
- Excel Add-in (.xlam with signed VBA)
- PowerPoint Add-in (.ppam with signed VBA)
- Office Web Add-in (manifest.xml + web app)
- VSTO Add-in (Visual Studio project structure)

Security model:
- Self-signed certificates for development
- Support for organizational certificates
- Timestamping for long-term validity

Uses existing TokenMoulds infrastructure:
- vba_compiler.py: Compiles .bas/.cls/.frm → vbaProject.bin
- authenticode_signer.py: Cross-platform signing (signtool + osslsigncode)
- macros.py: Full compile → sign → inject pipeline
"""

from __future__ import annotations

import json
import tempfile
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, TYPE_CHECKING

# Use existing TokenMoulds signing infrastructure
from tokenmoulds.authenticode_signer import (
    AuthenticodeSigner,
    get_signer_status,
)
from tokenmoulds.macros import (
    compile_and_inject_vba,
)
from tokenmoulds.mcp.cache import TemplateFormat
from tokenmoulds.mcp.tools.token_utils import escape_xml as _escape_xml

if TYPE_CHECKING:
    from tokenmoulds.mcp.server import TokenMouldsServer


# Per-format data for macro-enabled add-in generation.
@dataclass(frozen=True)
class _AddinFormatInfo:
    macro_ext: str
    content_type_old: bytes
    content_type_new: bytes
    host_name: str


_ADDIN_FORMATS: Dict[TemplateFormat, _AddinFormatInfo] = {
    TemplateFormat.WORD: _AddinFormatInfo(
        macro_ext=".docm",
        content_type_old=b"wordprocessingml.template.main+xml",
        content_type_new=b"wordprocessingml.document.macroEnabled.main+xml",
        host_name="Document",
    ),
    TemplateFormat.EXCEL: _AddinFormatInfo(
        macro_ext=".xlam",
        content_type_old=b"spreadsheetml.template.main+xml",
        content_type_new=b"spreadsheetml.addin.macroEnabled.main+xml",
        host_name="Workbook",
    ),
    TemplateFormat.POWERPOINT: _AddinFormatInfo(
        macro_ext=".ppam",
        content_type_old=b"presentationml.template.main+xml",
        content_type_new=b"presentationml.addin.macroEnabled.main+xml",
        host_name="Presentation",
    ),
}

_DEFAULT_FORMAT_INFO = _AddinFormatInfo(
    macro_ext=".docm",
    content_type_old=b"",
    content_type_new=b"",
    host_name="Document",
)


# ============================================================================
# JSON Schemas for MCP Tools
# ============================================================================

CREATE_VBA_ADDIN_SCHEMA: Dict[str, Any] = {
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Add-in name (used for filename and internal references)",
        },
        "description": {
            "type": "string",
            "description": "Add-in description",
        },
        "target": {
            "type": "string",
            "enum": ["word", "excel", "powerpoint"],
            "description": "Target Office application",
        },
        "vba_code": {
            "type": "string",
            "description": "VBA source code for the add-in",
        },
        "modules": {
            "type": "array",
            "description": "VBA modules (alternative to single vba_code)",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Module name"},
                    "type": {
                        "type": "string",
                        "enum": ["standard", "class", "form"],
                        "description": "Module type",
                    },
                    "code": {"type": "string", "description": "Module source code"},
                },
                "required": ["name", "code"],
            },
        },
        "sign": {
            "type": "boolean",
            "default": True,
            "description": "Sign the add-in (requires certificate)",
        },
        "certificate_path": {
            "type": "string",
            "description": "Path to .pfx certificate (uses self-signed if not provided)",
        },
        "certificate_password": {
            "type": "string",
            "description": "Certificate password",
        },
        "brand_name": {
            "type": "string",
            "description": "Brand for theming the add-in UI",
        },
    },
    "required": ["name", "target"],
}

CREATE_WEB_ADDIN_SCHEMA: Dict[str, Any] = {
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Add-in display name",
        },
        "id": {
            "type": "string",
            "description": "Add-in GUID (auto-generated if not provided)",
        },
        "description": {
            "type": "string",
            "description": "Add-in description",
        },
        "target": {
            "type": "string",
            "enum": ["word", "excel", "powerpoint", "outlook"],
            "description": "Target Office application",
        },
        "source_url": {
            "type": "string",
            "description": "URL where add-in web app is hosted",
        },
        "permissions": {
            "type": "array",
            "items": {
                "type": "string",
                "enum": [
                    "ReadDocument",
                    "WriteDocument",
                    "ReadAllDocument",
                    "ReadWriteDocument",
                ],
            },
            "description": "Required permissions",
        },
        "commands": {
            "type": "array",
            "description": "Ribbon commands",
            "items": {
                "type": "object",
                "properties": {
                    "id": {"type": "string"},
                    "label": {"type": "string"},
                    "icon": {"type": "string"},
                    "action": {"type": "string"},
                },
                "required": ["id", "label", "action"],
            },
        },
        "brand_name": {
            "type": "string",
            "description": "Brand for styling",
        },
    },
    "required": ["name", "target", "source_url"],
}

GENERATE_CERTIFICATE_SCHEMA: Dict[str, Any] = {
    "type": "object",
    "properties": {
        "common_name": {
            "type": "string",
            "description": "Certificate CN (e.g., 'My Organization Code Signing')",
        },
        "organization": {
            "type": "string",
            "description": "Organization name",
        },
        "validity_days": {
            "type": "integer",
            "default": 365,
            "description": "Certificate validity in days",
        },
        "key_size": {
            "type": "integer",
            "enum": [2048, 4096],
            "default": 2048,
            "description": "RSA key size",
        },
        "password": {
            "type": "string",
            "description": "Password to protect the .pfx file",
        },
        "output_path": {
            "type": "string",
            "description": "Where to save the certificate",
        },
    },
    "required": ["common_name", "password"],
}

LIST_CERTIFICATES_SCHEMA: Dict[str, Any] = {
    "type": "object",
    "properties": {
        "include_expired": {
            "type": "boolean",
            "default": False,
            "description": "Include expired certificates",
        },
    },
}


def _get_certificates_dir(server: "TokenMouldsServer") -> Path:
    """Get the certificates directory."""
    certs_dir = server.cache_manager.cache_root / "certificates"
    certs_dir.mkdir(parents=True, exist_ok=True)
    return certs_dir


async def generate_self_signed_certificate(
    arguments: Dict[str, Any],
    server: "TokenMouldsServer",
) -> Dict[str, Any]:
    """Generate a self-signed code signing certificate.

    Uses OpenSSL to create a certificate suitable for signing
    Office VBA projects.

    Args:
        arguments: Tool arguments
        server: TokenMouldsServer instance

    Returns:
        Result with certificate info
    """
    import subprocess
    from datetime import timedelta

    common_name = arguments.get("common_name")
    if not common_name:
        return {"error": "Missing required 'common_name' parameter"}

    password = arguments.get("password")
    if not password:
        return {"error": "Missing required 'password' parameter"}

    organization = arguments.get("organization", "")
    validity_days = arguments.get("validity_days", 365)
    key_size = arguments.get("key_size", 2048)

    # Determine output path
    output_path = arguments.get("output_path")
    if output_path:
        cert_path = Path(output_path)
    else:
        certs_dir = _get_certificates_dir(server)
        safe_name = _sanitize_filename(common_name)
        cert_path = certs_dir / f"{safe_name}.pfx"

    try:
        # Check if OpenSSL is available
        result = subprocess.run(
            ["openssl", "version"],
            capture_output=True,
            text=True,
        )
        if result.returncode != 0:
            return {
                "error": "OpenSSL not found. Install OpenSSL to generate certificates.",
                "suggestion": "On macOS: brew install openssl",
            }

        # Generate certificate using OpenSSL
        with tempfile.TemporaryDirectory() as tmpdir:
            key_path = Path(tmpdir) / "private.key"
            crt_path = Path(tmpdir) / "certificate.crt"

            # Build subject string (escape values for slash-delimited format)
            cn_escaped = _escape_openssl_subject_value(common_name)
            subject_parts = [f"/CN={cn_escaped}"]
            if organization:
                org_escaped = _escape_openssl_subject_value(organization)
                subject_parts.append(f"/O={org_escaped}")
            subject = "".join(subject_parts)

            # Generate private key
            subprocess.run(
                [
                    "openssl",
                    "genrsa",
                    "-out",
                    str(key_path),
                    str(key_size),
                ],
                check=True,
                capture_output=True,
            )

            # Generate self-signed certificate
            subprocess.run(
                [
                    "openssl",
                    "req",
                    "-new",
                    "-x509",
                    "-key",
                    str(key_path),
                    "-out",
                    str(crt_path),
                    "-days",
                    str(validity_days),
                    "-subj",
                    subject,
                    "-addext",
                    "keyUsage=digitalSignature",
                    "-addext",
                    "extendedKeyUsage=codeSigning",
                ],
                check=True,
                capture_output=True,
            )

            # Create PFX
            cert_path.parent.mkdir(parents=True, exist_ok=True)
            subprocess.run(
                [
                    "openssl",
                    "pkcs12",
                    "-export",
                    "-out",
                    str(cert_path),
                    "-inkey",
                    str(key_path),
                    "-in",
                    str(crt_path),
                    "-password",
                    f"pass:{password}",
                ],
                check=True,
                capture_output=True,
            )

        # Calculate thumbprint (SHA-1 of DER-encoded certificate).
        # Extract the certificate from the PFX via openssl, then pipe
        # through ``openssl x509 -fingerprint`` to get the true thumbprint.
        extract_cert = subprocess.run(
            [
                "openssl",
                "pkcs12",
                "-in",
                str(cert_path),
                "-password",
                f"pass:{password}",
                "-clcerts",
                "-nokeys",
            ],
            capture_output=True,
        )
        fingerprint_result = subprocess.run(
            ["openssl", "x509", "-fingerprint", "-sha1", "-noout"],
            input=extract_cert.stdout,
            capture_output=True,
            text=True,
        )
        # Output looks like "sha1 Fingerprint=AA:BB:CC:..."
        fp_line = fingerprint_result.stdout.strip()
        thumbprint = fp_line.partition("=")[2].replace(":", "").upper()

        # Calculate validity dates
        valid_from = datetime.now(timezone.utc)
        valid_until = valid_from + timedelta(days=validity_days)

        # Add certificate to signer's store for easy access
        signer = AuthenticodeSigner()
        signer.cert_store.add_certificate(common_name, str(cert_path), password)

        return {
            "success": True,
            "action": "generate_self_signed_certificate",
            "certificate": {
                "path": str(cert_path),
                "common_name": common_name,
                "organization": organization,
                "valid_from": valid_from.isoformat(),
                "valid_until": valid_until.isoformat(),
                "thumbprint": thumbprint,
                "key_size": key_size,
            },
            "signer_backend": signer.backend_name,
            "message": f"Generated self-signed certificate '{common_name}' valid for {validity_days} days.",
            "warning": "Self-signed certificates are for development only. Use organizational certificates for production.",
        }

    except subprocess.CalledProcessError as e:
        return {
            "error": f"OpenSSL command failed: {e.stderr.decode() if e.stderr else str(e)}",
        }
    except Exception as e:
        return {
            "error": f"Failed to generate certificate: {str(e)}",
        }


async def list_certificates(
    arguments: Dict[str, Any],
    server: "TokenMouldsServer",
) -> Dict[str, Any]:
    """List available code signing certificates.

    Args:
        arguments: Tool arguments
        server: TokenMouldsServer instance

    Returns:
        List of certificate info and signer status
    """
    certs_dir = _get_certificates_dir(server)

    certificates = []

    for cert_file in certs_dir.glob("*.pfx"):
        try:
            info = {
                "path": str(cert_file),
                "name": cert_file.stem,
                "size_bytes": cert_file.stat().st_size,
                "created": datetime.fromtimestamp(
                    cert_file.stat().st_ctime
                ).isoformat(),
            }
            certificates.append(info)
        except Exception:
            continue

    # Get signer status and recommendations
    signer_status = get_signer_status()

    return {
        "success": True,
        "action": "list_certificates",
        "certificates_dir": str(certs_dir),
        "count": len(certificates),
        "certificates": certificates,
        "signer": {
            "available": signer_status.get("available"),
            "backend": signer_status.get("backend"),
            "platform": signer_status.get("platform"),
        },
        "recommendations": signer_status.get("recommendations", {}).get(
            "installation", []
        )[:3],
    }


# ============================================================================
# VBA Add-in Generation
# ============================================================================


async def create_vba_addin(
    arguments: Dict[str, Any],
    server: "TokenMouldsServer",
) -> Dict[str, Any]:
    """Create a VBA add-in for Office applications.

    Uses TokenMoulds VBA compilation and signing infrastructure:
    - vba_compiler.py: Compiles source to vbaProject.bin
    - authenticode_signer.py: Signs with certificate
    - macros.py: Injects into template

    Args:
        arguments: Tool arguments
        server: TokenMouldsServer instance

    Returns:
        Result with add-in path and info
    """
    name = arguments.get("name")
    if not name:
        return {"error": "Missing required 'name' parameter"}

    target = arguments.get("target")
    if not target:
        return {"error": "Missing required 'target' parameter"}

    description = arguments.get("description", f"{name} Add-in")
    vba_code = arguments.get("vba_code", "")
    modules = arguments.get("modules", [])
    should_sign = arguments.get("sign", True)
    brand_name = arguments.get("brand_name") or server.active_brand

    # Resolve format
    try:
        template_format = TemplateFormat(target)
    except ValueError:
        template_format = TemplateFormat.WORD
    fmt_info = _ADDIN_FORMATS.get(template_format, _DEFAULT_FORMAT_INFO)

    # Create output path
    safe_name = _sanitize_filename(name)
    output_file = server.output_path / f"{safe_name}{fmt_info.macro_ext}"

    try:
        # Check for cached template
        template_bytes = None
        if brand_name and brand_name in server.loaded_tokens:
            tokens = server.loaded_tokens[brand_name]
            token_hash = server.cache_manager.compute_token_hash(tokens)
            template_bytes = server.cache_manager.get_template(
                brand_name, template_format, token_hash
            )

        if template_bytes is None:
            return {
                "error": f"No {target} template available.",
                "suggestion": f"Generate a template first with generate_{target}_template.",
            }

        # Write template to temp file for processing
        with tempfile.NamedTemporaryFile(suffix=".dotx", delete=False) as tmp:
            tmp.write(template_bytes)
            template_path = Path(tmp.name)

        signed = False
        modules_compiled = 0

        # If we have VBA code/modules, compile and inject
        if vba_code or modules:
            # Create temp directory for VBA source files
            with tempfile.TemporaryDirectory() as vba_dir:
                vba_source_dir = Path(vba_dir)

                # Write VBA code to .bas file
                if vba_code:
                    main_module = vba_source_dir / "Main.bas"
                    main_module.write_text(vba_code, encoding="utf-8")
                    modules_compiled += 1

                # Write additional modules
                for mod in modules:
                    mod_name = mod.get("name", "Module1")
                    mod_type = mod.get("type", "standard")
                    mod_code = mod.get("code", "")

                    ext_map = {"standard": ".bas", "class": ".cls", "form": ".frm"}
                    mod_ext = ext_map.get(mod_type, ".bas")

                    mod_file = vba_source_dir / f"{mod_name}{mod_ext}"
                    mod_file.write_text(mod_code, encoding="utf-8")
                    modules_compiled += 1

                # Get signing certificate if provided
                cert_path = arguments.get("certificate_path")
                cert_password = arguments.get("certificate_password")

                # Use TokenMoulds compile_and_inject_vba pipeline
                success = compile_and_inject_vba(
                    template=template_path,
                    vba_source_dir=vba_source_dir,
                    output=output_file,
                    project_config={"name": name},
                    certificate_path=Path(cert_path) if cert_path else None,
                    certificate_password=cert_password,
                )

                if not success:
                    return {
                        "error": "VBA compilation failed",
                        "suggestion": "Check VBA source code syntax and ensure vbaProject-Compiler is installed",
                    }

                # Check if signing was successful
                if should_sign and cert_path:
                    signer = AuthenticodeSigner()
                    signed = signer.is_available
        else:
            # No VBA code - just convert template to macro-enabled format
            _convert_template_to_macro_enabled(template_path, output_file, fmt_info)

        # Clean up temp template
        template_path.unlink(missing_ok=True)

        # Get signer status for info
        signer_status = get_signer_status()

        return {
            "success": True,
            "action": "create_vba_addin",
            "file_path": str(output_file.absolute()),
            "filename": output_file.name,
            "name": name,
            "description": description,
            "target": target,
            "modules_compiled": modules_compiled,
            "signed": signed,
            "brand_used": brand_name,
            "file_size_kb": round(output_file.stat().st_size / 1024, 1),
            "signer_backend": signer_status.get("backend"),
            "message": f"Created {target.title()} add-in '{name}' with {modules_compiled} VBA modules.",
        }

    except Exception as e:
        return {
            "error": f"Failed to create add-in: {str(e)}",
            "name": name,
        }


def _convert_template_to_macro_enabled(
    template_path: Path,
    output_path: Path,
    fmt_info: _AddinFormatInfo,
) -> None:
    """Convert template to macro-enabled format (without VBA code)."""
    import zipfile
    from io import BytesIO

    template_bytes = template_path.read_bytes()
    input_zip = BytesIO(template_bytes)
    output_zip = BytesIO()

    old_ct = fmt_info.content_type_old
    new_ct = fmt_info.content_type_new

    with zipfile.ZipFile(input_zip, "r") as zin:
        with zipfile.ZipFile(
            output_zip, "w", zipfile.ZIP_DEFLATED, compresslevel=9
        ) as zout:
            for item in zin.namelist():
                data = zin.read(item)

                if item == "[Content_Types].xml" and old_ct and new_ct:
                    data = data.replace(old_ct, new_ct)

                zout.writestr(item, data)

    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_bytes(output_zip.getvalue())


# ============================================================================
# Web Add-in Generation
# ============================================================================


async def create_web_addin(
    arguments: Dict[str, Any],
    server: "TokenMouldsServer",
) -> Dict[str, Any]:
    """Create an Office Web Add-in manifest.

    Generates a manifest.xml file for Office Web Add-ins that can be
    sideloaded or published to AppSource.

    Args:
        arguments: Tool arguments
        server: TokenMouldsServer instance

    Returns:
        Result with manifest path and project structure
    """
    name = arguments.get("name")
    if not name:
        return {"error": "Missing required 'name' parameter"}

    target = arguments.get("target")
    if not target:
        return {"error": "Missing required 'target' parameter"}

    source_url = arguments.get("source_url")
    if not source_url:
        return {"error": "Missing required 'source_url' parameter"}

    description = arguments.get("description", f"{name} Add-in")
    addin_id = arguments.get("id") or _generate_guid()
    permissions = arguments.get("permissions", ["ReadWriteDocument"])
    commands = arguments.get("commands", [])
    # Create output directory
    safe_name = _sanitize_filename(name)
    output_dir = server.output_path / f"{safe_name}-webaddin"
    output_dir.mkdir(parents=True, exist_ok=True)

    try:
        # Generate manifest
        manifest_xml = _generate_web_addin_manifest(
            name=name,
            addin_id=addin_id,
            description=description,
            target=target,
            source_url=source_url,
            permissions=permissions,
            commands=commands,
        )

        # Write manifest
        manifest_path = output_dir / "manifest.xml"
        manifest_path.write_text(manifest_xml, encoding="utf-8")

        # Generate package.json for npm-based development
        package_json = _generate_package_json(name, description)
        (output_dir / "package.json").write_text(
            json.dumps(package_json, indent=2),
            encoding="utf-8",
        )

        # Generate README
        readme = _generate_webaddin_readme(name, target, source_url)
        (output_dir / "README.md").write_text(readme, encoding="utf-8")

        return {
            "success": True,
            "action": "create_web_addin",
            "output_dir": str(output_dir.absolute()),
            "manifest_path": str(manifest_path.absolute()),
            "addin_id": addin_id,
            "name": name,
            "description": description,
            "target": target,
            "source_url": source_url,
            "permissions": permissions,
            "commands_count": len(commands),
            "files_created": ["manifest.xml", "package.json", "README.md"],
            "message": f"Created Office Web Add-in project for {target.title()}.",
            "next_steps": [
                "Review and customize manifest.xml",
                "Deploy your web app to the source_url",
                "Sideload the add-in for testing",
            ],
        }

    except Exception as e:
        return {
            "error": f"Failed to create web add-in: {str(e)}",
            "name": name,
        }


def _generate_guid() -> str:
    """Generate a random GUID for add-in ID."""
    import uuid

    return str(uuid.uuid4())


def _generate_web_addin_manifest(
    name: str,
    addin_id: str,
    description: str,
    target: str,
    source_url: str,
    permissions: List[str],
    commands: List[Dict[str, Any]],
) -> str:
    """Generate Office Web Add-in manifest XML."""
    # Host mapping — includes outlook which isn't in _ADDIN_FORMATS
    _HOSTS = {
        "word": "Document",
        "excel": "Workbook",
        "powerpoint": "Presentation",
        "outlook": "Mailbox",
    }
    host = _HOSTS.get(target, "Document")

    # Permission set
    permission_set = permissions[0] if permissions else "ReadWriteDocument"

    manifest = f'''<?xml version="1.0" encoding="UTF-8"?>
<OfficeApp
    xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
    xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides"
    xsi:type="TaskPaneApp">

    <Id>{addin_id}</Id>
    <Version>1.0.0.0</Version>
    <ProviderName>TokenMoulds</ProviderName>
    <DefaultLocale>en-US</DefaultLocale>
    <DisplayName DefaultValue="{_escape_xml(name)}"/>
    <Description DefaultValue="{_escape_xml(description)}"/>

    <IconUrl DefaultValue="{source_url}/assets/icon-32.png"/>
    <HighResolutionIconUrl DefaultValue="{source_url}/assets/icon-64.png"/>
    <SupportUrl DefaultValue="{source_url}/support"/>

    <Hosts>
        <Host Name="{host}"/>
    </Hosts>

    <DefaultSettings>
        <SourceLocation DefaultValue="{source_url}/taskpane.html"/>
    </DefaultSettings>

    <Permissions>{permission_set}</Permissions>

</OfficeApp>
'''
    return manifest


def _generate_package_json(name: str, description: str) -> Dict[str, Any]:
    """Generate package.json for web add-in project."""
    safe_name = name.lower().replace(" ", "-")
    return {
        "name": safe_name,
        "version": "1.0.0",
        "description": description,
        "main": "index.js",
        "scripts": {
            "start": "office-addin-debugging start manifest.xml",
            "stop": "office-addin-debugging stop manifest.xml",
            "validate": "office-addin-manifest validate manifest.xml",
            "build": "webpack --mode production",
            "dev": "webpack serve --mode development",
        },
        "dependencies": {
            "@microsoft/office-js": "^1.1.0",
        },
        "devDependencies": {
            "office-addin-debugging": "^4.0.0",
            "office-addin-manifest": "^1.0.0",
            "webpack": "^5.0.0",
            "webpack-cli": "^5.0.0",
            "webpack-dev-server": "^4.0.0",
        },
        "author": "TokenMoulds",
        "license": "MIT",
    }


def _generate_webaddin_readme(name: str, target: str, source_url: str) -> str:
    """Generate README for web add-in project."""
    return f"""# {name}

Office Web Add-in for {target.title()}, generated by TokenMoulds.

## Setup

1. Install dependencies:
   ```bash
   npm install
   ```

2. Deploy your web app to: `{source_url}`

3. Validate the manifest:
   ```bash
   npm run validate
   ```

4. Sideload for testing:
   ```bash
   npm run start
   ```

## Project Structure

- `manifest.xml` - Office Add-in manifest
- `package.json` - Node.js dependencies
- `src/` - Add your web app source here

## Resources

- [Office Add-ins documentation](https://docs.microsoft.com/office/dev/add-ins/)
- [Script Lab](https://aka.ms/getscriptlab) - Prototype add-in ideas

Generated by TokenMoulds MCP Server.
"""


def _sanitize_filename(name: str) -> str:
    """Replace non-alphanumeric characters (except ``-`` and ``_``) with ``_``."""
    return "".join(c if c.isalnum() or c in "-_" else "_" for c in name)


def _escape_openssl_subject_value(value: str) -> str:
    """Escape a value for use in OpenSSL's slash-delimited subject string.

    In the ``/CN=.../O=...`` format, ``\\`` and ``/`` inside field values
    must be escaped so OpenSSL doesn't misparse them as field separators
    or escape characters.
    """
    return value.replace("\\", "\\\\").replace("/", "\\/")
