#!/usr/bin/env python3
"""Generate public API documentation.

This script:
1. Generates the OpenAPI JSON schema for public endpoints
2. Builds the static ReDoc HTML using Redocly CLI
"""

import json
import pathlib
import re
import subprocess
import sys

import fastapi

from api.metering import router as usages_router
from api.speech.tts import router as tts_router
from api.voices import router as voices_router
from api.voices.pronunciations import router as pronunciations_router


def load_readme_content() -> tuple[str, str]:
    """Load the API documentation content.

    Returns:
        tuple: (intro_before_features, remaining_content_from_features)
    """
    docs_dir = pathlib.Path(__file__).parent
    api_doc_path = docs_dir / "api_doc.md"
    content = api_doc_path.read_text()

    # First, apply regex to promote all headings in the entire text
    promoted_content = re.sub(r"^##", "#", content, flags=re.MULTILINE)

    # Remove the first H1 title (first line starting with "# ")
    promoted_content = re.sub(r"^# .*\n", "", promoted_content, count=1)

    # Split at "# Features"
    if "# Features" in promoted_content:
        intro, main_content = promoted_content.split("# Features", 1)
        main_content = "# Features" + main_content
        # Drop the title from intro (remove any remaining H1 headings)
        intro = re.sub(r"^# .*\n", "", intro, flags=re.MULTILINE)
    else:
        intro = ""
        main_content = promoted_content

    return (intro.strip(), main_content.strip())


def load_api_overview() -> str:
    """Load a concise API overview for the OpenAPI description."""
    first_paragraph, _ = load_readme_content()
    return first_paragraph


def create_docs_app() -> fastapi.FastAPI:
    """Create FastAPI app with only public endpoints for documentation."""
    app = fastapi.FastAPI(
        title="Gradium API",
        description=load_api_overview(),
        version="0.1.0",
        redoc_url="/redoc",
        openapi_url="/openapi.json",
        servers=[
            {
                "url": "https://eu.api.gradium.ai/api",
                "description": "EU API",
            },
            {
                "url": "https://us.api.gradium.ai/api",
                "description": "US API",
            },
        ],
    )

    # Only include public-facing routers
    app.include_router(tts_router.router)
    app.include_router(voices_router.router)
    app.include_router(pronunciations_router.router)
    app.include_router(usages_router.router)

    return app


def load_websocket_tts_description() -> str:
    """Load WebSocket documentation from markdown file."""
    docs_dir = pathlib.Path(__file__).parent
    websocket_docs_path = docs_dir / "websocket_tts_description.md"
    return websocket_docs_path.read_text()


def load_websocket_stt_description() -> str:
    """Load WebSocket documentation from markdown file."""
    docs_dir = pathlib.Path(__file__).parent
    websocket_docs_path = docs_dir / "websocket_stt_description.md"
    return websocket_docs_path.read_text()


def load_post_tts_description() -> str:
    """Load POST TTS documentation from markdown file."""
    docs_dir = pathlib.Path(__file__).parent
    post_tts_docs_path = docs_dir / "post_tts_description.md"
    return post_tts_docs_path.read_text()


def load_rewriting_rules() -> str:
    """Load rewriting rules documentation from markdown file."""
    docs_dir = pathlib.Path(__file__).parent
    rewriting_rules_path = docs_dir / "rewriting_rules.md"
    return rewriting_rules_path.read_text()


def load_faq() -> str:
    """Load FAQ documentation from markdown file."""
    docs_dir = pathlib.Path(__file__).parent
    faq_path = docs_dir / "faq.md"
    return faq_path.read_text()


def load_release_notes() -> str:
    """Load change log documentation from markdown file."""
    docs_dir = pathlib.Path(__file__).parent
    change_log_path = docs_dir / "change_log.md"
    return change_log_path.read_text()


