"""
Convert Markdown documentation to Word document (.docx)

This script converts markdown documentation to formatted Word documents
with proper styling for headings, tables, code blocks, and lists.
Mermaid diagrams are converted to images using the mermaid.ink API.

Usage:
    # Convert a single file
    python scripts/convert_md_to_docx.py <input.md> [output.docx]

    # Convert all markdown files in abp-dev directory
    python scripts/convert_md_to_docx.py --all

    # Convert specific file (legacy mode - SHACL_RULES_DOCUMENTATION.md)
    python scripts/convert_md_to_docx.py

Requirements:
    pip install python-docx markdown beautifulsoup4 requests
"""

import argparse
import base64
import re
import sys
import time
from io import BytesIO
from pathlib import Path

import markdown
import requests
from bs4 import BeautifulSoup
from docx import Document
from docx.enum.style import WD_STYLE_TYPE
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Inches, Pt, RGBColor

# Default file paths (for backward compatibility)
DEFAULT_INPUT = Path(__file__).parent.parent / "config/environments/abp-dev/SHACL_RULES_DOCUMENTATION.md"
DEFAULT_OUTPUT = Path(__file__).parent.parent / "config/environments/abp-dev/SHACL_RULES_DOCUMENTATION.docx"


def setup_document_styles(doc):
    """Set up custom styles for the document."""
    styles = doc.styles

    # Modify Normal style
    normal_style = styles["Normal"]
    normal_font = normal_style.font
    normal_font.name = "Calibri"
    normal_font.size = Pt(11)

    # Create or modify Heading styles
    for i in range(1, 4):
        heading_name = f"Heading {i}"
        if heading_name in styles:
            heading = styles[heading_name]
        else:
            heading = styles.add_style(heading_name, WD_STYLE_TYPE.PARAGRAPH)

        heading_font = heading.font
        heading_font.name = "Calibri"
        heading_font.bold = True
        heading_font.color.rgb = RGBColor(0, 51, 102)  # Dark blue

        if i == 1:
            heading_font.size = Pt(20)
        elif i == 2:
            heading_font.size = Pt(16)
        elif i == 3:
            heading_font.size = Pt(14)

    # Code style
    try:
        code_style = styles["Code"]
    except KeyError:
        code_style = styles.add_style("Code", WD_STYLE_TYPE.PARAGRAPH)

    code_font = code_style.font
    code_font.name = "Consolas"
    code_font.size = Pt(9)
    code_style.paragraph_format.left_indent = Inches(0.5)
    code_style.paragraph_format.space_before = Pt(6)
    code_style.paragraph_format.space_after = Pt(6)


def parse_markdown_to_html(md_content):
    """Convert markdown to HTML using markdown library."""
    # Configure markdown with extensions
    html = markdown.markdown(md_content, extensions=["tables", "fenced_code", "nl2br", "sane_lists"])
    return html


def add_heading(doc, text, level):
    """Add a heading to the document."""
    heading = doc.add_heading(text, level=level)
    return heading


def add_paragraph(doc, text, style=None):
    """Add a paragraph to the document."""
    if style:
        p = doc.add_paragraph(text, style=style)
    else:
        p = doc.add_paragraph(text)
    return p


def add_code_block(doc, code_text, language=""):
    """Add a code block with monospace font."""
    p = doc.add_paragraph(style="Code")
    run = p.add_run(code_text)
    run.font.name = "Consolas"
    run.font.size = Pt(9)
    # Light gray background (simulated with shading)
    return p


def add_table_from_markdown(doc, table_html):
    """Parse HTML table and add it to the document."""
    soup = BeautifulSoup(table_html, "html.parser")

    # Find all rows
    rows = soup.find_all("tr")
    if not rows:
        return

    # Get number of columns from first row
    first_row = rows[0]
    cols = len(first_row.find_all(["th", "td"]))

    # Create table
    table = doc.add_table(rows=len(rows), cols=cols)
    table.style = "Light Grid Accent 1"

    # Populate table
    for i, row in enumerate(rows):
        cells = row.find_all(["th", "td"])
        for j, cell in enumerate(cells):
            table_cell = table.rows[i].cells[j]
            # Extract text, handling nested elements
            cell_text = cell.get_text(strip=True)
            table_cell.text = cell_text

            # Bold header row
            if i == 0:
                for paragraph in table_cell.paragraphs:
                    for run in paragraph.runs:
                        run.font.bold = True

    # Add spacing after table
    doc.add_paragraph()


