import os
import re
import html
import shutil
import hashlib
import tempfile
import subprocess
from pathlib import Path
from docutils import nodes
from docutils.parsers.rst import Directive
from sphinx.application import Sphinx
from sphinx.util import logging
from sphinx.util.fileutil import copy_asset_file

logger = logging.getLogger(__name__)

class ABCExtension:
    """Unified ABC extension that supports both RST and Markdown formats"""
    
    def __init__(self, app: Sphinx):
        self.app = app
        self.static_dir = Path(app.outdir) / "_static"
        
        # 为不同格式使用不同的输出目录，避免冲突
        self.abc_images_dir = self.static_dir / "_abc_images"
        self.abc_images_dir.mkdir(parents=True, exist_ok=True)
        
        # 检查abcm2ps是否可用
        self.abcm2ps_available = self._check_abcm2ps()
    
    def _check_abcm2ps(self) -> bool:
        """检查abcm2ps是否可用"""
        try:
            subprocess.run(["abcm2ps", "-V"], capture_output=True, text=True, check=True)
            return True
        except (subprocess.CalledProcessError, FileNotFoundError):
            logger.error("abcm2ps not found. Please install it. Refer to: https://github.com/lewdlime/abcm2ps")
            return False
    
    @staticmethod
    def ensure_svg_background(svg_path: Path):
        """确保SVG文件有白色背景"""
        try:
            with open(svg_path, 'r', encoding='utf-8') as f:
                content = f.read()

            # 如果已经有 background 样式，跳过
            if 'background' in content or 'rect' in content:
                return

            # 在 <svg> 标签内插入 style 属性
            if '<svg' in content:
                content = content.replace('<svg', '<svg style="background: white;"', 1)

            with open(svg_path, 'w', encoding='utf-8') as f:
                f.write(content)
        except Exception as e:
            logger.warning(f"Failed to add background to SVG: {e}")
    
    def _render_abc_to_svg(self, abc_content: str, options: dict | None = None, format_type: str = "rst") -> Path:
        """解析ABC代码并使用abcm2ps渲染为SVG文件, 并返回SVG文件路径

        :param str abc_content: ABC代码内容
        :param dict options: 选项字典, 包含staffwidth, scale, voicebreak, landscape
        :param str format_type: 解析文件的文件格式, defaults to "rst"
        :return Path: SVG文件路径
        """
        if not self.abcm2ps_available:
            raise RuntimeError("abcm2ps is not available")
        
        if not abc_content.strip():
            raise ValueError("ABC content is empty")
        
        # 选择适当的输出目录
        images_dir = self.abc_images_dir
        
        # 生成唯一文件名
        content_hash = hashlib.md5(abc_content.encode()).hexdigest()[:10]
        filename = f"abc_{content_hash}.svg"
        svg_path = images_dir / filename
        
        # 检查是否已存在渲染结果
        if svg_path.exists() and not self.app.config.abc_force_rebuild:
            return svg_path
        try:
            # 创建临时目录
            with tempfile.TemporaryDirectory() as tmpdir:
                tmp_path = Path(tmpdir)
                
                # 生成ABC文件
                abc_file = tmp_path / "input.abc"
                with abc_file.open('w') as f:
                    f.write(abc_content)
                
                # 构建abcm2ps命令
                output_file = tmp_path / "output"
                cmd = [
                    "abcm2ps",
                    "-g",  # 生成SVG格式
                    f"-O{output_file}",
                    str(abc_file)
                ]
                
                # 添加选项
                if options:
                    if 'staffwidth' in options and options['staffwidth']:
                        cmd.extend(["-w", str(options['staffwidth'])])
                    if 'scale' in options and options['scale']:
                        cmd.extend(["-s", str(options['scale'])])
                    if 'voicebreak' in options and options['voicebreak']:
                        cmd.extend(["-b", str(options['voicebreak'])])
                    if 'landscape' in options and options['landscape']:
                        cmd.extend(["-l"])
                
                # 执行渲染
                result = subprocess.run(
                    cmd,
                    capture_output=True,
                    text=True,
                    cwd=tmpdir
                )
                
                if result.returncode != 0:
                    error_msg = f"abcm2ps failed: {result.stderr}"
                    logger.error(error_msg)
                    raise RuntimeError(error_msg)
                
                # 查找生成的SVG文件
                svg_files = list(tmp_path.glob("output*.svg"))
                if not svg_files:
                    error_msg = "No SVG file generated by abcm2ps"
                    logger.error(error_msg)
                    raise RuntimeError(error_msg)
                
                # 复制到最终位置
                shutil.copy(svg_files[0], svg_path)
                self.ensure_svg_background(svg_path)
        
        except Exception as e:
            logger.error(f"ABC rendering error: {str(e)}")
            raise
        
        return svg_path
    
    def _get_svg_url(self, svg_path: Path) -> str:
        """生成正确的相对URL路径"""
        # 获取相对于 _static 目录的路径
        try:
            # 方法1：RTD特定处理
            if os.environ.get('READTHEDOCS', 'False') == 'True':
                # RTD需要相对路径
                rtd_version = os.environ.get('READTHEDOCS_VERSION', 'latest')
                rtd_language = os.environ.get('READTHEDOCS_LANGUAGE', 'en')
                
                # 构建RTD上的绝对路径
                # 注意：RTD的路径结构为 /{语言}/{版本}/_static/...
                prefix = f"/{rtd_language}/{rtd_version}"
                logger.info(f"RTD prefix: {prefix}")
                return f"{prefix}/_static/_abc_images/{svg_path.name}"
            # 方法2：本地环境
            else:
                logger.info(f"Not RTD: {svg_path}")
                return str(svg_path)
        except Exception as e:
            logger.error(f"Path calculation error: {e}")
            # 最终回退：只返回文件名
            return f"/_static/{svg_path.name}"

    def render_rst(self, abc_content: str, options: dict | None = None, caption: str | None  = None) -> list:
        """RST专用渲染方法"""
        try:
            svg_path = self._render_abc_to_svg(abc_content, options)
            svg_url = self._get_svg_url(svg_path=svg_path)
            safe_alt = html.escape(f"ABC notation - {svg_path.stem}")
            
            # 创建HTML
            raw_html = (
                f'<div class="abc-container" style="display: block; width: 100%; margin: 1.5rem 0; text-align: center;">'
                f'<img src="{svg_url}" class="abc-rendered" '
                f'alt="{safe_alt}" '
                f'style="max-width: 100%; height: auto; display: inline-block; '
                f'border: 1px solid #ddd; padding: 5px; background: white; '
                f'margin: 0 auto;" />'
                f'</div>'
            )

            # 创建 raw 节点
            raw_node = nodes.raw('', raw_html, format='html')

            # 如果有标题，用 figure 包裹
            if caption:
                caption_node = nodes.caption(text=caption)
                figure_node = nodes.figure('', raw_node, caption_node)
                return [figure_node]

            return [raw_node]
            
        except Exception as e:
            error_msg = f"ABC rendering error: {str(e)}"
            logger.error(error_msg, exc_info=True)  # ✅ 添加exc_info=True获取完整堆栈
            # ✅ 修复3: 返回可见的错误占位符
            error_html = (
                f'<div class="abc-error" style="border: 2px dashed #dc3232; padding: 1rem; '
                f'margin: 1rem 0; background: #fff8f8; color: #dc3232;">'
                f'<p>❌ ABC Rendering Error: {html.escape(str(e))}</p>'
                f'<pre style="background: #f8f8f8; padding: 0.5rem; font-family: monospace; '
                f'font-size: 0.9em; max-height: 100px; overflow: auto; margin-top: 0.5rem;">'
                f'{html.escape(abc_content[:100])}...</pre>'
                f'</div>'
            )
            return [nodes.raw('', error_html, format='html')]
    
    def render_md(self, abc_content: str) -> str:
        """Markdown专用渲染方法"""
        try:
            svg_path = self._render_abc_to_svg(abc_content, None)
            svg_url = self._get_svg_url(svg_path=svg_path)
            safe_alt = html.escape(f"ABC notation - {svg_path.stem}")
            logger.info(f"SVG_stem: {svg_path.stem}")
            
            # 创建图像标签
            return (
                f'<div class="abc-container" style="display: block; width: 100%; margin: 1.5rem 0; text-align: center;">'
                f'<img src="{svg_url}" alt="{safe_alt}" class="abc-rendered" '
                f'style="max-width: 100%; height: auto; display: inline-block; '
                f'border: 1px solid #ddd; padding: 5px; background: white; '
                f'margin: 0 auto;">'
                f'</div>'
            )
        except Exception as e:
            logger.error(f"ABC processing error: {str(e)}", exc_info=True)
            safe_error = html.escape(str(e))
            safe_content = html.escape(abc_content[:100])
            return (
                f'<div class="abc-error" style="border: 2px dashed #dc3232; padding: 1rem; '
                f'margin: 1rem 0; background: #fff8f8; color: #dc3232;">'
                f'<p>❌ ABC Error: {safe_error}</p>'
                f'<pre style="background: #f8f8f8; padding: 0.5rem; font-family: monospace; '
                f'font-size: 0.9em; max-height: 100px; overflow: auto; margin-top: 0.5rem;">'
                f'{safe_content}...</pre>'
                f'</div>'
            )