def add_websocket_docs(schema: dict) -> dict:
    """Add WebSocket endpoint documentation to the OpenAPI schema.

    OpenAPI 3.0 doesn't natively support WebSockets, but we can document
    them as regular paths with detailed descriptions.
    """
    websocket_paths = {
        "/api/speech/tts": {
            "get": {
                "tags": ["TTS"],
                "summary": "TTS WebSocket Stream",
                "description": load_websocket_tts_description(),
                "parameters": [
                    {
                        "name": "x-api-key",
                        "in": "header",
                        "required": True,
                        "schema": {"type": "string"},
                        "description": "Your Gradium API key",
                    }
                ],
                "responses": {
                    "101": {"description": "WebSocket connection established"}
                },
            }
        },
        "/api/speech/asr": {
            "get": {
                "tags": ["STT"],
                "summary": "STT WebSocket Stream",
                "description": load_websocket_stt_description(),
                "parameters": [
                    {
                        "name": "x-api-key",
                        "in": "header",
                        "required": True,
                        "schema": {"type": "string"},
                        "description": "Your Gradium API key",
                    }
                ],
                "responses": {
                    "101": {"description": "WebSocket connection established"}
                },
            }
        },
        "/api/post/speech/tts": {
            "post": {
                "tags": ["TTS"],
                "summary": "TTS POST Endpoint",
                "description": load_post_tts_description(),
                "parameters": [
                    {
                        "name": "x-api-key",
                        "in": "header",
                        "required": True,
                        "schema": {"type": "string"},
                        "description": "Your Gradium API key",
                    }
                ],
                "requestBody": {
                    "required": True,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "text",
                                    "voice_id",
                                    "output_format",
                                ],
                                "properties": {
                                    "text": {
                                        "type": "string",
                                        "description": "The text to convert to speech",
                                    },
                                    "voice_id": {
                                        "type": "string",
                                        "description": "Voice ID from the library or custom voice ID",
                                    },
                                    "output_format": {
                                        "type": "string",
                                        "enum": [
                                            "wav",
                                            "pcm",
                                            "opus",
                                            "ulaw_8000",
                                            "alaw_8000",
                                            "pcm_8000",
                                            "pcm_16000",
                                            "pcm_24000",
                                        ],
                                        "description": "Audio output format",
                                    },
                                    "only_audio": {
                                        "type": "boolean",
                                        "description": "When true, returns raw audio bytes instead of JSON",
                                    },
                                },
                            }
                        }
                    },
                },
                "responses": {
                    "200": {"description": "Audio data returned successfully"},
                    "400": {"description": "Bad request"},
                    "401": {"description": "Unauthorized"},
                },
            }
        },
    }

    schema["paths"].update(websocket_paths)
    return schema


def organize_tags(schema: dict) -> dict:
    """Organize tags into Documentation and API Reference sections."""
    # Update all path tags to be under "API Reference"
    for path_data in schema.get("paths", {}).values():
        for method_data in path_data.values():
            if "tags" in method_data:
                old_tags = method_data["tags"]
                new_tags = []
                for tag in old_tags:
                    # Map existing tags to nested API Reference sections
                    tag_mapping = {
                        "tts": "TTS",
                        "stt": "STT",
                        "voices": "Voices",
                        "pronunciations": "Pronunciations",
                        "usages": "Credits",
                    }
                    mapped = tag_mapping.get(tag, tag)
                    new_tags.append(mapped)
                method_data["tags"] = new_tags

    # Use x-tagGroups to create Documentation and API Reference sections
    # Documentation tag has no endpoints, just description
    schema["x-tagGroups"] = [
        {
            "name": "Documentation",
            "tags": ["Documentation", "FAQ", "Release notes"],
        },
        {
            "name": "API Reference",
            "tags": ["TTS", "STT", "Voices", "Pronunciations", "Credits"],
        },
    ]

    # Update tags metadata
    _, readme_content = load_readme_content()
    rewriting_rules = load_rewriting_rules()
    faq = load_faq()
    change_log = load_release_notes()

    # Append rewriting rules section to the end of documentation
    full_documentation = f"{readme_content}\n\n{rewriting_rules}"

    schema["tags"] = [
        {
            "name": "Documentation",
            "description": full_documentation,
            "x-displayName": "Documentation",
        },
        {
            "name": "FAQ",
            "description": faq,
        },
        {
            "name": "Release notes",
            "description": change_log,
        },
        {
            "name": "TTS",
            "description": (
                "Text-to-Speech endpoints for converting text to audio"
            ),
        },
        {
            "name": "STT",
            "description": (
                "Speech-to-Text endpoints for converting audio to text"
            ),
        },
        {
            "name": "Voices",
            "description": "Manage custom voice clones",
        },
        {
            "name": "Pronunciations",
            "description": "Manage pronunciation dictionaries for custom text rewriting",
        },
        {
            "name": "Credits",
            "description": "Monitor API credit balance",
        },
    ]

    return schema


