"""
Test generators and parametrize helpers powered by SST.

These functions generate test parameters from the YAML SST, enabling
data-driven testing where test cases come directly from configuration.

Usage:
    from lightwave.schema.testing import sst_param_tenants, sst_param_routes

    # Test runs for each tenant defined in SST
    @pytest.mark.parametrize("tenant", sst_param_tenants())
    def test_tenant_has_domain(tenant):
        assert tenant.get_domain("primary") is not None

    # Test runs for each route defined in SST
    @pytest.mark.parametrize("route", sst_param_routes())
    def test_route_has_name(route):
        assert route.name is not None
"""

from __future__ import annotations

from typing import Any

from lightwave.schema.core.registry import (
    BlueprintEntry,
    EntryPointEntry,
    LayoutEntry,
    RouteEntry,
    TenantEntry,
    UserFlowEntry,
    UserTypeEntry,
    build_registry,
)

# Cache registry for parametrize functions (loaded at collection time)
_cached_registry = None


def _get_registry():
    """Get or create cached registry for parametrize."""
    global _cached_registry
    if _cached_registry is None:
        _cached_registry = build_registry(use_db=False)
    return _cached_registry


def sst_param_routes(
    layout: str | None = None,
    requires: str | None = None,
    named_only: bool = True,
) -> list[RouteEntry]:
    """
    Get routes for pytest.mark.parametrize.

    Args:
        layout: Filter by layout name (optional)
        requires: Filter by auth requirement (optional)
        named_only: Only include routes with names (default: True)

    Usage:
        @pytest.mark.parametrize("route", sst_param_routes())
        def test_all_routes_valid(route):
            assert route.pattern.startswith("/")

        @pytest.mark.parametrize("route", sst_param_routes(layout="app"))
        def test_app_routes_require_auth(route):
            assert route.requires is not None
    """
    registry = _get_registry()
    routes = registry.routes.all()

    if named_only:
        routes = [r for r in routes if r.name]
    if layout:
        routes = [r for r in routes if r.layout == layout]
    if requires:
        routes = [r for r in routes if r.requires == requires]

    return routes


def sst_param_tenants(
    with_feature: str | None = None,
) -> list[TenantEntry]:
    """
    Get tenants for pytest.mark.parametrize.

    Args:
        with_feature: Filter by feature flag (optional)

    Usage:
        @pytest.mark.parametrize("tenant", sst_param_tenants())
        def test_all_tenants_have_domain(tenant):
            assert len(tenant.get_all_domains()) > 0

        @pytest.mark.parametrize("tenant", sst_param_tenants(with_feature="use_teams"))
        def test_team_tenants(tenant):
            assert tenant.has_feature("use_teams")
    """
    registry = _get_registry()
    tenants = registry.tenants.all()

    if with_feature:
        tenants = [t for t in tenants if t.has_feature(with_feature)]

    return tenants


def sst_param_layouts() -> list[LayoutEntry]:
    """
    Get layouts for pytest.mark.parametrize.

    Usage:
        @pytest.mark.parametrize("layout", sst_param_layouts())
        def test_all_layouts_have_template(layout):
            assert layout.template_path.endswith(".html")
    """
    return _get_registry().layouts.all()


def sst_param_blueprints(
    layout: str | None = None,
) -> list[BlueprintEntry]:
    """
    Get blueprints for pytest.mark.parametrize.

    Args:
        layout: Filter by allowed layout (optional)

    Usage:
        @pytest.mark.parametrize("blueprint", sst_param_blueprints())
        def test_all_blueprints_have_schema(blueprint):
            assert blueprint.schema is not None

        @pytest.mark.parametrize("blueprint", sst_param_blueprints(layout="web"))
        def test_web_blueprints(blueprint):
            assert "web" in blueprint.allowed_layouts
    """
    registry = _get_registry()
    blueprints = registry.blueprints.all()

    if layout:
        blueprints = [b for b in blueprints if layout in b.allowed_layouts]

    return blueprints


def sst_param_user_types() -> list[UserTypeEntry]:
    """
    Get user types for pytest.mark.parametrize.

    Usage:
        @pytest.mark.parametrize("user_type", sst_param_user_types())
        def test_all_user_types_valid(user_type):
            assert user_type.name is not None
    """
    return _get_registry().user_types.all()


