"""Template rendering validation tests.

Renders each Jinja2 template with a sample FSI context and validates:
- No Jinja2 errors occur during rendering
- Rendered YAML is parseable (for .yml templates)
- Rendered JSON is parseable (for .json templates)
- CLAUDE.md output is > 300 lines
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

import pytest
import yaml
from jinja2 import Environment, FileSystemLoader, StrictUndefined

# Path to templates directory
TEMPLATES_DIR = Path(__file__).parent.parent.parent / "src" / "exokit" / "core" / "templates"

# All template subdirectories and their files
TEMPLATE_FILES: list[str] = [
    "root/CLAUDE.md.j2",
    "root/pyproject.toml.j2",
    "root/.env.example.j2",
    "root/.gitignore.j2",
    "root/databricks.yml.j2",
    "root/requirements.txt.j2",
    "conf/demo_manifest.json.j2",
    "conf/catalog_schema.json.j2",
    "claude/agents/lakehouse-dev.md.j2",
    "claude/agents/app-dev.md.j2",
    "claude/agents/dashboard-dev.md.j2",
    "claude/agents/reviewer.md.j2",
    "claude/commands/plan.md.j2",
    "claude/commands/build-next.md.j2",
    "claude/commands/deploy.md.j2",
    "claude/commands/talk-track.md.j2",
    "docs/IMPLEMENTATION_PLAN.md.j2",
    "docs/PROGRESS.md.j2",
    "docs/supplements/ARCHITECTURE.md.j2",
    "docs/supplements/CODING_STANDARDS.md.j2",
    "docs/supplements/WAVE_PLAYBOOK.md.j2",
    "lakehouse/resources/jobs/data_generation_job.yml.j2",
    "lakehouse/resources/jobs/ml_training_job.yml.j2",
    "lakehouse/resources/pipelines/main_pipeline.yml.j2",
    "scripts/deploy.sh.j2",
    "scripts/deploy-all.sh.j2",
    "scripts/deploy-lakehouse.sh.j2",
    "scripts/deploy-app.sh.j2",
    "scripts/validate.sh.j2",
    "scripts/get_job_errors.py.j2",
    "scripts/setup-genie.py.j2",
    "lakehouse/src/data_generation/setup_catalog.py.j2",
    "lakehouse/src/data_generation/CLAUDE.md.j2",
    "lakehouse/src/pipelines/CLAUDE.md.j2",
    "lakehouse/src/ml/CLAUDE.md.j2",
    "docs/TALK_TRACK.md.j2",
    "lakehouse/src/data_generation/structured/generate_sample.py.j2",
    "lakehouse/src/pipelines/bronze/sample_customers_bronze.sql.j2",
    "lakehouse/src/pipelines/silver/sample_customers_silver.sql.j2",
    "lakehouse/src/pipelines/gold/sample_summary_gold.sql.j2",
    "lakehouse/src/ml/train_sample_model.py.j2",
    "lakehouse/src/dashboards/sample_dashboard.lvdash.json.j2",
    "lakehouse/resources/dashboards/sample_dashboard.yml.j2",
    "lakehouse/src/dashboards/setup_sample_genie.py.j2",
]

# Templates that produce YAML output
YAML_TEMPLATES: list[str] = [
    "root/databricks.yml.j2",
    "lakehouse/resources/jobs/data_generation_job.yml.j2",
    "lakehouse/resources/jobs/ml_training_job.yml.j2",
    "lakehouse/resources/pipelines/main_pipeline.yml.j2",
    "lakehouse/resources/dashboards/sample_dashboard.yml.j2",
]

# Templates that produce JSON output
JSON_TEMPLATES: list[str] = [
    "conf/demo_manifest.json.j2",
    "conf/catalog_schema.json.j2",
    "lakehouse/src/dashboards/sample_dashboard.lvdash.json.j2",
]


@pytest.fixture()
def sample_fsi_context() -> dict[str, Any]:
    """Sample FSI context for template rendering."""
    return {
        "company": "Acme Bank",
        "industry": "fsi",
        "demo_description": (
            "Comprehensive FSI demo spanning all departments with fraud detection and customer 360"
        ),
        "catalog": "acme_bank_fsi",
        "bundle_name": "acme-bank-fsi-demo",
        "app_name": "acme-bank-fsi-app",
        "warehouse_id": "abc123def456",
        "workspace_host": "https://adb-1234567890.azuredatabricks.net",
        "databricks_profile": "DEFAULT",
        "lakebase_url": "postgresql://user:pass@host:5432/db",
        "notification_email": "demo@acmebank.com",
        "generated_at": "2026-03-02T00:00:00Z",
        "exokit_version": "0.1.0",
        "display_name": "Acme Bank",
        "demo_title": "Acme Bank FSI Demo",
    }


@pytest.fixture()
def jinja_env() -> Environment:
    """Create a Jinja2 environment for template rendering."""
    return Environment(
        loader=FileSystemLoader(str(TEMPLATES_DIR)),
        undefined=StrictUndefined,
        trim_blocks=True,
        lstrip_blocks=True,
    )


def _render_template(
    jinja_env: Environment,
    template_path: str,
    context: dict[str, Any],
) -> str:
    """Render a single template with the given context."""
    template = jinja_env.get_template(template_path)
    return template.render(**context)


class TestTemplateFilesExist:
    """Verify all expected template files exist on disk."""

    @pytest.mark.parametrize("template_path", TEMPLATE_FILES)
    def test_template_file_exists(self, template_path: str) -> None:
        """Each template file must exist in the templates directory."""
        full_path = TEMPLATES_DIR / template_path
        assert full_path.exists(), f"Template not found: {full_path}"
        assert full_path.is_file(), f"Not a file: {full_path}"


class TestTemplateRendering:
    """Verify all templates render without Jinja2 errors."""

    @pytest.mark.parametrize("template_path", TEMPLATE_FILES)
    def test_template_renders_without_error(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
        template_path: str,
    ) -> None:
        """Each template must render without Jinja2 errors using sample FSI context."""
        rendered = _render_template(jinja_env, template_path, sample_fsi_context)
        assert len(rendered) > 0, f"Template rendered empty output: {template_path}"

    @pytest.mark.parametrize("template_path", TEMPLATE_FILES)
    def test_template_renders_without_none_profile(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
        template_path: str,
    ) -> None:
        """Templates must also render when optional fields are None."""
        context = {**sample_fsi_context, "databricks_profile": None, "lakebase_url": None}
        rendered = _render_template(jinja_env, template_path, context)
        assert len(rendered) > 0, f"Template rendered empty with None optionals: {template_path}"


class TestClaudeMdTemplate:
    """Specific validations for the CLAUDE.md template."""

    def test_claude_md_exceeds_300_lines(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Rendered CLAUDE.md must be > 300 lines."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        line_count = len(rendered.strip().splitlines())
        assert line_count > 300, f"CLAUDE.md only has {line_count} lines, expected > 300"

    def test_claude_md_contains_tech_stack(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must contain technology stack section."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "Technology Stack" in rendered
        assert "DLT" in rendered
        assert "FastAPI" in rendered
        assert "React" in rendered
        assert "TanStack Query" in rendered

    def test_claude_md_contains_architecture(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must contain architecture section."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "3-Layer Backend Architecture" in rendered
        assert "Router" in rendered
        assert "Service" in rendered
        assert "Repository" in rendered

    def test_claude_md_contains_wave_structure(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must contain wave structure section."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "Wave Structure" in rendered
        assert "Wave 1" in rendered
        assert "Wave 5" in rendered

    def test_claude_md_contains_agent_roles(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must contain agent roles section."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "Agent Roles" in rendered
        assert "lakehouse-dev" in rendered
        assert "app-dev" in rendered
        assert "dashboard-dev" in rendered
        assert "reviewer" in rendered

    def test_claude_md_contains_dlt_patterns(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must contain DLT/SDP patterns section."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "DLT/SDP Patterns" in rendered
        assert "STREAMING TABLE" in rendered
        assert "LIVE TABLE" in rendered
        assert "read_files" in rendered

    def test_claude_md_contains_quality_gates(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must contain quality gates section."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "Quality Gates" in rendered
        assert "ruff" in rendered
        assert "mypy" in rendered
        assert "pytest" in rendered

    def test_claude_md_contains_never_do(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must contain 'things you must never do' section."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "Things You Must NEVER Do" in rendered

    def test_claude_md_contains_missing_context(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must contain missing context protocol."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "Missing Context Protocol" in rendered

    def test_claude_md_contains_manifest_reference(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must reference demo_manifest.json."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "demo_manifest.json" in rendered

    def test_claude_md_substitutes_company(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """CLAUDE.md must substitute the company name."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "Acme Bank" in rendered
        assert "acme_bank_fsi" in rendered