def process_html_content(doc, html_content):
    """Process HTML content and add to Word document."""
    soup = BeautifulSoup(html_content, "html.parser")

    for element in soup.children:
        if element.name is None:
            continue

        # Headings
        if element.name in ["h1", "h2", "h3", "h4", "h5", "h6"]:
            level = int(element.name[1])
            level = min(level, 3)  # Word supports up to 9, but we'll use 1-3
            text = element.get_text(strip=True)
            add_heading(doc, text, level)

        # Paragraphs
        elif element.name == "p":
            text = element.get_text(strip=True)
            if text:
                add_paragraph(doc, text)

        # Tables
        elif element.name == "table":
            add_table_from_markdown(doc, str(element))

        # Code blocks
        elif element.name == "pre":
            code = element.get_text()
            add_code_block(doc, code)

        # Lists
        elif element.name in ["ul", "ol"]:
            for li in element.find_all("li", recursive=False):
                text = li.get_text(strip=True)
                p = doc.add_paragraph(text, style="List Bullet" if element.name == "ul" else "List Number")

        # Horizontal rule
        elif element.name == "hr":
            p = doc.add_paragraph()
            p.add_run("_" * 80)
            p.alignment = WD_ALIGN_PARAGRAPH.CENTER


def mermaid_to_image(mermaid_code):
    """
    Convert mermaid diagram code to image using mermaid.ink API.
    Returns BytesIO object containing the image data.
    """
    try:
        # Encode mermaid code for URL
        # mermaid.ink uses base64-encoded, deflated diagram definition
        graphbytes = mermaid_code.encode("utf-8")
        base64_bytes = base64.urlsafe_b64encode(graphbytes)
        base64_string = base64_bytes.decode("ascii")

        # Use mermaid.ink API (simple base64 encoding)
        url = f"https://mermaid.ink/img/{base64_string}"

        print("  Fetching diagram from mermaid.ink...")
        response = requests.get(url, timeout=10)

        if response.status_code == 200:
            return BytesIO(response.content)
        else:
            print(f"  ⚠️  Failed to fetch diagram (status {response.status_code})")
            return None
    except Exception as e:
        print(f"  ⚠️  Error converting mermaid to image: {e}")
        return None


def extract_mermaid_diagrams(md_content):
    """
    Extract all mermaid diagrams from markdown content.
    Returns list of tuples: (placeholder_id, mermaid_code)
    """
    pattern = r"```mermaid\n(.*?)\n```"
    diagrams = []

    for i, match in enumerate(re.finditer(pattern, md_content, flags=re.DOTALL)):
        mermaid_code = match.group(1)
        placeholder_id = f"__MERMAID_DIAGRAM_{i}__"
        diagrams.append((placeholder_id, mermaid_code))

    return diagrams


def replace_mermaid_with_placeholders(md_content):
    """Replace mermaid diagrams with unique placeholders."""
    pattern = r"```mermaid\n(.*?)\n```"
    counter = [0]

    def replace_func(match):
        placeholder = f"__MERMAID_DIAGRAM_{counter[0]}__"
        counter[0] += 1
        return f"\n{placeholder}\n"

    return re.sub(pattern, replace_func, md_content, flags=re.DOTALL)


def extract_document_title(md_content: str) -> str:
    """Extract document title from first # heading."""
    lines = md_content.split("\n")
    for line in lines:
        if line.startswith("# "):
            return line.lstrip("# ").strip()
    return "Documentation"


def process_inline_formatting(paragraph, text):
    """
    Process inline markdown formatting and add formatted runs to paragraph.
    Handles: ***bold italic***, **bold**, *italic*, `code`
    """
    # Pattern to match inline formatting
    # Order matters: match longest patterns first (*** before ** and *)
    pattern = r"(\*\*\*([^*]+)\*\*\*)|(\*\*([^*]+)\*\*)|(\*([^*]+)\*)|(`([^`]+)`)"

    last_end = 0
    for match in re.finditer(pattern, text):
        # Add any text before this match
        if match.start() > last_end:
            paragraph.add_run(text[last_end : match.start()])

        # Determine which group matched and format accordingly
        if match.group(1):  # ***bold italic***
            run = paragraph.add_run(match.group(2))
            run.bold = True
            run.italic = True
        elif match.group(3):  # **bold**
            run = paragraph.add_run(match.group(4))
            run.bold = True
        elif match.group(5):  # *italic*
            run = paragraph.add_run(match.group(6))
            run.italic = True
        elif match.group(7):  # `code`
            run = paragraph.add_run(match.group(8))
            run.font.name = "Consolas"
            run.font.size = Pt(10)

        last_end = match.end()

    # Add any remaining text after the last match
    if last_end < len(text):
        paragraph.add_run(text[last_end:])