def sst_param_user_flows(
    requires_user_type: str | None = None,
    with_entry_points: bool = False,
) -> list[UserFlowEntry]:
    """
    Get user flows for pytest.mark.parametrize.

    Args:
        requires_user_type: Filter by required user type (optional)
        with_entry_points: Only include flows with entry points (optional)

    Usage:
        @pytest.mark.parametrize("flow", sst_param_user_flows())
        def test_all_flows_have_steps(flow):
            assert len(flow.steps) > 0

        @pytest.mark.parametrize("flow", sst_param_user_flows(with_entry_points=True))
        def test_entry_point_flows(flow):
            assert len(flow.entry_points) > 0
    """
    registry = _get_registry()
    flows = registry.user_flows.all()

    if requires_user_type:
        flows = [f for f in flows if f.requires_user_type == requires_user_type]
    if with_entry_points:
        flows = [f for f in flows if f.entry_points]

    return flows


def sst_param_entry_points(
    tenant: str | None = None,
) -> list[EntryPointEntry]:
    """
    Get entry points for pytest.mark.parametrize.

    Args:
        tenant: Filter by tenant slug (optional)

    Usage:
        @pytest.mark.parametrize("entry_point", sst_param_entry_points())
        def test_all_entry_points_valid(entry_point):
            assert entry_point.domain is not None
            assert entry_point.path.startswith("/")

        @pytest.mark.parametrize("entry_point", sst_param_entry_points(tenant="cineos"))
        def test_cineos_entry_points(entry_point):
            assert "cineos" in entry_point.domain
    """
    registry = _get_registry()
    entry_points = registry.user_flows.entry_points()

    if tenant:
        entry_points = [e for e in entry_points if e.tenant == tenant]

    return entry_points


# =============================================================================
# Advanced generators for complex test scenarios
# =============================================================================


def sst_param_route_layout_pairs() -> list[tuple[RouteEntry, LayoutEntry]]:
    """
    Get (route, layout) pairs for testing route-layout relationships.

    Only includes routes that have a layout specified.

    Usage:
        @pytest.mark.parametrize("route,layout", sst_param_route_layout_pairs())
        def test_route_layout_consistency(route, layout):
            if route.requires:
                # Auth-required routes should use auth-aware layouts
                assert layout.auth_level is not None
    """
    registry = _get_registry()
    pairs = []

    for route in registry.routes.all():
        if route.layout and route.layout != "dynamic":
            layout = registry.layouts.get(route.layout)
            if layout:
                pairs.append((route, layout))

    return pairs


def sst_param_flow_steps() -> list[tuple[UserFlowEntry, dict[str, Any]]]:
    """
    Get (flow, step) pairs for testing individual flow steps.

    Flattens all flows into (flow, step) tuples for parametrized testing.

    Usage:
        @pytest.mark.parametrize("flow,step", sst_param_flow_steps())
        def test_all_steps_have_url(flow, step):
            assert "url" in step or "method" in step
    """
    registry = _get_registry()
    pairs = []

    for flow in registry.user_flows.all():
        for step in flow.steps:
            pairs.append((flow, step))

    return pairs


def sst_param_tenant_domains() -> list[tuple[TenantEntry, str]]:
    """
    Get (tenant, domain) pairs for testing all tenant domains.

    Usage:
        @pytest.mark.parametrize("tenant,domain", sst_param_tenant_domains())
        def test_domain_resolves(tenant, domain):
            # Test that domain is configured correctly
            assert domain is not None
    """
    registry = _get_registry()
    pairs = []

    for tenant in registry.tenants.all():
        for domain in tenant.get_all_domains():
            pairs.append((tenant, domain))

    return pairs


# =============================================================================
# E2E Test Generation from User Flows
# =============================================================================


