"""
Unit tests for the profile_report module.

Following Prime Directive principles:
- API research completed from existing edaflow functions
- Defensive testing for edge cases
- Comprehensive test coverage
"""

import pytest
import pandas as pd
import numpy as np
import os
import tempfile
from pathlib import Path

# Import the function to test
from edaflow.analysis.report import profile_report
import edaflow


class TestProfileReportBasicFunctionality:
    """Test basic functionality of profile_report."""
    
    def test_profile_report_with_simple_dataframe_html(self):
        """Test profile_report with simple DataFrame returning HTML."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 28, 32],
            'salary': [50000, 60000, 70000, 55000, 65000],
            'department': ['HR', 'IT', 'IT', 'HR', 'Finance']
        })
        
        result = profile_report(df, output_format="html")
        
        # Should return a file path
        assert isinstance(result, str)
        assert result.endswith('.html')
        assert os.path.exists(result)
        
        # Clean up
        if os.path.exists(result):
            os.remove(result)
    
    def test_profile_report_with_simple_dataframe_dict(self):
        """Test profile_report with simple DataFrame returning dict."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 28, 32],
            'salary': [50000, 60000, 70000, 55000, 65000],
            'department': ['HR', 'IT', 'IT', 'HR', 'Finance']
        })
        
        result = profile_report(df, output_format="dict")
        
        # Should return a dict with expected keys
        assert isinstance(result, dict)
        assert 'overview' in result
        assert 'summary_stats' in result
        assert 'missing_values' in result
        assert 'categorical_insights' in result
        assert 'visualizations' in result
    
    def test_profile_report_from_main_package(self):
        """Test that profile_report is accessible from main edaflow package."""
        df = pd.DataFrame({
            'x': [1, 2, 3],
            'y': [4, 5, 6]
        })
        
        # Should be able to call from main package
        result = edaflow.profile_report(df, output_format="dict")
        assert isinstance(result, dict)
    
    def test_profile_report_overview_section(self):
        """Test that overview section contains expected metrics."""
        df = pd.DataFrame({
            'col1': [1, 2, 3, 4, 5],
            'col2': ['a', 'b', 'c', 'd', 'e']
        })
        
        result = profile_report(df, output_format="dict")
        overview = result['overview']
        
        assert isinstance(overview, pd.DataFrame)
        assert len(overview) >= 5  # Should have at least 5 metrics
        
        # Check that key metrics are present
        metrics = overview['Metric'].tolist()
        assert 'Number of Rows' in metrics
        assert 'Number of Columns' in metrics
        assert 'Memory Usage (MB)' in metrics