class ABCDirective(Directive):
    """Sphinx directive for rendering ABC music notation in RST files"""
    required_arguments = 0
    optional_arguments = 1
    final_argument_whitespace = True
    has_content = True
    
    option_spec = {
        'staffwidth': int,
        'scale': float,
        'voicebreak': int,
        'landscape': bool,
    }

    def run(self):
        # 获取ABC代码内容
        abc_content = '\n'.join(self.content)
        
        if not abc_content.strip():
            return [nodes.error(text="ABC directive has no content")]
        
        # 获取指令选项和标题
        options = dict(self.options)
        caption = self.arguments[0] if self.arguments else None
        
        # 通过app获取统一扩展
        app = self.state.document.settings.env.app
        if not hasattr(app, 'abc_extension'):
            app.abc_extension = ABCExtension(app)
        
        # 使用统一扩展渲染
        return app.abc_extension.render_rst(abc_content, options, caption)


def on_source_read(app: Sphinx, docname: str, source: list):
    """读取源文件时处理ABC代码块"""
    
    # 正确获取源文件路径并检查扩展名
    source_file = app.env.doc2path(docname)
    
    # 检查是否是Markdown文件
    if str(source_file).endswith('.md'):
        # 初始化扩展（如果尚未初始化）
        if not hasattr(app, 'abc_extension'):
            app.abc_extension = ABCExtension(app)
        
        # 改进的正则表达式，匹配各种代码块变体
        pattern = r'(?P<indent>[ \t]*)```(?P<lang>abc|ABC)[ \t]*([^\n]*\n)?(?P<content>(?:.|\n)*?)(?P=indent)```'
        
        def replace_abc_block(match):
            indent = match.group('indent')
            content = match.group('content').strip()
            return indent + app.abc_extension.render_md(content)
        
        # 处理Markdown中的ABC代码块
        source[0] = re.sub(
            pattern, 
            replace_abc_block, 
            source[0], 
            flags=re.IGNORECASE | re.MULTILINE
        )


