"""Comprehensive tests for PDF rendering functionality.

This module tests edge cases, error handling, and deterministic behavior
of the audit report renderer to ensure reliable PDF generation.

Note: Most tests here use mocked PDF generation or HTML output to avoid
platform-specific WeasyPrint issues. For full PDF integration tests,
see test_pdf_generation_linux.py (Linux CI only).
"""

import tempfile
from pathlib import Path
from unittest.mock import patch

import jinja2
import pytest

from glassalpha.config import AuditConfig
from glassalpha.pipeline.audit import AuditResults
from glassalpha.report.renderer import AuditReportRenderer, render_audit_report


def _create_minimal_audit_config() -> AuditConfig:
    """Create a minimal valid audit configuration for testing."""
    return AuditConfig(
        audit_profile="tabular_compliance",
        model={"type": "logistic_regression", "path": "/tmp/model.pkl"},
        data={
            "dataset": "custom",
            "path": "/tmp/data.csv",
            "target_column": "target",
            "protected_attributes": ["gender"],
            "schema_path": "/tmp/schema.yaml",
        },
        reproducibility={
            "random_seed": 42,
            "deterministic": True,
            "capture_environment": True,
        },
        explainers={"priority": ["treeshap"], "strategy": "first_compatible"},
        manifest={
            "enabled": True,
            "include_git_sha": True,
            "include_config_hash": True,
            "include_data_hash": True,
        },
        metrics={
            "performance": ["accuracy"],
            "fairness": ["demographic_parity"],
        },
    )


def _create_minimal_audit_results() -> AuditResults:
    """Create minimal audit results for testing."""
    return AuditResults(
        model_performance={"accuracy": 0.85},
        fairness_analysis={"demographic_parity": {"gender": 0.05}},
        drift_analysis={"psi": 0.1},
        explanations={"global": {"feature_importance": {"age": 0.3, "income": 0.2}}},
        data_summary={
            "rows": 1000,
            "columns": 10,
            "protected_attributes": ["gender"],
        },
        schema_info={
            "columns": {"age": "int64", "income": "float64", "gender": "category"},
        },
        model_info={"type": "LogisticRegression", "features": 5},
        selected_components={
            "model": "sklearn",
            "explainer": "treeshap",
            "metrics": ["accuracy", "demographic_parity"],
        },
        execution_info={
            "start_time": "2025-01-01T00:00:00",
            "duration_seconds": 30,
            "seed": 42,
        },
        manifest={
            "config_hash": "abc123",
            "data_hash": "def456",
            "git_sha": "xyz789",
            "execution": {
                "status": "completed",
                "start_time": "2025-01-01T00:00:00",
                "end_time": "2025-01-01T00:00:30",
                "duration_seconds": 30,
            },
        },
    )