class TestProfileReportNumericalAnalysis:
    """Test numerical column analysis functionality."""
    
    def test_profile_report_with_numeric_columns(self):
        """Test profile_report with only numeric columns."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 40, 45],
            'salary': [50000, 60000, 70000, 80000, 90000],
            'experience': [2, 5, 8, 12, 15]
        })
        
        result = profile_report(df, output_format="dict")
        
        # Check summary stats
        assert 'summary_stats' in result
        assert not result['summary_stats'].empty
        assert len(result['summary_stats']) == 3  # 3 numeric columns
        
        # Check that summary stats has expected columns
        stats_cols = result['summary_stats'].columns.tolist()
        assert 'mean' in stats_cols
        assert 'std' in stats_cols
        assert 'min' in stats_cols
        assert 'max' in stats_cols
    
    def test_profile_report_numeric_with_missing_values(self):
        """Test numeric analysis with missing values."""
        df = pd.DataFrame({
            'age': [25, 30, None, 40, 45],
            'salary': [50000, None, 70000, 80000, 90000]
        })
        
        result = profile_report(df, output_format="dict")
        
        # Check that missing values are reported
        summary_stats = result['summary_stats']
        assert 'missing' in summary_stats.columns
        assert 'missing_%' in summary_stats.columns
        
        # Check missing counts
        assert summary_stats.loc['age', 'missing'] == 1
        assert summary_stats.loc['salary', 'missing'] == 1
    
    def test_profile_report_numeric_with_zeros(self):
        """Test numeric analysis tracks zero values."""
        df = pd.DataFrame({
            'age': [0, 30, 35, 0, 45],
            'salary': [50000, 0, 70000, 80000, 0]
        })
        
        result = profile_report(df, output_format="dict")
        
        summary_stats = result['summary_stats']
        assert 'zeros' in summary_stats.columns
        assert 'zeros_%' in summary_stats.columns
        
        # Check zero counts
        assert summary_stats.loc['age', 'zeros'] == 2
        assert summary_stats.loc['salary', 'zeros'] == 2


class TestProfileReportCategoricalAnalysis:
    """Test categorical column analysis functionality."""
    
    def test_profile_report_with_categorical_columns(self):
        """Test profile_report with categorical columns."""
        df = pd.DataFrame({
            'department': ['HR', 'IT', 'IT', 'HR', 'Finance', 'IT'],
            'city': ['NYC', 'LA', 'NYC', 'LA', 'NYC', 'LA'],
            'level': ['Junior', 'Senior', 'Mid', 'Senior', 'Junior', 'Senior']
        })
        
        result = profile_report(df, top_n_categorical=3, output_format="dict")
        
        # Check categorical insights
        assert 'categorical_insights' in result
        cat_insights = result['categorical_insights']
        
        # Should analyze all 3 columns (or up to top_n_categorical)
        assert isinstance(cat_insights, dict)
        assert len(cat_insights) <= 3
    
    def test_profile_report_categorical_top_n(self):
        """Test top_n_categorical parameter limits analyzed columns."""
        df = pd.DataFrame({
            'col1': ['A', 'B', 'A', 'B', 'A'],
            'col2': ['X', 'Y', 'Z', 'X', 'Y'],
            'col3': ['P', 'Q', 'R', 'S', 'T'],
            'col4': ['M', 'N', 'M', 'N', 'M']
        })
        
        # Test with top_n=2
        result = profile_report(df, top_n_categorical=2, output_format="dict")
        cat_insights = result['categorical_insights']
        
        # Should analyze only top 2
        assert len(cat_insights) <= 2
    
    def test_profile_report_categorical_frequency_distribution(self):
        """Test that categorical frequency distribution is calculated."""
        df = pd.DataFrame({
            'department': ['IT'] * 5 + ['HR'] * 3 + ['Finance'] * 2
        })
        
        result = profile_report(df, top_n_categorical=1, output_format="dict")
        cat_insights = result['categorical_insights']
        
        dept_info = cat_insights['department']
        assert 'top_categories' in dept_info
        assert 'values' in dept_info['top_categories']
        assert 'counts' in dept_info['top_categories']
        assert 'percentages' in dept_info['top_categories']
        
        # Check frequency order (IT should be first with 5 occurrences)
        top_values = dept_info['top_categories']['values']
        top_counts = dept_info['top_categories']['counts']
        assert top_values[0] == 'IT'
        assert top_counts[0] == 5


class TestProfileReportMissingValues:
    """Test missing value analysis functionality."""
    
    def test_profile_report_with_missing_values(self):
        """Test profile_report correctly identifies missing values."""
        df = pd.DataFrame({
            'age': [25, None, 35, None, 45],
            'name': ['Alice', 'Bob', None, 'David', 'Eve'],
            'salary': [50000, 60000, 70000, 80000, 90000]  # No missing
        })
        
        result = profile_report(df, output_format="dict")
        missing = result['missing_values']
        
        # Should have 2 columns with missing values
        assert len(missing) == 2
        assert 'age' in missing['Column'].values
        assert 'name' in missing['Column'].values
    
    def test_profile_report_no_missing_values(self):
        """Test profile_report when no missing values exist."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 40, 45],
            'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve']
        })
        
        result = profile_report(df, output_format="dict")
        missing = result['missing_values']
        
        # Should indicate no missing values
        assert len(missing) == 1
        assert 'No missing values' in missing['Column'].values[0]