def convert_markdown_to_docx(input_file: Path, output_file: Path | None = None, skip_mermaid: bool = False):
    """
    Main conversion function.

    Args:
        input_file: Path to input markdown file
        output_file: Path to output DOCX file (defaults to input_file.with_suffix('.docx'))
        skip_mermaid: If True, skip mermaid diagram conversion (faster)
    """
    if output_file is None:
        output_file = input_file.with_suffix(".docx")

    print(f"📄 Converting: {input_file.name}")
    print(f"   Output: {output_file.name}")

    # Read markdown content
    with open(input_file, encoding="utf-8") as f:
        md_content = f.read()

    # Extract document title
    doc_title = extract_document_title(md_content)

    # Extract and convert mermaid diagrams
    diagram_images = {}
    if not skip_mermaid:
        mermaid_diagrams = extract_mermaid_diagrams(md_content)
        if mermaid_diagrams:
            print(f"   Found {len(mermaid_diagrams)} mermaid diagrams")
            for placeholder_id, mermaid_code in mermaid_diagrams:
                image_data = mermaid_to_image(mermaid_code)
                if image_data:
                    diagram_images[placeholder_id] = image_data
                time.sleep(0.5)  # Be nice to the API

        # Replace mermaid diagrams with placeholders
        md_content = replace_mermaid_with_placeholders(md_content)

    # Create document
    doc = Document()

    # Set up styles
    setup_document_styles(doc)

    # Add title page
    title = doc.add_heading(doc_title, 0)
    title.alignment = WD_ALIGN_PARAGRAPH.CENTER

    doc.add_paragraph()
    subtitle = doc.add_paragraph(input_file.stem)
    subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
    subtitle.runs[0].font.size = Pt(14)
    subtitle.runs[0].font.color.rgb = RGBColor(0, 51, 102)

    doc.add_page_break()

    # Process content line by line for better control
    lines = md_content.split("\n")
    i = 0
    in_code_block = False
    code_lines = []
    in_table = False
    table_lines = []

    while i < len(lines):
        line = lines[i]

        # Code blocks
        if line.startswith("```"):
            if in_code_block:
                # End of code block
                code_text = "\n".join(code_lines)
                add_code_block(doc, code_text)
                code_lines = []
                in_code_block = False
            else:
                # Start of code block
                in_code_block = True
            i += 1
            continue

        if in_code_block:
            code_lines.append(line)
            i += 1
            continue

        # Tables (detect by | character)
        if "|" in line and line.strip().startswith("|"):
            if not in_table:
                in_table = True
                table_lines = []
            table_lines.append(line)
            i += 1
            # Check if next line is not a table
            if i >= len(lines) or "|" not in lines[i]:
                # Convert table
                table_md = "\n".join(table_lines)
                table_html = markdown.markdown(table_md, extensions=["tables"])
                add_table_from_markdown(doc, table_html)
                in_table = False
                table_lines = []
            continue

        # Headings
        if line.startswith("#"):
            level = len(line) - len(line.lstrip("#"))
            text = line.lstrip("#").strip()
            if text:
                add_heading(doc, text, min(level, 3))
            i += 1
            continue

        # Horizontal rules
        if line.strip() in ["---", "***", "___"]:
            doc.add_paragraph("_" * 80)
            i += 1
            continue

        # Lists
        if line.strip().startswith("- ") or line.strip().startswith("* "):
            text = line.strip()[2:]
            doc.add_paragraph(text, style="List Bullet")
            i += 1
            continue

        if re.match(r"^\d+\.\s", line.strip()):
            text = re.sub(r"^\d+\.\s", "", line.strip())
            doc.add_paragraph(text, style="List Number")
            i += 1
            continue

        # Check for mermaid diagram placeholders
        if line.strip().startswith("__MERMAID_DIAGRAM_"):
            placeholder = line.strip()
            if placeholder in diagram_images:
                # Add diagram title
                p = doc.add_paragraph()
                run = p.add_run("Diagram:")
                run.font.bold = True
                run.font.size = Pt(11)

                # Insert image
                try:
                    doc.add_picture(diagram_images[placeholder], width=Inches(6.0))
                    last_paragraph = doc.paragraphs[-1]
                    last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
                except Exception as e:
                    print(f"  ⚠️  Error inserting image: {e}")
                    doc.add_paragraph("[Diagram could not be embedded]")

                # Add spacing
                doc.add_paragraph()
            else:
                # Fallback if image wasn't generated
                doc.add_paragraph("[Mermaid diagram - see original markdown for visualization]")
            i += 1
            continue

        # Regular paragraphs
        if line.strip():
            # Handle inline formatting (bold, italic, code)
            p = doc.add_paragraph()
            process_inline_formatting(p, line)
        else:
            # Empty line - add spacing
            if i > 0 and i < len(lines) - 1:
                pass  # Skip empty lines between paragraphs

        i += 1

    # Save document
    print(f"Saving Word document: {output_file}")
    doc.save(output_file)
    print(f"✅ Conversion complete! Document saved to: {output_file}")