class TestYamlTemplates:
    """Verify YAML templates produce valid YAML output."""

    @pytest.mark.parametrize("template_path", YAML_TEMPLATES)
    def test_yaml_template_produces_valid_yaml(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
        template_path: str,
    ) -> None:
        """Rendered YAML templates must produce parseable YAML."""
        rendered = _render_template(jinja_env, template_path, sample_fsi_context)
        try:
            parsed = yaml.safe_load(rendered)
        except yaml.YAMLError as e:
            pytest.fail(f"Invalid YAML from {template_path}: {e}")
        assert parsed is not None, f"YAML parsed to None: {template_path}"
        assert isinstance(parsed, dict), f"YAML root is not a dict: {template_path}"

    def test_databricks_yml_has_bundle_name(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """databricks.yml must have the correct bundle name."""
        rendered = _render_template(jinja_env, "root/databricks.yml.j2", sample_fsi_context)
        parsed = yaml.safe_load(rendered)
        assert parsed["bundle"]["name"] == "acme-bank-fsi-demo"

    def test_databricks_yml_has_targets(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """databricks.yml must have dev and prod targets."""
        rendered = _render_template(jinja_env, "root/databricks.yml.j2", sample_fsi_context)
        parsed = yaml.safe_load(rendered)
        assert "dev" in parsed["targets"]
        assert "prod" in parsed["targets"]

    def test_data_generation_job_has_tasks(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Data generation job must have setup_catalog and generate_sample tasks."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/resources/jobs/data_generation_job.yml.j2",
            sample_fsi_context,
        )
        parsed = yaml.safe_load(rendered)
        jobs = parsed["resources"]["jobs"]
        assert len(jobs) == 1
        job = next(iter(jobs.values()))
        assert len(job["tasks"]) == 2
        assert job["tasks"][0]["task_key"] == "setup_catalog"
        assert job["tasks"][1]["task_key"] == "generate_sample"

    def test_ml_training_job_has_valid_structure(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """ML training job must have the train_sample_model task."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/resources/jobs/ml_training_job.yml.j2",
            sample_fsi_context,
        )
        parsed = yaml.safe_load(rendered)
        jobs = parsed["resources"]["jobs"]
        job = next(iter(jobs.values()))
        assert "name" in job
        assert len(job["tasks"]) == 1
        assert job["tasks"][0]["task_key"] == "train_sample_model"

    def test_pipeline_has_libraries(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """DLT pipeline must have library glob patterns for bronze/silver/gold."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/resources/pipelines/main_pipeline.yml.j2",
            sample_fsi_context,
        )
        parsed = yaml.safe_load(rendered)
        pipelines = parsed["resources"]["pipelines"]
        pipeline = next(iter(pipelines.values()))
        libs = pipeline["libraries"]
        assert len(libs) == 3  # bronze, silver, gold

    def test_pipeline_has_target(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """DLT pipeline must have a target key (required for UC pipelines)."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/resources/pipelines/main_pipeline.yml.j2",
            sample_fsi_context,
        )
        parsed = yaml.safe_load(rendered)
        pipelines = parsed["resources"]["pipelines"]
        pipeline = next(iter(pipelines.values()))
        assert "target" in pipeline

    def test_databricks_yml_has_placeholder_variables(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """databricks.yml must have pipeline_target_schema and schema variables."""
        rendered = _render_template(jinja_env, "root/databricks.yml.j2", sample_fsi_context)
        parsed = yaml.safe_load(rendered)
        variables = parsed["variables"]
        assert "pipeline_target_schema" in variables
        assert "schema" in variables


class TestJsonTemplates:
    """Verify JSON templates produce valid JSON output."""

    @pytest.mark.parametrize("template_path", JSON_TEMPLATES)
    def test_json_template_produces_valid_json(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
        template_path: str,
    ) -> None:
        """Rendered JSON templates must produce parseable JSON."""
        rendered = _render_template(jinja_env, template_path, sample_fsi_context)
        try:
            parsed = json.loads(rendered)
        except json.JSONDecodeError as e:
            pytest.fail(f"Invalid JSON from {template_path}: {e}")
        assert parsed is not None, f"JSON parsed to None: {template_path}"
        assert isinstance(parsed, dict), f"JSON root is not a dict: {template_path}"

    def test_demo_manifest_has_waves(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """demo_manifest.json must have 5 waves."""
        rendered = _render_template(jinja_env, "conf/demo_manifest.json.j2", sample_fsi_context)
        parsed = json.loads(rendered)
        assert len(parsed["waves"]) == 5
        assert parsed["meta"]["company"] == "Acme Bank"
        assert parsed["meta"]["industry"] == "fsi"

    def test_demo_manifest_waves_are_pending(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """All waves in the manifest should start as pending."""
        rendered = _render_template(jinja_env, "conf/demo_manifest.json.j2", sample_fsi_context)
        parsed = json.loads(rendered)
        for wave in parsed["waves"]:
            assert wave["status"] == "pending"

    def test_catalog_schema_has_catalog(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """catalog_schema.json must have catalog name and schemas array."""
        rendered = _render_template(jinja_env, "conf/catalog_schema.json.j2", sample_fsi_context)
        parsed = json.loads(rendered)
        assert parsed["catalog"] == "acme_bank_fsi"
        assert "schemas" in parsed  # Empty initially — Claude populates during planning


class TestGetJobErrorsTemplate:
    """Verify get_job_errors.py template renders correctly."""

    def test_renders_valid_python(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """get_job_errors.py must render syntactically valid Python."""
        rendered = _render_template(jinja_env, "scripts/get_job_errors.py.j2", sample_fsi_context)
        # Compile to check syntax validity
        compile(rendered, "get_job_errors.py", "exec")

    def test_contains_profile_default(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """get_job_errors.py must use the configured profile as default."""
        rendered = _render_template(jinja_env, "scripts/get_job_errors.py.j2", sample_fsi_context)
        assert "DEFAULT" in rendered

    def test_contains_profile_none_fallback(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """get_job_errors.py must fall back to DEFAULT when profile is None."""
        context = {**sample_fsi_context, "databricks_profile": None}
        rendered = _render_template(jinja_env, "scripts/get_job_errors.py.j2", context)
        assert "DEFAULT" in rendered
        compile(rendered, "get_job_errors.py", "exec")

    def test_contains_json_output_option(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """get_job_errors.py must support --json output."""
        rendered = _render_template(jinja_env, "scripts/get_job_errors.py.j2", sample_fsi_context)
        assert "--json" in rendered
        assert "as_json" in rendered

    def test_contains_argparse_cli(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """get_job_errors.py must use argparse for CLI."""
        rendered = _render_template(jinja_env, "scripts/get_job_errors.py.j2", sample_fsi_context)
        assert "argparse" in rendered
        assert "run_id" in rendered

    def test_handles_task_and_query_errors(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """get_job_errors.py must handle both task-level and SQL query errors."""
        rendered = _render_template(jinja_env, "scripts/get_job_errors.py.j2", sample_fsi_context)
        assert "TaskError" in rendered
        assert "QueryError" in rendered
        assert "query_history" in rendered


class TestContextVariableSubstitution:
    """Verify context variables are properly substituted in rendered output."""

    def test_company_name_substituted(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Company name must appear in rendered templates."""
        for template_path in ["root/CLAUDE.md.j2", "conf/demo_manifest.json.j2"]:
            rendered = _render_template(jinja_env, template_path, sample_fsi_context)
            assert "Acme Bank" in rendered, f"Company not in {template_path}"

    def test_catalog_substituted(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Catalog name must appear in rendered templates."""
        rendered = _render_template(jinja_env, "root/CLAUDE.md.j2", sample_fsi_context)
        assert "acme_bank_fsi" in rendered

    def test_bundle_name_substituted(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Bundle name must appear in rendered config templates."""
        rendered = _render_template(jinja_env, "root/databricks.yml.j2", sample_fsi_context)
        assert "acme-bank-fsi-demo" in rendered


class TestSetupCatalogTemplate:
    """Verify setup_catalog.py template renders correctly."""

    def test_renders_valid_python(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """setup_catalog.py must render syntactically valid Python."""
        rendered = _render_template(
            jinja_env, "lakehouse/src/data_generation/setup_catalog.py.j2", sample_fsi_context
        )
        # Filter out Databricks magic lines before compiling
        lines = [
            line
            for line in rendered.splitlines()
            if not line.startswith("# MAGIC")
            and not line.startswith("# COMMAND")
            and not line.startswith("# Databricks notebook source")
        ]
        compile("\n".join(lines), "setup_catalog.py", "exec")

    def test_contains_catalog_name(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """setup_catalog.py must contain the catalog name."""
        rendered = _render_template(
            jinja_env, "lakehouse/src/data_generation/setup_catalog.py.j2", sample_fsi_context
        )
        assert "acme_bank_fsi" in rendered

    def test_contains_create_catalog(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """setup_catalog.py must contain CREATE CATALOG statement."""
        rendered = _render_template(
            jinja_env, "lakehouse/src/data_generation/setup_catalog.py.j2", sample_fsi_context
        )
        assert "CREATE CATALOG" in rendered

    def test_contains_create_volume(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """setup_catalog.py must contain CREATE VOLUME statement."""
        rendered = _render_template(
            jinja_env, "lakehouse/src/data_generation/setup_catalog.py.j2", sample_fsi_context
        )
        assert "CREATE VOLUME" in rendered


class TestDirectoryCLAUDEMdTemplates:
    """Verify directory-level CLAUDE.md templates render correctly."""

    def test_data_gen_claude_md_contains_patterns(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Data gen CLAUDE.md must contain UDF and Faker patterns."""
        rendered = _render_template(
            jinja_env, "lakehouse/src/data_generation/CLAUDE.md.j2", sample_fsi_context
        )
        assert "pandas_udf" in rendered
        assert "Faker" in rendered

    def test_pipelines_claude_md_contains_patterns(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Pipelines CLAUDE.md must contain DLT tier patterns."""
        rendered = _render_template(
            jinja_env, "lakehouse/src/pipelines/CLAUDE.md.j2", sample_fsi_context
        )
        assert "STREAMING TABLE" in rendered
        assert "LIVE TABLE" in rendered

    def test_ml_claude_md_contains_patterns(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """ML CLAUDE.md must contain decimal casting and MLflow patterns."""
        rendered = _render_template(jinja_env, "lakehouse/src/ml/CLAUDE.md.j2", sample_fsi_context)
        assert "Decimal" in rendered or "decimal" in rendered
        assert "MLflow" in rendered


class TestDeployScriptTemplates:
    """Verify deploy script templates render correctly."""

    def test_deploy_all_has_shebang(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """deploy-all.sh must start with shebang."""
        rendered = _render_template(jinja_env, "scripts/deploy-all.sh.j2", sample_fsi_context)
        assert rendered.startswith("#!/usr/bin/env bash")

    def test_deploy_lakehouse_references_jobs(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """deploy-lakehouse.sh must reference the bundle name for job runs."""
        rendered = _render_template(jinja_env, "scripts/deploy-lakehouse.sh.j2", sample_fsi_context)
        assert "acme_bank_fsi_demo" in rendered

    def test_deploy_app_references_app(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """deploy-app.sh must reference the app name."""
        rendered = _render_template(jinja_env, "scripts/deploy-app.sh.j2", sample_fsi_context)
        assert "acme-bank-fsi-app" in rendered

    def test_deploy_all_calls_subscripts(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """deploy-all.sh must call deploy-lakehouse.sh and deploy-app.sh."""
        rendered = _render_template(jinja_env, "scripts/deploy-all.sh.j2", sample_fsi_context)
        assert "deploy-lakehouse.sh" in rendered
        assert "deploy-app.sh" in rendered


class TestPlanTemplate:
    """Verify plan.md.j2 contains Phase 0 planning content."""

    def test_renders_without_error(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """plan.md.j2 must render without Jinja2 errors."""
        rendered = _render_template(jinja_env, "claude/commands/plan.md.j2", sample_fsi_context)
        assert len(rendered) > 0

    def test_contains_phase_0(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """plan.md.j2 must contain Phase 0 planning content."""
        rendered = _render_template(jinja_env, "claude/commands/plan.md.j2", sample_fsi_context)
        assert "Phase 0" in rendered

    def test_contains_step_06(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """plan.md.j2 must contain Step 0.6 for scaffold placeholder updates."""
        rendered = _render_template(jinja_env, "claude/commands/plan.md.j2", sample_fsi_context)
        assert "Step 0.6" in rendered
        assert "Apply Plan to Scaffold Placeholders" in rendered
        assert "databricks bundle validate" in rendered


class TestBuildNextTemplate:
    """Verify build-next.md.j2 contains building logic."""

    def test_contains_build_validate_loop(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """build-next.md.j2 must contain the build-validate loop."""
        rendered = _render_template(
            jinja_env, "claude/commands/build-next.md.j2", sample_fsi_context
        )
        assert "Build-Validate Loop" in rendered
        assert "databricks bundle validate" in rendered


class TestDeployTemplate:
    """Verify deploy.md.j2 references deployment scripts."""

    def test_contains_deploy_scripts(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """deploy.md.j2 must reference the deploy scripts."""
        rendered = _render_template(jinja_env, "claude/commands/deploy.md.j2", sample_fsi_context)
        assert "deploy-all.sh" in rendered
        assert "deploy-lakehouse.sh" in rendered
        assert "deploy-app.sh" in rendered

    def test_contains_troubleshooting(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """deploy.md.j2 must contain troubleshooting with get_job_errors.py."""
        rendered = _render_template(jinja_env, "claude/commands/deploy.md.j2", sample_fsi_context)
        assert "get_job_errors.py" in rendered


class TestTalkTrackTemplate:
    """Verify TALK_TRACK.md template renders correctly."""

    def test_renders_with_wave_sections(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """TALK_TRACK.md must contain wave sections."""
        rendered = _render_template(jinja_env, "docs/TALK_TRACK.md.j2", sample_fsi_context)
        assert "Wave 1" in rendered
        assert "Wave 5" in rendered

    def test_contains_industry(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """TALK_TRACK.md must contain the industry."""
        rendered = _render_template(jinja_env, "docs/TALK_TRACK.md.j2", sample_fsi_context)
        assert "FSI" in rendered


class TestHelloWorldTemplates:
    """Verify hello-world runnable code templates render correctly."""

    @staticmethod
    def _strip_databricks_lines(rendered: str) -> str:
        """Remove Databricks notebook magic lines for Python compilation."""
        lines = [
            line
            for line in rendered.splitlines()
            if not line.startswith("# MAGIC")
            and not line.startswith("# COMMAND")
            and not line.startswith("# Databricks notebook source")
        ]
        return "\n".join(lines)

    def test_generate_sample_renders_valid_python(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """generate_sample.py must render syntactically valid Python."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/data_generation/structured/generate_sample.py.j2",
            sample_fsi_context,
        )
        cleaned = self._strip_databricks_lines(rendered)
        compile(cleaned, "generate_sample.py", "exec")

    def test_generate_sample_contains_volume_path(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """generate_sample.py must reference the landing/customers volume path."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/data_generation/structured/generate_sample.py.j2",
            sample_fsi_context,
        )
        assert "landing/customers" in rendered

    def test_bronze_sql_contains_streaming_table(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Bronze SQL must contain STREAMING TABLE."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/pipelines/bronze/sample_customers_bronze.sql.j2",
            sample_fsi_context,
        )
        assert "STREAMING TABLE" in rendered

    def test_silver_sql_contains_constraints(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Silver SQL must contain CONSTRAINT."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/pipelines/silver/sample_customers_silver.sql.j2",
            sample_fsi_context,
        )
        assert "CONSTRAINT" in rendered

    def test_gold_sql_contains_live_table(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Gold SQL must contain LIVE TABLE."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/pipelines/gold/sample_summary_gold.sql.j2",
            sample_fsi_context,
        )
        assert "LIVE TABLE" in rendered

    def test_train_sample_renders_valid_python(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """train_sample_model.py must render syntactically valid Python."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/ml/train_sample_model.py.j2",
            sample_fsi_context,
        )
        cleaned = self._strip_databricks_lines(rendered)
        compile(cleaned, "train_sample_model.py", "exec")

    def test_train_sample_contains_mlflow(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """train_sample_model.py must contain mlflow."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/ml/train_sample_model.py.j2",
            sample_fsi_context,
        )
        assert "mlflow" in rendered


class TestHelloWorldDashboardTemplates:
    """Verify hello-world dashboard, Genie, and resource templates render correctly."""

    @staticmethod
    def _strip_databricks_lines(rendered: str) -> str:
        """Remove Databricks notebook magic lines for Python compilation."""
        lines = [
            line
            for line in rendered.splitlines()
            if not line.startswith("# MAGIC")
            and not line.startswith("# COMMAND")
            and not line.startswith("# Databricks notebook source")
        ]
        return "\n".join(lines)

    def test_dashboard_json_has_pages(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Dashboard JSON must have a pages key with at least 1 page."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/dashboards/sample_dashboard.lvdash.json.j2",
            sample_fsi_context,
        )
        parsed = json.loads(rendered)
        assert "pages" in parsed
        assert len(parsed["pages"]) >= 1

    def test_dashboard_json_has_datasets(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Dashboard JSON must have datasets with gold_summary and silver_customers."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/dashboards/sample_dashboard.lvdash.json.j2",
            sample_fsi_context,
        )
        parsed = json.loads(rendered)
        assert "datasets" in parsed
        dataset_names = [d["name"] for d in parsed["datasets"]]
        assert "gold_summary" in dataset_names
        assert "silver_customers" in dataset_names

    def test_dashboard_yml_has_warehouse(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """Dashboard resource YAML must reference warehouse_id."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/resources/dashboards/sample_dashboard.yml.j2",
            sample_fsi_context,
        )
        assert "warehouse_id" in rendered

    def test_genie_setup_renders_valid_python(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """setup_sample_genie.py must render syntactically valid Python."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/dashboards/setup_sample_genie.py.j2",
            sample_fsi_context,
        )
        cleaned = self._strip_databricks_lines(rendered)
        compile(cleaned, "setup_sample_genie.py", "exec")

    def test_genie_setup_contains_genie_api(
        self,
        jinja_env: Environment,
        sample_fsi_context: dict[str, Any],
    ) -> None:
        """setup_sample_genie.py must contain the Genie API endpoint."""
        rendered = _render_template(
            jinja_env,
            "lakehouse/src/dashboards/setup_sample_genie.py.j2",
            sample_fsi_context,
        )
        assert "genie/spaces" in rendered