class TestProfileReportVisualizations:
    """Test visualization generation functionality."""
    
    def test_profile_report_generates_histograms(self):
        """Test that histograms are generated for numeric columns."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 40, 45],
            'salary': [50000, 60000, 70000, 80000, 90000]
        })
        
        result = profile_report(df, output_format="dict")
        viz = result['visualizations']
        
        # Should have histograms
        assert 'histograms' in viz
        assert viz['histograms'] is not None
    
    def test_profile_report_generates_correlation_heatmap(self):
        """Test that correlation heatmap is generated for multiple numeric columns."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 40, 45],
            'salary': [50000, 60000, 70000, 80000, 90000],
            'experience': [2, 5, 8, 12, 15]
        })
        
        result = profile_report(df, output_format="dict")
        viz = result['visualizations']
        
        # Should have correlation heatmap (needs at least 2 numeric columns)
        assert 'correlation_heatmap' in viz
        assert viz['correlation_heatmap'] is not None
    
    def test_profile_report_no_heatmap_single_numeric(self):
        """Test that no heatmap is generated with only 1 numeric column."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 40, 45],
            'name': ['A', 'B', 'C', 'D', 'E']
        })
        
        result = profile_report(df, output_format="dict")
        viz = result['visualizations']
        
        # Should not have correlation heatmap (only 1 numeric column)
        assert 'correlation_heatmap' not in viz or viz.get('correlation_heatmap') is None


class TestProfileReportEdgeCases:
    """Test edge cases and defensive programming."""
    
    def test_profile_report_empty_dataframe_raises_error(self):
        """Test that empty DataFrame raises ValueError."""
        df = pd.DataFrame()
        
        with pytest.raises(ValueError, match="DataFrame is empty"):
            profile_report(df)
    
    def test_profile_report_invalid_type_raises_error(self):
        """Test that non-DataFrame input raises TypeError."""
        with pytest.raises(TypeError, match="must be a pandas DataFrame"):
            profile_report([1, 2, 3])
    
    def test_profile_report_invalid_output_format_raises_error(self):
        """Test that invalid output_format raises ValueError."""
        df = pd.DataFrame({'x': [1, 2, 3]})
        
        with pytest.raises(ValueError, match="output_format must be"):
            profile_report(df, output_format="pdf")
    
    def test_profile_report_invalid_top_n_raises_error(self):
        """Test that invalid top_n_categorical raises ValueError."""
        df = pd.DataFrame({'x': [1, 2, 3]})
        
        with pytest.raises(ValueError, match="top_n_categorical must be a positive integer"):
            profile_report(df, top_n_categorical=-1)
        
        with pytest.raises(ValueError, match="top_n_categorical must be a positive integer"):
            profile_report(df, top_n_categorical=0)
    
    def test_profile_report_no_numeric_columns(self):
        """Test profile_report with no numeric columns."""
        df = pd.DataFrame({
            'name': ['Alice', 'Bob', 'Charlie'],
            'department': ['HR', 'IT', 'Finance']
        })
        
        result = profile_report(df, output_format="dict")
        
        # Should handle gracefully
        assert result['summary_stats'].empty
        assert 'message' in result['numeric_insights']
    
    def test_profile_report_no_categorical_columns(self):
        """Test profile_report with no categorical columns."""
        df = pd.DataFrame({
            'age': [25, 30, 35],
            'salary': [50000, 60000, 70000]
        })
        
        result = profile_report(df, output_format="dict")
        
        # Should handle gracefully
        cat_insights = result['categorical_insights']
        assert 'message' in cat_insights
    
    def test_profile_report_single_row(self):
        """Test profile_report with single row DataFrame."""
        df = pd.DataFrame({
            'age': [25],
            'name': ['Alice'],
            'salary': [50000]
        })
        
        # Should not raise error
        result = profile_report(df, output_format="dict")
        assert isinstance(result, dict)
    
    def test_profile_report_single_column(self):
        """Test profile_report with single column DataFrame."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 40, 45]
        })
        
        # Should not raise error
        result = profile_report(df, output_format="dict")
        assert isinstance(result, dict)
        assert not result['summary_stats'].empty
    
    def test_profile_report_all_missing_values(self):
        """Test profile_report with all missing values in a column."""
        df = pd.DataFrame({
            'age': [None, None, None],
            'salary': [50000, 60000, 70000]
        })
        
        result = profile_report(df, output_format="dict")
        
        # Should handle gracefully
        missing = result['missing_values']
        assert 'age' in missing['Column'].values


