"""
Tests for Effects web UI.

Tests effect metadata parsing, listing, and templates.
"""

import pytest
import re
from pathlib import Path
from unittest.mock import MagicMock


def parse_effect_metadata_standalone(source: str) -> dict:
    """
    Standalone copy of parse_effect_metadata for testing.

    This avoids import issues with the router module.
    """
    metadata = {
        "name": "",
        "version": "1.0.0",
        "author": "",
        "temporal": False,
        "description": "",
        "params": [],
        "dependencies": [],
        "requires_python": ">=3.10",
    }

    # Parse PEP 723 dependencies
    pep723_match = re.search(r"# /// script\n(.*?)# ///", source, re.DOTALL)
    if pep723_match:
        block = pep723_match.group(1)
        deps_match = re.search(r'# dependencies = \[(.*?)\]', block, re.DOTALL)
        if deps_match:
            metadata["dependencies"] = re.findall(r'"([^"]+)"', deps_match.group(1))
        python_match = re.search(r'# requires-python = "([^"]+)"', block)
        if python_match:
            metadata["requires_python"] = python_match.group(1)

    # Parse docstring @-tags
    docstring_match = re.search(r'"""(.*?)"""', source, re.DOTALL)
    if not docstring_match:
        docstring_match = re.search(r"'''(.*?)'''", source, re.DOTALL)

    if docstring_match:
        docstring = docstring_match.group(1)
        lines = docstring.split("\n")

        current_param = None
        desc_lines = []
        in_description = False

        for line in lines:
            stripped = line.strip()

            if stripped.startswith("@effect "):
                metadata["name"] = stripped[8:].strip()
                in_description = False

            elif stripped.startswith("@version "):
                metadata["version"] = stripped[9:].strip()

            elif stripped.startswith("@author "):
                metadata["author"] = stripped[8:].strip()

            elif stripped.startswith("@temporal "):
                val = stripped[10:].strip().lower()
                metadata["temporal"] = val in ("true", "yes", "1")

            elif stripped.startswith("@description"):
                in_description = True
                desc_lines = []

            elif stripped.startswith("@param "):
                if in_description:
                    metadata["description"] = " ".join(desc_lines)
                    in_description = False
                if current_param:
                    metadata["params"].append(current_param)
                parts = stripped[7:].split()
                if len(parts) >= 2:
                    current_param = {
                        "name": parts[0],
                        "type": parts[1],
                        "description": "",
                    }
                else:
                    current_param = None

            elif stripped.startswith("@range ") and current_param:
                range_parts = stripped[7:].split()
                if len(range_parts) >= 2:
                    try:
                        current_param["range"] = [float(range_parts[0]), float(range_parts[1])]
                    except ValueError:
                        pass

            elif stripped.startswith("@default ") and current_param:
                current_param["default"] = stripped[9:].strip()

            elif stripped.startswith("@example"):
                if in_description:
                    metadata["description"] = " ".join(desc_lines)
                    in_description = False
                if current_param:
                    metadata["params"].append(current_param)
                    current_param = None

            elif in_description and stripped:
                desc_lines.append(stripped)

            elif current_param and stripped and not stripped.startswith("@"):
                current_param["description"] = stripped

        if in_description:
            metadata["description"] = " ".join(desc_lines)

        if current_param:
            metadata["params"].append(current_param)

    return metadata


class TestEffectMetadataParsing:
    """Tests for parse_effect_metadata function."""

    def test_parses_pep723_dependencies(self) -> None:
        """Should extract dependencies from PEP 723 script block."""
        source = '''
# /// script
# requires-python = ">=3.10"
# dependencies = ["numpy", "opencv-python"]
# ///
"""
@effect test_effect
"""
def process_frame(frame, params, state):
    return frame, state
'''
        meta = parse_effect_metadata_standalone(source)

        assert meta["dependencies"] == ["numpy", "opencv-python"]
        assert meta["requires_python"] == ">=3.10"

    def test_parses_effect_name(self) -> None:
        """Should extract effect name from @effect tag."""
        source = '''
"""
@effect brightness
@version 2.0.0
@author @artist@example.com
"""
def process_frame(frame, params, state):
    return frame, state
'''
        meta = parse_effect_metadata_standalone(source)

        assert meta["name"] == "brightness"
        assert meta["version"] == "2.0.0"
        assert meta["author"] == "@artist@example.com"

    def test_parses_parameters(self) -> None:
        """Should extract parameter definitions."""
        source = '''
"""
@effect brightness
@param level float
@range -1.0 1.0
@default 0.0
Brightness adjustment level
"""
def process_frame(frame, params, state):
    return frame, state
'''
        meta = parse_effect_metadata_standalone(source)

        assert len(meta["params"]) == 1
        param = meta["params"][0]
        assert param["name"] == "level"
        assert param["type"] == "float"
        assert param["range"] == [-1.0, 1.0]
        assert param["default"] == "0.0"

    def test_parses_temporal_flag(self) -> None:
        """Should parse temporal flag correctly."""
        source_temporal = '''
"""
@effect motion_blur
@temporal true
"""
def process_frame(frame, params, state):
    return frame, state
'''
        source_not_temporal = '''
"""
@effect brightness
@temporal false
"""
def process_frame(frame, params, state):
    return frame, state
'''

        assert parse_effect_metadata_standalone(source_temporal)["temporal"] is True
        assert parse_effect_metadata_standalone(source_not_temporal)["temporal"] is False

    def test_handles_missing_metadata(self) -> None:
        """Should return sensible defaults for minimal source."""
        source = '''
def process_frame(frame, params, state):
    return frame, state
'''
        meta = parse_effect_metadata_standalone(source)

        assert meta["name"] == ""
        assert meta["version"] == "1.0.0"
        assert meta["dependencies"] == []
        assert meta["params"] == []

    def test_parses_description(self) -> None:
        """Should extract description text."""
        source = '''
"""
@effect test
@description
This is a multi-line
description of the effect.
@param x float
"""
def process_frame(frame, params, state):
    return frame, state
'''
        meta = parse_effect_metadata_standalone(source)

        assert "multi-line" in meta["description"]