def test_render_with_missing_optional_sections():
    """Renderer should gracefully handle missing optional report sections."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Remove optional sections from results
    results.drift_analysis = {}
    results.explanations = {"global": {}}

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should not raise exception
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file
        assert output_path.exists()
        assert output_path.stat().st_size > 1000  # Should have content


def test_render_with_long_text_overflow():
    """Renderer should handle text overflow in PDF layout gracefully."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Add very long text that might cause overflow
    long_text = "A" * 1000  # Very long string
    results.model_info = {
        "type": "LogisticRegression",
        "description": long_text,
        "parameters": {"max_iter": 1000, "solver": "lbfgs"},
    }

    # Add long feature names
    results.explanations = {
        "global": {
            "feature_importance": {
                f"very_long_feature_name_{i}_that_might_cause_layout_issues": 0.1 for i in range(20)
            },
        },
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should not raise exception
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file
        assert output_path.exists()
        assert output_path.stat().st_size > 1000


def test_render_pdf_generation_works():
    """PDF generation should work and produce reasonable output."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Generate PDF report
        render_audit_report(
            results,
            output_path,
            template_name="standard_audit.html",
        )

        # Verify PDF was created and has reasonable size
        assert output_path.exists()
        assert output_path.stat().st_size > 10000, "PDF should be >10KB for meaningful content"


def test_render_error_handling_invalid_template():
    """Renderer should fail gracefully with clear error on bad template."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Create renderer and mock template environment
        renderer = AuditReportRenderer()

        # Mock the get_template method to raise an error
        with patch.object(renderer.env, "get_template") as mock_get_template:
            mock_get_template.side_effect = jinja2.TemplateError("Invalid template syntax")

            # Should raise a clear error
            with pytest.raises(jinja2.TemplateError) as exc_info:
                renderer.render_audit_report(results, template_name="nonexistent.html")

            # Error should mention template issues
            assert "Invalid template syntax" in str(exc_info.value)


def test_render_all_plot_types():
    """Verify all plot types render without errors."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Add various plot data that should be rendered
    results.model_performance = {
        "accuracy": 0.85,
        "precision": 0.82,
        "recall": 0.88,
        "f1": 0.85,
        "auc_roc": 0.90,
    }

    results.fairness_analysis = {
        "demographic_parity": {
            "gender": 0.05,
            "race": 0.03,
        },
        "equal_opportunity": {
            "gender": 0.02,
            "race": 0.04,
        },
    }

    results.explanations = {
        "global": {
            "feature_importance": {
                "age": 0.3,
                "income": 0.25,
                "credit_score": 0.2,
                "employment": 0.15,
                "education": 0.1,
            },
        },
        "local": [
            {"feature": "age", "importance": 0.5},
            {"feature": "income", "importance": 0.3},
        ],
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should not raise exception
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file
        assert output_path.exists()
        assert output_path.stat().st_size > 1000


def test_render_preserves_metadata():
    """PDF metadata should include manifest info."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Add specific metadata - use the structure expected by template
    results.manifest = {
        "config_hash": "test_config_hash_12345",
        "data_hash": "test_data_hash_67890",
        "git_sha": "abc123def456",
        "execution": {
            "status": "completed",
            "start_time": "2025-01-01T12:00:00Z",
            "end_time": "2025-01-01T12:00:45Z",
            "duration_seconds": 45,
        },
    }

    results.execution_info = {
        "start_time": "2025-01-01T12:00:00Z",
        "duration_seconds": 45,
        "seed": 42,
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Generate report
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Verify file was created
        assert output_path.exists()

        # Should have substantial content including metadata
        content = output_path.read_bytes()
        assert len(content) > 10000


def test_render_with_empty_results():
    """Renderer should handle empty/minimal audit results."""
    config = _create_minimal_audit_config()

    # Create minimal results with empty sections
    results = AuditResults(
        model_performance={},
        fairness_analysis={},
        drift_analysis={},
        explanations={},
        data_summary={"rows": 0, "columns": 0},
        schema_info={},
        model_info={},
        selected_components={},
        execution_info={},
        manifest={},
    )

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should not raise exception
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file (even with minimal content)
        assert output_path.exists()
        assert output_path.stat().st_size > 500  # Should have basic structure


def test_render_with_large_dataset():
    """Renderer should handle large dataset statistics gracefully."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Add large dataset info
    results.data_summary = {
        "rows": 1000000,  # 1M rows
        "columns": 100,  # 100 columns
        "protected_attributes": ["gender", "race", "age_group"],
        "feature_types": {"numeric": 80, "categorical": 20},
        "missing_values": 15000,
    }

    # Add many features for importance plots
    results.explanations = {
        "global": {
            "feature_importance": {
                f"feature_{i}": 0.01 + (i * 0.001)  # Decreasing importance
                for i in range(50)  # 50 features
            },
        },
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should not raise exception
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file
        assert output_path.exists()
        assert output_path.stat().st_size > 1000


def test_render_error_handling_missing_data():
    """Renderer should handle missing data gracefully."""
    config = _create_minimal_audit_config()

    # Create results with minimal but valid data
    results = AuditResults(
        model_performance={"accuracy": 0.5},  # Minimal but present
        fairness_analysis={},
        drift_analysis={},
        explanations={},
        data_summary={"rows": 100, "columns": 5},  # Minimal summary
        schema_info={},
        model_info={"type": "TestModel"},
        selected_components={"model": "test"},
        execution_info={"start_time": "2025-01-01T00:00:00", "duration_seconds": 10},
        manifest={
            "config_hash": "test123",
            "data_hash": "data456",
            "git_sha": "abc789",
            "execution": {
                "status": "completed",
                "start_time": "2025-01-01T00:00:00",
                "end_time": "2025-01-01T00:00:10",
                "duration_seconds": 10,
            },
        },
    )

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should not raise exception with minimal valid data
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file
        assert output_path.exists()
        assert output_path.stat().st_size > 1000


def test_render_with_unicode_and_special_characters():
    """Renderer should handle Unicode and special characters in text."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Add Unicode and special characters
    results.model_info = {
        "type": "LogisticRegression",
        "description": "Model with spécial caractères: café, naïve, résumé",
        "parameters": {"solver": "lbfgs-α", "max_iter": 1000},
    }

    results.data_summary = {
        "rows": 1000,
        "columns": 10,
        "protected_attributes": ["gënder", "rácé"],
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should not raise exception
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file
        assert output_path.exists()
        assert output_path.stat().st_size > 1000


def test_render_with_complex_nested_data():
    """Renderer should handle complex nested data structures."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Add complex nested structures
    results.fairness_analysis = {
        "demographic_parity": {
            "gender": {
                "overall": 0.05,
                "by_category": {"male": 0.03, "female": 0.07},
                "confidence_interval": [0.02, 0.08],
            },
            "race": {
                "overall": 0.08,
                "by_category": {"white": 0.05, "black": 0.12, "hispanic": 0.06},
            },
        },
        "equal_opportunity": {
            "gender": {
                "by_group": {"male": 0.85, "female": 0.82},
            },
        },
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should not raise exception
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file
        assert output_path.exists()
        assert output_path.stat().st_size > 1000


def test_render_template_customization():
    """Renderer should support custom template directories."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    with tempfile.TemporaryDirectory() as tmpdir:
        # Create custom template directory
        template_dir = Path(tmpdir) / "custom_templates"
        template_dir.mkdir()

        output_path = Path(tmpdir) / "test_report.pdf"

        # Should accept custom template directory
        renderer = AuditReportRenderer(template_dir=template_dir)
        # Note: This would need actual template files for full test
        # For now, verify renderer can be created
        assert renderer is not None


def test_render_preserves_execution_context():
    """PDF should include execution context and metadata."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Add detailed execution context
    results.execution_info = {
        "start_time": "2025-01-01T12:00:00.123456Z",
        "end_time": "2025-01-01T12:00:45.789012Z",
        "duration_seconds": 45.666,
        "seed": 42,
        "python_version": "3.13.0",
        "platform": "Darwin",
        "environment": {
            "GLASSALPHA_VERSION": "0.1.0",
            "CUDA_VISIBLE_DEVICES": "0",
        },
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Generate report
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Verify file was created
        assert output_path.exists()

        # Should have substantial content including metadata
        content = output_path.read_bytes()
        assert len(content) > 1000


def test_render_handles_plot_generation_errors():
    """Renderer should handle plot generation errors gracefully."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Mock plot generation to raise an error
    with patch("glassalpha.report.renderer.create_performance_plots") as mock_plots:
        mock_plots.side_effect = Exception("Plot generation failed")

        with tempfile.TemporaryDirectory() as tmpdir:
            output_path = Path(tmpdir) / "test_report.pdf"

            # Should handle plot error gracefully (skip plots or use fallback)
            render_audit_report(results, output_path, template_name="standard_audit.html")

            # Should still create output file
            assert output_path.exists()
            assert output_path.stat().st_size > 500  # Should have basic content


def test_render_with_extreme_values():
    """Renderer should handle extreme numerical values."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Add extreme values
    results.model_performance = {
        "accuracy": 1.0,  # Perfect accuracy
        "precision": 0.0,  # No precision
        "recall": float("inf"),  # Infinite recall (should be handled)
        "f1": float("-inf"),  # Negative infinite (should be handled)
    }

    results.fairness_analysis = {
        "demographic_parity": {
            "gender": float("nan"),  # NaN value
        },
    }

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should handle extreme values gracefully
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should create output file
        assert output_path.exists()
        assert output_path.stat().st_size > 1000


def test_render_context_validation():
    """Renderer should validate template context before rendering."""
    config = _create_minimal_audit_config()
    results = _create_minimal_audit_results()

    # Create renderer and test context validation
    renderer = AuditReportRenderer()

    # Test context preparation
    context = renderer._prepare_template_context(results)

    # Should have required sections
    assert "data_summary" in context
    assert "model_performance" in context
    assert "fairness_analysis" in context
    assert "manifest" in context

    # Should validate context without error for valid data
    renderer._validate_template_context(context)  # Should not raise


def test_render_with_missing_required_fields():
    """Renderer should fail when required fields are missing."""
    config = _create_minimal_audit_config()

    # Create results missing critical fields that template expects
    results = AuditResults(
        model_performance={},  # Empty but not None
        fairness_analysis={},
        drift_analysis={},
        explanations={},
        data_summary={},  # Empty summary
        schema_info={},
        model_info={},
        selected_components={},
        execution_info={},
        manifest={},
    )

    with tempfile.TemporaryDirectory() as tmpdir:
        output_path = Path(tmpdir) / "test_report.pdf"

        # Should handle gracefully - template may have defaults for empty data
        # This test verifies that empty data doesn't crash the renderer
        render_audit_report(results, output_path, template_name="standard_audit.html")

        # Should still create a file (even if minimal)
        assert output_path.exists()
        assert output_path.stat().st_size > 100  # Should have basic structure