def generate_flow_test_class(flow_name: str) -> str:
    """
    Generate a pytest test class from a user flow.

    This generates Python code that can be written to a test file.
    The generated tests pull all values from SST, not hardcoded.

    Args:
        flow_name: Name of the flow to generate tests for

    Returns:
        Python source code for a test class

    Usage:
        code = generate_flow_test_class("auth_signup")
        print(code)  # Write to tests/e2e/test_auth_signup.py
    """
    registry = _get_registry()
    flow = registry.user_flows.get(flow_name)

    if not flow:
        raise ValueError(f"Flow '{flow_name}' not found in SST")

    lines = [
        '"""',
        f"Auto-generated E2E tests for {flow.display_name}.",
        "",
        "Generated from user_flows.yaml - DO NOT EDIT MANUALLY.",
        "Regenerate with: lw test generate-e2e",
        '"""',
        "",
        "import pytest",
        "from lightwave.schema.testing import sst_spec, flow_test",
        "from lightwave.schema.core.registry import get_registry",
        "",
        "",
        f'@sst_spec("FLOW-{flow_name.upper()}", file="user_flows.yaml")',
        f"class Test{_to_class_name(flow_name)}:",
        f'    """E2E tests for: {flow.display_name}"""',
        "",
        "    @pytest.fixture(autouse=True)",
        "    def setup_flow(self):",
        "        self.registry = get_registry(use_db=False)",
        f'        self.flow = self.registry.user_flows.get("{flow_name}")',
        "",
    ]

    # Generate test for entry points
    if flow.entry_points:
        lines.extend(
            [
                "    def test_entry_points_defined(self):",
                '        """Flow has entry points defined in SST."""',
                "        assert len(self.flow.entry_points) > 0",
                "",
            ]
        )

    # Generate tests for each step
    for i, step in enumerate(flow.steps):
        step_url = step.get("url", step.get("pattern", ""))
        step_method = step.get("method", "GET")
        step_layout = step.get("layout")
        step_user_type = step.get("user_type")

        lines.extend(
            [
                f"    @flow_test(step={i + 1})",
                f"    def test_step_{i + 1}_{_to_method_name(step_url)}(self, client):",
                f'        """Step {i + 1}: {step_method} {step_url}"""',
                f"        step = self.flow.steps[{i}]",
                '        url = step.get("url", "/")',
                "",
                f"        response = client.{step_method.lower()}(url)",
                "",
            ]
        )

        # Add assertions based on step definition
        if step_layout:
            lines.append(f"        # Expected layout: {step_layout}")
            lines.append("        # assert layout in response (implementation-specific)")

        if step_user_type:
            if step_user_type == "guest":
                lines.append("        # Guest access - should not require auth")
                lines.append("        assert response.status_code != 403")
            else:
                lines.append(f"        # Requires user type: {step_user_type}")

        lines.append("")

    # Generate full flow test
    lines.extend(
        [
            "    def test_full_flow(self, client):",
            f'        """Complete {flow.display_name} flow."""',
            "        for i, step in enumerate(self.flow.steps):",
            '            url = step.get("url", "/")',
            '            method = step.get("method", "GET").upper()',
            "",
            '            if method == "GET":',
            "                response = client.get(url)",
            '            elif method == "POST":',
            '                response = client.post(url, data=step.get("data", {}))',
            "            else:",
            "                continue",
            "",
            "            # Each step should succeed or redirect",
            "            assert response.status_code in [200, 301, 302, 303], \\",
            '                f"Step {i+1} failed: {method} {url} returned {response.status_code}"',
            "",
        ]
    )

    return "\n".join(lines)


def generate_all_flow_tests(output_dir: str | None = None) -> dict[str, str]:
    """
    Generate E2E test files for all user flows.

    Args:
        output_dir: Directory to write test files (optional, returns dict if None)

    Returns:
        Dictionary mapping filename to generated code
    """
    registry = _get_registry()
    generated = {}

    for flow_name in registry.user_flows.names():
        code = generate_flow_test_class(flow_name)
        filename = f"test_{flow_name}.py"
        generated[filename] = code

    if output_dir:
        from pathlib import Path

        out_path = Path(output_dir)
        out_path.mkdir(parents=True, exist_ok=True)

        # Write conftest.py
        conftest = '''"""
E2E Test Configuration - Auto-generated from SST.

This conftest provides fixtures for E2E testing based on user_flows.yaml.
"""

import pytest

# Load SST testing fixtures
pytest_plugins = ["lightwave.schema.testing"]


@pytest.fixture
def client():
    """HTTP client for E2E tests."""
    from django.test import Client
    return Client()
'''
        (out_path / "conftest.py").write_text(conftest)

        # Write test files
        for filename, code in generated.items():
            (out_path / filename).write_text(code)

    return generated


def _to_class_name(name: str) -> str:
    """Convert snake_case to PascalCase."""
    return "".join(word.title() for word in name.split("_"))


def _to_method_name(url: str) -> str:
    """Convert URL to valid method name."""
    # Remove leading/trailing slashes
    url = url.strip("/")
    # Replace special chars
    url = url.replace("/", "_").replace("-", "_").replace("{", "").replace("}", "")
    # Handle empty
    if not url:
        return "root"
    return url.lower()