# def copy_abc_assets(app, exception):
#     """复制ABC扩展的静态资源文件"""
#     if exception:
#         logger.warning("Exception occurred, skipping ABC assets copy")
#         return
    
#     # 获取源CSS文件路径
#     src_dir = os.path.dirname(__file__)
#     css_src = os.path.join(src_dir, "abc.css")
    
#     # 目标目录
#     static_dir = os.path.join(app.outdir, "_static")
    
#     # 确保目标目录存在
#     os.makedirs(static_dir, exist_ok=True)
    
#     # 检查源文件是否存在
#     if os.path.isfile(css_src):
#         try:
#             copy_asset_file(css_src, static_dir)
#             logger.info(f"Copied ABC CSS to {static_dir}")
#         except Exception as e:
#             logger.warning(f"Failed to copy ABC CSS: {str(e)}")
#     else:
#         logger.warning(f"ABC CSS file not found: {css_src}")


def copy_abc_assets(app, exception):
    """确保所有ABC图像被复制到RTD可访问的位置"""
    if exception:
        return
    
    # ✅ 修复2：RTD模式检测
    on_rtd = os.environ.get('READTHEDOCS', 'False') == 'True'
    
    static_dir = Path(app.outdir) / '_static'
    abc_images_dir = static_dir / '_abc_images'
    
    if not abc_images_dir.exists():
        return
    
    # ✅ 修复3：复制文件到多个位置（RTD兼容）
    for svg_file in abc_images_dir.glob('*.svg'):
        # 位置1：_static根目录（RTD最可靠）
        root_dest = static_dir / svg_file.name
        if not root_dest.exists():
            shutil.copy2(svg_file, root_dest)
            logger.info(f"Copied to root static: {root_dest}")
        
        # 位置2：保留原始位置
        sub_dest = abc_images_dir / svg_file.name
        if not sub_dest.exists():
            shutil.copy2(svg_file, sub_dest)
            logger.info(f"Copied to subdirectory: {sub_dest}")
        
        # ✅ 修复4：RTD特定：创建符号链接
        if on_rtd:
            link_path = static_dir / f"{svg_file.stem}.svg"
            if not link_path.exists():
                try:
                    os.symlink(svg_file.absolute(), link_path.absolute())
                    logger.info(f"Created symlink: {link_path}")
                except Exception as e:
                    logger.warning(f"Failed to create symlink: {e}")


def setup(app: Sphinx):
    """设置统一的ABC扩展"""
    
    # 设置RST处理
    app.add_directive("abc", ABCDirective)
    # 添加共享配置
    app.add_config_value("abc_force_rebuild", False, "env")
    app.connect("builder-inited", lambda app: 
        (Path(app.outdir) / "_static/_abc_images").mkdir(parents=True, exist_ok=True))
    
    app.connect("source-read", on_source_read)  # 设置Markdown处理
    app.connect("build-finished", copy_abc_assets)  # 复制CSS资源
    app.add_css_file("abc.css")  # 添加CSS
    
    return {
        'version': '1.2',
        'parallel_read_safe': True,
        'parallel_write_safe': True
    }