def convert_all_markdown_in_directory(directory: Path, skip_mermaid: bool = False):
    """Convert all markdown files in a directory to DOCX."""
    md_files = sorted(directory.glob("*.md"))

    if not md_files:
        print(f"ℹ️  No markdown files found in {directory}")
        return

    print(f"\n📂 Converting {len(md_files)} markdown files in {directory.name}/\n")

    success_count = 0
    for md_file in md_files:
        try:
            output_file = md_file.with_suffix(".docx")
            convert_markdown_to_docx(md_file, output_file, skip_mermaid=skip_mermaid)
            file_size = output_file.stat().st_size / 1024
            print(f"   ✅ Created: {output_file.name} ({file_size:.1f} KB)\n")
            success_count += 1
        except Exception as e:
            print(f"   ❌ Error: {e}\n")

    print(f"✨ Successfully converted {success_count}/{len(md_files)} files")


def main():
    """Main entry point."""
    parser = argparse.ArgumentParser(
        description="Convert Markdown files to DOCX format with formatting",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Convert a single file
  python scripts/convert_md_to_docx.py config/environments/abp-dev/SHACL_RULES_DOCUMENTATION.md

  # Convert with custom output name
  python scripts/convert_md_to_docx.py input.md output.docx

  # Convert all MD files in abp-dev directory
  python scripts/convert_md_to_docx.py --all

  # Convert all files without mermaid diagrams (faster)
  python scripts/convert_md_to_docx.py --all --skip-mermaid

  # Legacy mode (default file)
  python scripts/convert_md_to_docx.py
        """,
    )

    parser.add_argument(
        "input_file", nargs="?", help="Path to the markdown file to convert (default: SHACL_RULES_DOCUMENTATION.md)"
    )
    parser.add_argument("output_file", nargs="?", help="Optional output path for the DOCX file")
    parser.add_argument("--all", action="store_true", help="Convert all markdown files in config/environments/abp-dev/")
    parser.add_argument(
        "--skip-mermaid", action="store_true", help="Skip mermaid diagram conversion for faster processing"
    )

    args = parser.parse_args()

    print("=" * 80)
    print("Markdown to Word (DOCX) Converter")
    print("=" * 80)
    print()

    # Handle --all flag
    if args.all:
        abp_dev_dir = Path(__file__).parent.parent / "config/environments/abp-dev"
        if not abp_dev_dir.exists():
            print(f"❌ Directory not found: {abp_dev_dir}")
            sys.exit(1)
        convert_all_markdown_in_directory(abp_dev_dir, skip_mermaid=args.skip_mermaid)
        return

    # Determine input file
    if args.input_file:
        input_file = Path(args.input_file)
    else:
        # Legacy mode - use default file
        input_file = DEFAULT_INPUT

    if not input_file.exists():
        print(f"❌ Error: Input file not found: {input_file}")
        sys.exit(1)

    # Determine output file
    if args.output_file:
        output_file = Path(args.output_file)
    else:
        output_file = input_file.with_suffix(".docx")

    # Convert single file
    try:
        convert_markdown_to_docx(input_file, output_file, skip_mermaid=args.skip_mermaid)
        file_size = output_file.stat().st_size / 1024
        print(f"   ✅ Created: {output_file.name} ({file_size:.1f} KB)")
        print("\n✨ Conversion complete!")
    except Exception as e:
        print(f"❌ Error: {e}")
        import traceback

        traceback.print_exc()
        sys.exit(1)


if __name__ == "__main__":
    main()