class TestProfileReportHTMLOutput:
    """Test HTML output generation."""
    
    def test_profile_report_html_file_exists(self):
        """Test that HTML file is created and exists."""
        df = pd.DataFrame({
            'age': [25, 30, 35],
            'name': ['Alice', 'Bob', 'Charlie']
        })
        
        result = profile_report(df, output_format="html")
        
        # File should exist
        assert os.path.exists(result)
        
        # Should be readable
        with open(result, 'r', encoding='utf-8') as f:
            content = f.read()
            assert len(content) > 0
            assert '<html' in content.lower()
            assert 'edaflow Profile Report' in content
        
        # Clean up
        os.remove(result)
    
    def test_profile_report_html_contains_sections(self):
        """Test that HTML contains expected sections."""
        df = pd.DataFrame({
            'age': [25, 30, 35],
            'salary': [50000, 60000, 70000],
            'department': ['HR', 'IT', 'IT']
        })
        
        result = profile_report(df, output_format="html")
        
        with open(result, 'r', encoding='utf-8') as f:
            content = f.read()
            
            # Check for key sections
            assert 'Dataset Overview' in content
            assert 'Missing Values' in content
            assert 'Summary Statistics' in content
            assert 'Categorical' in content or 'categorical' in content
        
        # Clean up
        os.remove(result)
    
    def test_profile_report_html_includes_visualizations(self):
        """Test that HTML includes embedded visualizations."""
        df = pd.DataFrame({
            'age': [25, 30, 35, 40, 45],
            'salary': [50000, 60000, 70000, 80000, 90000]
        })
        
        result = profile_report(df, output_format="html")
        
        with open(result, 'r', encoding='utf-8') as f:
            content = f.read()
            
            # Check for base64 encoded images
            assert 'data:image/png;base64,' in content
        
        # Clean up
        os.remove(result)


class TestProfileReportIntegration:
    """Integration tests verifying use of existing edaflow functions."""
    
    def test_profile_report_uses_edaflow_functions(self):
        """Test that profile_report leverages existing edaflow functionality."""
        # This test verifies integration by checking that the report
        # includes data that would come from edaflow functions
        
        df = pd.DataFrame({
            'age': [25, 30, None, 40, 45],
            'salary': [50000, 60000, 70000, 80000, 90000],
            'department': ['HR', 'IT', 'IT', 'HR', 'Finance']
        })
        
        result = profile_report(df, output_format="dict")
        
        # Verify that missing value analysis is present (from check_null_columns)
        assert 'missing_values' in result
        missing = result['missing_values']
        assert not missing.empty or 'No missing values' in str(missing)
        
        # Verify that visualizations are present (from visualize_* functions)
        assert 'visualizations' in result
        assert isinstance(result['visualizations'], dict)
    
    def test_profile_report_comprehensive_workflow(self):
        """Test comprehensive workflow with mixed data types."""
        # Create a realistic dataset
        np.random.seed(42)
        df = pd.DataFrame({
            'id': range(1, 51),
            'age': np.random.randint(20, 60, 50),
            'salary': np.random.randint(40000, 120000, 50),
            'experience': np.random.randint(0, 30, 50),
            'department': np.random.choice(['HR', 'IT', 'Finance', 'Marketing'], 50),
            'city': np.random.choice(['NYC', 'LA', 'Chicago', 'Houston'], 50),
            'performance': np.random.choice(['Low', 'Medium', 'High'], 50)
        })
        
        # Add some missing values
        df.loc[5:7, 'age'] = None
        df.loc[10:12, 'salary'] = None
        
        # Generate report
        result = profile_report(df, top_n_categorical=3, output_format="dict")
        
        # Verify all sections are present
        assert 'overview' in result
        assert 'summary_stats' in result
        assert 'missing_values' in result
        assert 'categorical_insights' in result
        assert 'visualizations' in result
        
        # Verify overview has correct counts
        overview = result['overview']
        row_count = overview[overview['Metric'] == 'Number of Rows']['Value'].values[0]
        col_count = overview[overview['Metric'] == 'Number of Columns']['Value'].values[0]
        assert row_count == '50'
        assert col_count == '7'
        
        # Verify missing values detected
        missing = result['missing_values']
        assert len(missing) >= 2  # Should have at least age and salary


# Run tests if this file is executed directly
if __name__ == "__main__":
    pytest.main([__file__, "-v"])