def generate_openapi_schema(output_path: pathlib.Path) -> None:
    """Generate and save OpenAPI schema."""
    print("📄 Generating OpenAPI schema...")
    app = create_docs_app()
    openapi_schema = app.openapi()

    # Add WebSocket documentation
    openapi_schema = add_websocket_docs(openapi_schema)

    # Organize into Documentation and API Reference sections
    openapi_schema = organize_tags(openapi_schema)

    with open(output_path, "w") as f:
        json.dump(openapi_schema, f, indent=2)

    print(f"✓ OpenAPI schema saved to {output_path}")


def build_redoc_html(
    openapi_path: pathlib.Path,
    output_path: pathlib.Path,
    config_path: pathlib.Path | None = None,
) -> None:
    """Build static ReDoc HTML using Redocly CLI.

    Args:
        openapi_path: Path to OpenAPI schema file
        output_path: Path where HTML should be written
        config_path: Optional path to redocly.yaml config file
    """
    print("\n🔨 Building ReDoc HTML...")

    cmd = [
        "npx",
        "@redocly/cli",
        "build-docs",
        str(openapi_path),
        "-o",
        str(output_path),
    ]

    # Add config file if provided
    if config_path and config_path.exists():
        cmd.extend(["--config", str(config_path)])
        print(f"  Using config: {config_path}")

    try:
        # Run without capturing output so user can see progress in real-time
        subprocess.run(
            cmd,
            check=True,
        )
        print(f"✓ ReDoc HTML built: {output_path}")

        # Add copy buttons to code blocks
        add_copy_buttons_to_code_blocks(output_path)
        print("✓ Copy buttons added to code blocks")

    except subprocess.CalledProcessError as e:
        print(
            f"✗ Failed to build ReDoc HTML (exit code {e.returncode})",
            file=sys.stderr,
        )
        sys.exit(1)
    except FileNotFoundError:
        print(
            "✗ npx not found. Please install Node.js and npm.", file=sys.stderr
        )
        sys.exit(1)


def add_copy_buttons_to_code_blocks(html_path: pathlib.Path) -> None:
    """Add copy buttons to all code blocks in the HTML."""
    html_content = html_path.read_text()

    # Add CSS and JavaScript for copy buttons
    copy_script = """
    <style>
      .code-block-wrapper {
        position: relative;
      }
      .copy-button {
        position: absolute;
        top: 8px;
        right: 8px;
        padding: 4px 8px;
        background: #4F46E5;
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 12px;
        opacity: 0;
        transition: opacity 0.2s;
      }
      .code-block-wrapper:hover .copy-button {
        opacity: 1;
      }
      .copy-button:hover {
        background: #4338CA;
      }
      .copy-button:active {
        background: #3730A3;
      }
    </style>
    <script>
      document.addEventListener('DOMContentLoaded', function() {
        // Find all code blocks
        const codeBlocks = document.querySelectorAll('pre code');

        codeBlocks.forEach(function(codeBlock) {
          const pre = codeBlock.parentElement;

          // Wrap in a div if not already wrapped
          if (!pre.parentElement.classList.contains('code-block-wrapper')) {
            const wrapper = document.createElement('div');
            wrapper.className = 'code-block-wrapper';
            pre.parentNode.insertBefore(wrapper, pre);
            wrapper.appendChild(pre);

            // Add copy button
            const button = document.createElement('button');
            button.className = 'copy-button';
            button.textContent = 'Copy';

            button.addEventListener('click', function() {
              const text = codeBlock.textContent;
              navigator.clipboard.writeText(text).then(function() {
                button.textContent = 'Copied!';
                setTimeout(function() {
                  button.textContent = 'Copy';
                }, 2000);
              });
            });

            wrapper.appendChild(button);
          }
        });
      });
    </script>
    """

    # Insert before </body>
    html_content = html_content.replace("</body>", f"{copy_script}</body>")

    html_path.write_text(html_content)


def main() -> None:
    """Main entry point."""
    # Define paths
    project_root = pathlib.Path(__file__).parents[2]
    docs_dir = pathlib.Path(__file__).parent
    openapi_path = project_root / "openapi_public.json"
    html_path = project_root / "api_docs.html"
    config_path = docs_dir / "redocly.yaml"

    print("🚀 Starting API documentation generation\n")

    # Step 1: Generate OpenAPI schema
    generate_openapi_schema(openapi_path)

    # Step 2: Build ReDoc HTML
    build_redoc_html(openapi_path, html_path, config_path)

    print("\n✨ Documentation generation complete!")
    print("\nYou can now:")
    print(f"  • Open {html_path} in a browser")
    print(f"  • Deploy {html_path} to a static site host")
    print(f"  • Reference {openapi_path} for API schema")


if __name__ == "__main__":
    main()
