"""
Test Symmetric Lasso algorithms in GCONCORD.
"""

import pytest
import numpy as np
import gconcord as gc


class TestSymmetricLasso:
    """Test Symmetric Lasso algorithm variants."""
    
    def setup_method(self):
        """Set up test data."""
        np.random.seed(789)
        self.p = 12
        A = np.random.randn(self.p, self.p)
        self.S = A @ A.T + 0.1 * np.eye(self.p)
        self.lambda_val = 0.1
        
    def test_symlasso_basic(self):
        """Test basic Symmetric Lasso functionality."""
        result = gc.symlasso(self.S, lambda_param=self.lambda_val, max_iterations=500)
        
        assert result['converged']
        assert result['algorithm_used'] in ['symlasso', 'symlasso_iterates']
        assert result['omega'].shape == (self.p, self.p)
        
        # Check symmetry
        omega_dense = result['omega'].toarray()
        np.testing.assert_allclose(omega_dense, omega_dense.T, rtol=1e-10)
        
    def test_symlasso_with_iterates(self):
        """Test Symmetric Lasso with iterate saving."""
        result = gc.symlasso(self.S, lambda_param=self.lambda_val, 
                           algorithm='symlasso_iterates', save_iterates=True, max_iterations=500)
        
        assert result['algorithm_used'] == 'symlasso_iterates'
        # symlasso_iterates saves convergence metrics, not objective values
        assert result['convergence_metrics'] is not None
        assert len(result['convergence_metrics']) > 0
        
    def test_symlasso_vs_concord_separation(self):
        """Test that symlasso and concord are properly separated."""
        # Try to use symlasso algorithm in concord() - should fail
        with pytest.raises(ValueError, match="Unknown algorithm: symlasso"):
            gc.concord(self.S, lambda_param=self.lambda_val, algorithm='symlasso')
            
    def test_symlasso_scalar_lambda_only(self):
        """Test that symlasso only accepts scalar lambda."""
        # Matrix lambda should fail
        Lambda_matrix = 0.1 * np.ones((self.p, self.p))
        
        with pytest.raises(TypeError, match="scalar lambda"):
            gc.symlasso(self.S, lambda_param=Lambda_matrix)
            
    def test_symlasso_auto_selection(self):
        """Test automatic algorithm selection for symlasso."""
        result = gc.symlasso(self.S, lambda_param=self.lambda_val, algorithm='auto', max_iterations=500)
        
        assert result['converged']
        assert result['algorithm_used'] in ['symlasso', 'symlasso_iterates']
        
    def test_symlasso_convergence_properties(self):
        """Test convergence properties of Symmetric Lasso."""
        # Run with different tolerances
        result_loose = gc.symlasso(self.S, lambda_param=self.lambda_val, tolerance=1e-3, max_iterations=500)
        result_tight = gc.symlasso(self.S, lambda_param=self.lambda_val, 
                                 tolerance=1e-8, max_iterations=500)
        
        # Both should produce valid solutions
        assert result_loose['converged']
        omega_loose = result_loose['omega'].toarray()
        omega_tight = result_tight['omega'].toarray()
        
        # Check positive definiteness
        assert np.all(np.linalg.eigvals(omega_loose) > 0)
        assert np.all(np.linalg.eigvals(omega_tight) > 0)
        
    def test_symlasso_sparsity_pattern(self):
        """Test sparsity pattern with different lambda values."""
        lambda_values = [0.01, 0.1, 0.5]
        sparsities = []
        
        for lam in lambda_values:
            result = gc.symlasso(self.S, lambda_param=lam)
            omega = result['omega']
            sparsity = 1 - omega.nnz / (self.p ** 2)
            sparsities.append(sparsity)
            
        # Higher lambda should produce sparser solutions
        assert sparsities[0] <= sparsities[1] <= sparsities[2]
        
    def test_symlasso_verbose_output(self):
        """Test verbose output option."""
        # This test just ensures verbose option doesn't break anything
        result = gc.symlasso(self.S, lambda_param=self.lambda_val, verbose=True, max_iterations=500)
        assert result['converged']