class TestHomePageEffectsCount:
    """Test that effects count is shown on home page."""

    def test_home_template_has_effects_card(self) -> None:
        """Home page should display effects count."""
        path = Path('/home/giles/art/art-celery/app/templates/home.html')
        content = path.read_text()

        assert 'stats.effects' in content, \
            "Home page should display stats.effects count"
        assert 'href="/effects"' in content, \
            "Home page should link to /effects"

    def test_home_route_provides_effects_count(self) -> None:
        """Home route should provide effects count in stats."""
        path = Path('/home/giles/art/art-celery/app/routers/home.py')
        content = path.read_text()

        assert 'stats["effects"]' in content, \
            "Home route should populate stats['effects']"


class TestNavigationIncludesEffects:
    """Test that Effects link is in navigation."""

    def test_base_template_has_effects_link(self) -> None:
        """Base template should have Effects navigation link."""
        base_path = Path('/home/giles/art/art-celery/app/templates/base.html')
        content = base_path.read_text()

        assert 'href="/effects"' in content
        assert "Effects" in content
        assert "active_tab == 'effects'" in content

    def test_effects_link_between_recipes_and_media(self) -> None:
        """Effects link should be positioned between Recipes and Media."""
        base_path = Path('/home/giles/art/art-celery/app/templates/base.html')
        content = base_path.read_text()

        recipes_pos = content.find('href="/recipes"')
        effects_pos = content.find('href="/effects"')
        media_pos = content.find('href="/media"')

        assert recipes_pos < effects_pos < media_pos, \
            "Effects link should be between Recipes and Media"


class TestEffectsTemplatesExist:
    """Tests for effects template files."""

    def test_list_template_exists(self) -> None:
        """List template should exist."""
        path = Path('/home/giles/art/art-celery/app/templates/effects/list.html')
        assert path.exists(), "effects/list.html template should exist"

    def test_detail_template_exists(self) -> None:
        """Detail template should exist."""
        path = Path('/home/giles/art/art-celery/app/templates/effects/detail.html')
        assert path.exists(), "effects/detail.html template should exist"

    def test_list_template_extends_base(self) -> None:
        """List template should extend base.html."""
        path = Path('/home/giles/art/art-celery/app/templates/effects/list.html')
        content = path.read_text()
        assert '{% extends "base.html" %}' in content

    def test_detail_template_extends_base(self) -> None:
        """Detail template should extend base.html."""
        path = Path('/home/giles/art/art-celery/app/templates/effects/detail.html')
        content = path.read_text()
        assert '{% extends "base.html" %}' in content

    def test_list_template_has_upload_button(self) -> None:
        """List template should have upload functionality."""
        path = Path('/home/giles/art/art-celery/app/templates/effects/list.html')
        content = path.read_text()
        assert 'Upload Effect' in content or 'upload' in content.lower()

    def test_detail_template_shows_parameters(self) -> None:
        """Detail template should display parameters."""
        path = Path('/home/giles/art/art-celery/app/templates/effects/detail.html')
        content = path.read_text()
        assert 'params' in content.lower() or 'parameter' in content.lower()

    def test_detail_template_shows_source_code(self) -> None:
        """Detail template should show source code."""
        path = Path('/home/giles/art/art-celery/app/templates/effects/detail.html')
        content = path.read_text()
        assert 'source' in content.lower()
        assert 'language-python' in content

    def test_detail_template_shows_dependencies(self) -> None:
        """Detail template should display dependencies."""
        path = Path('/home/giles/art/art-celery/app/templates/effects/detail.html')
        content = path.read_text()
        assert 'dependencies' in content.lower()


class TestEffectsRouterExists:
    """Tests for effects router configuration."""

    def test_effects_router_file_exists(self) -> None:
        """Effects router should exist."""
        path = Path('/home/giles/art/art-celery/app/routers/effects.py')
        assert path.exists(), "effects.py router should exist"

    def test_effects_router_has_list_endpoint(self) -> None:
        """Effects router should have list endpoint."""
        path = Path('/home/giles/art/art-celery/app/routers/effects.py')
        content = path.read_text()
        assert '@router.get("")' in content or "@router.get('')" in content

    def test_effects_router_has_detail_endpoint(self) -> None:
        """Effects router should have detail endpoint."""
        path = Path('/home/giles/art/art-celery/app/routers/effects.py')
        content = path.read_text()
        assert '@router.get("/{cid}")' in content

    def test_effects_router_has_upload_endpoint(self) -> None:
        """Effects router should have upload endpoint."""
        path = Path('/home/giles/art/art-celery/app/routers/effects.py')
        content = path.read_text()
        assert '@router.post("/upload")' in content

    def test_effects_router_renders_templates(self) -> None:
        """Effects router should render HTML templates."""
        path = Path('/home/giles/art/art-celery/app/routers/effects.py')
        content = path.read_text()
        assert 'effects/list.html' in content
        assert 'effects/detail.html' in content
