"""ThemeBlocksMixin - block rendering and content filter methods."""

import json
import re
from typing import Any

from markupsafe import escape


class ThemeBlocksMixin:
    """
    Block rendering and content filter mixin.

    Mixed into ThemeService to provide Editor.js block rendering,
    HTML sanitization, and content filtering.
    """

    def _date_filter(self, value: Any, format: str = "%Y-%m-%d") -> str:
        """Format date/datetime value."""
        if value is None:
            return ""
        if isinstance(value, str):
            # Try to parse ISO format string
            try:
                from datetime import datetime
                value = datetime.fromisoformat(value.replace("Z", "+00:00"))
            except (ValueError, AttributeError):
                return value[:10] if len(value) >= 10 else value
        if hasattr(value, "strftime"):
            return value.strftime(format)
        return str(value)

    def _excerpt(self, content: Any, length: int = 200) -> str:
        """Extract plain text excerpt from content."""
        if not content:
            return ""

        text = ""

        # Handle dict (Editor.js blocks)
        if isinstance(content, dict) and "blocks" in content:
            texts = []
            for block in content.get("blocks", []):
                if block.get("type") in ("paragraph", "header"):
                    block_text = block.get("data", {}).get("text", "")
                    # Strip HTML tags
                    import re

                    block_text = re.sub(r"<[^>]+>", "", block_text)
                    texts.append(block_text)
            text = " ".join(texts)
        elif isinstance(content, str):
            # Try parsing as JSON
            try:
                data = json.loads(content)
                return self._excerpt(data, length)
            except (json.JSONDecodeError, TypeError):
                import re

                text = re.sub(r"<[^>]+>", "", content)
        else:
            text = str(content)

        # Truncate
        if len(text) <= length:
            return text
        return text[: length - 3].rsplit(" ", 1)[0] + "..."

    def _sanitize_url(self, url: str) -> str:
        """Sanitize URL to prevent XSS via javascript: or data: URLs."""
        if not url:
            return ""
        url = url.strip()
        # Allow only safe protocols
        if url.startswith(("http://", "https://", "/", "./", "../")):
            return escape(url)
        # Block javascript:, data:, vbscript:, etc.
        return ""

    def _sanitize_html(self, html: str, allow_tags: set = None) -> str:
        """Sanitize HTML allowing only specific tags with safe style attributes."""
        if allow_tags is None:
            allow_tags = {"b", "i", "strong", "em", "a", "br", "u", "mark", "code", "s", "span"}

        import re
        # Safe CSS properties for inline styles
        safe_style_pattern = re.compile(
            r"^(color|background|background-color)\s*:\s*"
            r"(#[0-9a-fA-F]{3,6}|rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\)|[a-zA-Z]+)\s*;?\s*$"
        )

        def sanitize_style(style_value: str) -> str:
            """Validate and sanitize style attribute value."""
            if not style_value:
                return ""
            # Split by semicolon and validate each property
            safe_styles = []
            for prop in style_value.split(";"):
                prop = prop.strip()
                if prop and safe_style_pattern.match(prop):
                    safe_styles.append(prop)
            return "; ".join(safe_styles) if safe_styles else ""

        def replace_tag(match):
            full_tag = match.group(1)
            tag_name = full_tag.lower().split()[0]
            clean_tag = tag_name.lstrip("/")

            if clean_tag not in allow_tags:
                return escape(match.group(0))

            # For closing tags, return as-is
            if tag_name.startswith("/"):
                return match.group(0)

            # Extract and sanitize style attribute
            style_match = re.search(r'style\s*=\s*["\']([^"\']*)["\']', full_tag, re.I)
            if style_match and clean_tag in ("span", "mark"):
                safe_style = sanitize_style(style_match.group(1))
                if safe_style:
                    return f'<{clean_tag} style="{safe_style}">'
                return f"<{clean_tag}>"

            # For tags that shouldn't have style, strip attributes
            if clean_tag in ("span", "mark", "s"):
                return f"<{clean_tag}>"

            return match.group(0)

        return re.sub(r"<(/?\w+[^>]*)>", replace_tag, html)

    def _validate_color(self, color: str) -> str:
        """Validate hex color, return empty string if invalid."""
        import re
        if color and re.match(r"^#[0-9a-fA-F]{6}$", color):
            return color
        return ""

    def _render_blocks(self, content: Any) -> str:
        """Render Editor.js blocks to HTML."""
        if not content:
            return ""

        data = content

        # Handle string content (possibly JSON)
        if isinstance(content, str):
            try:
                data = json.loads(content)
                # Handle double-encoded JSON (legacy data)
                if isinstance(data, str):
                    data = json.loads(data.replace("\\!", "!"))
            except json.JSONDecodeError:
                return content

        if not isinstance(data, dict) or "blocks" not in data:
            return str(content)

        html_parts = []
        for block in data.get("blocks", []):
            block_type = block.get("type")
            block_data = block.get("data", {})

            if block_type == "paragraph":
                text = self._sanitize_html(block_data.get("text", ""))
                tunes = block.get("tunes", {})
                alignment = tunes.get("alignmentTune", {}).get("alignment", "left")
                text_color = self._validate_color(
                    tunes.get("colorTune", {}).get("textColor", "")
                )
                bg_color = self._validate_color(
                    tunes.get("colorTune", {}).get("backgroundColor", "")
                )

                styles = []
                if alignment in ("center", "right"):
                    styles.append(f"text-align: {alignment}")
                if text_color:
                    styles.append(f"color: {text_color}")
                if bg_color:
                    styles.append(f"background: {bg_color}")
                    styles.append("padding: 1rem")
                    styles.append("border-radius: 0.5rem")

                style_attr = f' style="{"; ".join(styles)}"' if styles else ""
                html_parts.append(f"<p{style_attr}>{text}</p>")

            elif block_type == "header":
                level = int(block_data.get("level", 2))
                level = max(1, min(6, level))
                text = escape(block_data.get("text", ""))
                tunes = block.get("tunes", {})
                alignment = tunes.get("alignmentTune", {}).get("alignment", "left")
                text_color = self._validate_color(
                    tunes.get("colorTune", {}).get("textColor", "")
                )
                bg_color = self._validate_color(
                    tunes.get("colorTune", {}).get("backgroundColor", "")
                )

                styles = []
                if alignment in ("center", "right"):
                    styles.append(f"text-align: {alignment}")
                if text_color:
                    styles.append(f"color: {text_color}")
                if bg_color:
                    styles.append(f"background: {bg_color}")
                    styles.append("padding: 1rem")
                    styles.append("border-radius: 0.5rem")

                style_attr = f' style="{"; ".join(styles)}"' if styles else ""
                html_parts.append(f"<h{level}{style_attr}>{text}</h{level}>")

            elif block_type == "list":
                style = block_data.get("style", "unordered")
                tag = "ol" if style == "ordered" else "ul"
                items = block_data.get("items", [])
                items_html = "".join(f"<li>{item}</li>" for item in items)
                html_parts.append(f"<{tag}>{items_html}</{tag}>")

            elif block_type == "image":
                file_data = block_data.get("file", {})
                url = self._sanitize_url(file_data.get("url", ""))
                caption = escape(block_data.get("caption", ""))
                if url:
                    html_parts.append(
                        f'<figure><img src="{url}" alt="{caption}" loading="lazy"><figcaption>{caption}</figcaption></figure>'
                    )

            elif block_type == "quote":
                text = block_data.get("text", "")
                caption = block_data.get("caption", "")
                html_parts.append(f"<blockquote><p>{text}</p><cite>{caption}</cite></blockquote>")

            elif block_type == "code":
                code = escape(block_data.get("code", ""))
                html_parts.append(f"<pre><code>{code}</code></pre>")

            elif block_type == "delimiter":
                html_parts.append("<hr>")

            elif block_type == "table":
                content = block_data.get("content", [])
                rows = []
                for row in content:
                    cells = "".join(f"<td>{self._sanitize_html(cell)}</td>" for cell in row)
                    rows.append(f"<tr>{cells}</tr>")
                html_parts.append(f'<table>{"".join(rows)}</table>')

            elif block_type == "raw":
                # Sanitize raw HTML to prevent XSS
                from .sanitizer import sanitizer_service

                raw_html = block_data.get("html", "")
                html_parts.append(sanitizer_service.sanitize(raw_html))

            elif block_type == "embed":
                service = escape(block_data.get("service", ""))
                embed = self._sanitize_url(block_data.get("embed", ""))
                caption = escape(block_data.get("caption", ""))
                caption_html = (
                    f'<figcaption class="embed__caption">{caption}</figcaption>' if caption else ""
                )
                if not embed:
                    continue

                # サービスごとに最適なiframe設定
                if service == "youtube":
                    iframe = f'<iframe src="{embed}" width="100%" height="400" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'
                elif service == "vimeo":
                    iframe = f'<iframe src="{embed}" width="100%" height="400" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>'
                elif service == "googleMaps":
                    iframe = f'<iframe src="{embed}" width="100%" height="400" style="border:0" allowfullscreen loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>'
                elif service == "spotify":
                    iframe = f'<iframe src="{embed}" width="100%" height="352" frameborder="0" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>'
                elif service == "soundcloud":
                    iframe = f'<iframe src="{embed}" width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay"></iframe>'
                elif service == "twitter":
                    iframe = (
                        f'<iframe src="{embed}" width="100%" height="500" frameborder="0"></iframe>'
                    )
                elif service == "instagram":
                    iframe = f'<iframe src="{embed}" width="100%" height="600" frameborder="0" scrolling="no" allowtransparency="true"></iframe>'
                elif service == "twitch":
                    iframe = f'<iframe src="{embed}" width="100%" height="400" frameborder="0" allowfullscreen></iframe>'
                else:
                    iframe = f'<iframe src="{embed}" width="100%" height="400" frameborder="0" allowfullscreen></iframe>'

                html_parts.append(
                    f'<figure class="embed embed--{service}">{iframe}{caption_html}</figure>'
                )

            elif block_type == "checklist":
                items = block_data.get("items", [])
                items_html = []
                for item in items:
                    checked = "checked" if item.get("checked") else ""
                    text = self._sanitize_html(item.get("text", ""))
                    items_html.append(
                        f'<li class="checklist-item"><input type="checkbox" {checked} disabled><span>{text}</span></li>'
                    )
                html_parts.append(f'<ul class="checklist">{"".join(items_html)}</ul>')

            elif block_type == "alert":
                alert_type = escape(block_data.get("type", "info"))
                message = self._sanitize_html(block_data.get("message", ""))
                html_parts.append(f'<div class="alert alert--{alert_type}">{message}</div>')

            elif block_type == "button":
                text = escape(block_data.get("text", "ボタン"))
                url = self._sanitize_url(block_data.get("url", "#")) or "#"
                style = escape(block_data.get("style", "primary"))
                html_parts.append(
                    f'<div class="button-block"><a href="{url}" class="btn btn--{style}">{text}</a></div>'
                )

            elif block_type == "linkCard":
                url = self._sanitize_url(block_data.get("url", ""))
                title = escape(block_data.get("title", ""))
                description = escape(block_data.get("description", ""))
                image = self._sanitize_url(block_data.get("image", ""))
                img_html = f'<img src="{image}" alt="" class="linkcard__image">' if image else ""
                if url:
                    html_parts.append(
                        f"""<a href="{url}" class="linkcard" target="_blank" rel="noopener">
                        {img_html}
                        <div class="linkcard__content">
                            <div class="linkcard__title">{title}</div>
                            <div class="linkcard__description">{description}</div>
                        </div>
                    </a>"""
                    )

            elif block_type == "columns":
                cols = block_data.get("content", [])
                cols_html = "".join(
                    f'<div class="column">{self._sanitize_html(col)}</div>' for col in cols
                )
                html_parts.append(f'<div class="columns columns--{len(cols)}">{cols_html}</div>')

            elif block_type == "map":
                src = self._sanitize_url(block_data.get("src", ""))
                caption = escape(block_data.get("caption", ""))
                caption_html = (
                    f'<figcaption class="map__caption">{caption}</figcaption>' if caption else ""
                )
                if src:
                    html_parts.append(
                        f"""<figure class="map">
                        <iframe src="{src}" width="100%" height="400" style="border:0" allowfullscreen loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
                        {caption_html}
                    </figure>"""
                    )

            elif block_type == "video":
                url = block_data.get("url", "")
                caption = escape(block_data.get("caption", ""))
                caption_html = (
                    f'<figcaption class="video__caption">{caption}</figcaption>' if caption else ""
                )

                # YouTube - extract video ID safely
                yt_match = re.search(r"(?:youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]+)", url)
                if yt_match:
                    video_id = escape(yt_match.group(1))
                    html_parts.append(
                        f"""<figure class="video video--youtube">
                        <iframe src="https://www.youtube.com/embed/{video_id}" width="100%" height="400" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
                        {caption_html}
                    </figure>"""
                    )
                    continue

                # Vimeo - extract video ID safely
                vimeo_match = re.search(r"vimeo\.com/(\d+)", url)
                if vimeo_match:
                    video_id = escape(vimeo_match.group(1))
                    html_parts.append(
                        f"""<figure class="video video--vimeo">
                        <iframe src="https://player.vimeo.com/video/{video_id}" width="100%" height="400" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
                        {caption_html}
                    </figure>"""
                    )
                    continue

                # Direct video (mp4, webm, ogg)
                safe_url = self._sanitize_url(url)
                if safe_url and re.search(r"\.(mp4|webm|ogg)(\?.*)?$", url, re.IGNORECASE):
                    html_parts.append(
                        f"""<figure class="video video--native">
                        <video src="{safe_url}" controls width="100%"></video>
                        {caption_html}
                    </figure>"""
                    )
                    continue

                # Generic iframe - only for safe URLs
                safe_url = self._sanitize_url(url)
                if safe_url:
                    html_parts.append(
                        f"""<figure class="video">
                        <iframe src="{safe_url}" width="100%" height="400" frameborder="0" allowfullscreen></iframe>
                        {caption_html}
                    </figure>"""
                    )

            elif block_type == "spacer":
                height = int(block_data.get("height", 50))
                height = max(10, min(500, height))
                html_parts.append(
                    f'<div class="spacer" style="height: {height}px;" aria-hidden="true"></div>'
                )

            elif block_type == "group":
                content = self._sanitize_html(block_data.get("content", ""))
                bg_color = escape(block_data.get("backgroundColor", "#f8fafc"))
                padding = int(block_data.get("padding", 16))
                padding = max(0, min(100, padding))
                # Validate hex color
                import re
                if not re.match(r'^#[0-9a-fA-F]{6}$', bg_color):
                    bg_color = "#f8fafc"
                html_parts.append(
                    f'<div class="group" style="background: {bg_color}; padding: {padding}px;">{content}</div>'
                )

            elif block_type == "cover":
                image_url = self._sanitize_url(block_data.get("imageUrl", ""))
                title = self._sanitize_html(block_data.get("title", ""))
                subtitle = self._sanitize_html(block_data.get("subtitle", ""))
                overlay_color = escape(block_data.get("overlayColor", "#000000"))
                overlay_opacity = float(block_data.get("overlayOpacity", 0.5))
                height = int(block_data.get("height", 400))
                height = max(100, min(1000, height))
                overlay_opacity = max(0, min(1, overlay_opacity))
                # Validate hex color
                import re
                if not re.match(r'^#[0-9a-fA-F]{6}$', overlay_color):
                    overlay_color = "#000000"
                r = int(overlay_color[1:3], 16)
                g = int(overlay_color[3:5], 16)
                b = int(overlay_color[5:7], 16)
                overlay_rgba = f"rgba({r}, {g}, {b}, {overlay_opacity})"
                bg_style = f'background-image: url({image_url});' if image_url else 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);'
                subtitle_html = f'<p class="cover__subtitle">{subtitle}</p>' if subtitle else ""
                html_parts.append(
                    f'<section class="cover" style="min-height: {height}px; {bg_style}">'
                    f'<div class="cover__overlay" style="background: {overlay_rgba};"></div>'
                    f'<div class="cover__content">'
                    f'<h2 class="cover__title">{title}</h2>'
                    f'{subtitle_html}'
                    f'</div>'
                    f'</section>'
                )

            elif block_type == "gallery":
                images = block_data.get("images", [])
                columns = int(block_data.get("columns", 3))
                columns = max(2, min(4, columns))
                caption = escape(block_data.get("caption", ""))
                caption_html = f'<figcaption class="gallery__caption">{caption}</figcaption>' if caption else ""
                images_html = []
                for img in images:
                    url = self._sanitize_url(img.get("url", ""))
                    if url:
                        images_html.append(f'<div class="gallery__item"><img src="{url}" alt="" loading="lazy"></div>')
                if images_html:
                    html_parts.append(
                        f'<figure class="gallery gallery--{columns}">'
                        f'<div class="gallery__grid">{"".join(images_html)}</div>'
                        f'{caption_html}</figure>'
                    )

        return "\n".join(html_parts)

    def render_blocks_html(self, content: Any) -> str:
        """Render Editor.js blocks to HTML (public API for preview)."""
        return self._render_blocks(content)
