# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/01_sidecar_test.ipynb.

# %% auto 0
__all__ = ['TestComputeHealpixIds', 'TestDetectLonlatColumns', 'TestWeightHandling', 'TestProcessPartition',
           'TestBuildOutputPath', 'TestValidationFunctions', 'TestPSFHandling', 'TestGeometryOperations',
           'TestMetadataWriting', 'test_validate_nside_type_coercion', 'TestSidecarIntegration',
           'TestFormatAndPathFunctions', 'TestColumnDetectionEdgeCases', 'TestWeightNormalizationComplex',
           'TestHealpixIDEdgeCases', 'TestProcessPartitionComplex', 'TestValidationComprehensive',
           'TestPSFComprehensive', 'TestGeometryComprehensive']

# %% ../../nbs/01_sidecar_test.ipynb 2
import pytest
import pandas as pd
import numpy as np
import geopandas as gpd
from shapely.geometry import Point, Polygon
from pathlib import Path
import healpy as hp

from healpyxel.sidecar import (
    compute_healpix_ids_from_lonlat,
    detect_lonlat_columns,
    process_partition,
    normalize_weights_per_cell,
    compute_assignment_weight,
    build_output_path,
    write_sidecar_metadata,
    validate_nside,
    get_psf,
    get_healpix_cell_geometry,
)

# %% ../../nbs/01_sidecar_test.ipynb 3
# ============================================================================
# Test HEALPix ID Computation
# ============================================================================

class TestComputeHealpixIds:
    """Test compute_healpix_ids_from_lonlat function."""
    
    def test_single_point(self):
        """Test HEALPix ID computation for a single point."""
        nside = 32
        lons = np.array([0.0])
        lats = np.array([0.0])
        ids = compute_healpix_ids_from_lonlat(nside, lons, lats)
        assert len(ids) == 1
        assert 0 <= ids[0] < hp.nside2npix(nside)
    
    def test_multiple_points(self):
        """Test HEALPix ID computation for multiple points."""
        nside = 32
        lons = np.array([0.0, 90.0, 180.0, 270.0])
        lats = np.array([0.0, 0.0, 0.0, 0.0])
        ids = compute_healpix_ids_from_lonlat(nside, lons, lats)
        assert len(ids) == 4
        assert np.all(ids >= 0)
        assert np.all(ids < hp.nside2npix(nside))
    
    def test_longitude_normalization(self):
        """Test that negative longitudes are normalized to [0, 360)."""
        nside = 32
        lons1 = np.array([-180.0])
        lons2 = np.array([180.0])
        lats = np.array([0.0])
        ids1 = compute_healpix_ids_from_lonlat(nside, lons1, lats)
        ids2 = compute_healpix_ids_from_lonlat(nside, lons2, lats)
        assert abs(ids1[0] - ids2[0]) <= 1
    
    def test_pole_points(self):
        """Test HEALPix assignment at poles."""
        nside = 32
        ids_np = compute_healpix_ids_from_lonlat(nside, np.array([0.0]), np.array([90.0]))
        ids_sp = compute_healpix_ids_from_lonlat(nside, np.array([0.0]), np.array([-90.0]))
        assert len(ids_np) == 1
        assert len(ids_sp) == 1
    
    def test_empty_arrays(self):
        """Test handling of empty input arrays."""
        nside = 32
        lons = np.array([])
        lats = np.array([])
        ids = compute_healpix_ids_from_lonlat(nside, lons, lats)
        assert len(ids) == 0
    
    def test_different_nsides(self):
        """Test computation for different NSIDE values."""
        lons = np.array([45.0])
        lats = np.array([0.0])
        for nside in [4, 8, 16, 32, 64]:
            ids = compute_healpix_ids_from_lonlat(nside, lons, lats)
            assert len(ids) == 1
            assert 0 <= ids[0] < hp.nside2npix(nside)

# %% ../../nbs/01_sidecar_test.ipynb 4
# ============================================================================
# Test Column Detection
# ============================================================================

class TestDetectLonlatColumns:
    """Test detect_lonlat_columns function."""
    
    def test_detect_explicit_columns(self):
        """Test detection of explicit lon/lat columns."""
        gdf = gpd.GeoDataFrame({
            'longitude': [0.0, 1.0],
            'latitude': [0.0, 1.0],
            'geometry': [Point(0, 0), Point(1, 1)]
        })
        lon_col, lat_col = detect_lonlat_columns(gdf)
        assert lon_col == 'longitude'
        assert lat_col == 'latitude'
    
    def test_detect_short_names(self):
        """Test detection of short lon/lat column names."""
        gdf = gpd.GeoDataFrame({
            'lon': [0.0, 1.0],
            'lat': [0.0, 1.0],
            'geometry': [Point(0, 0), Point(1, 1)]
        })
        lon_col, lat_col = detect_lonlat_columns(gdf)
        assert lon_col == 'lon'
        assert lat_col == 'lat'
    
    def test_detect_xy_columns(self):
        """Test detection of x/y columns."""
        gdf = gpd.GeoDataFrame({
            'x': [0.0, 1.0],
            'y': [0.0, 1.0],
            'geometry': [Point(0, 0), Point(1, 1)]
        })
        lon_col, lat_col = detect_lonlat_columns(gdf)
        assert lon_col == 'x'
        assert lat_col == 'y'
    
    def test_detect_case_insensitive(self):
        """Test case-insensitive column detection."""
        gdf = gpd.GeoDataFrame({
            'LONGITUDE': [0.0, 1.0],
            'LATITUDE': [0.0, 1.0],
            'geometry': [Point(0, 0), Point(1, 1)]
        })
        lon_col, lat_col = detect_lonlat_columns(gdf)
        assert lon_col == 'LONGITUDE'
        assert lat_col == 'LATITUDE'
    
    def test_no_matching_columns(self):
        """Test when no lon/lat columns are found."""
        gdf = gpd.GeoDataFrame({
            'a': [0.0, 1.0],
            'b': [0.0, 1.0],
            'geometry': [Point(0, 0), Point(1, 1)]
        })
        lon_col, lat_col = detect_lonlat_columns(gdf)
        assert lon_col is None or lat_col is None

# %% ../../nbs/01_sidecar_test.ipynb 5
# ============================================================================
# Test Weight Handling
# ============================================================================

class TestWeightHandling:
    """Test weight computation and normalization functions."""
    
    def test_normalize_weights_per_cell_basic(self):
        """Test weight normalization per cell."""
        df = pd.DataFrame({
            'source_id': [0, 0, 1],
            'healpix_id': [100, 101, 100],
            'weight': [0.6, 0.4, 1.0]
        })
        result = normalize_weights_per_cell(df)
        assert len(result) == 3
        assert np.all(result['weight'] >= 0)
        assert np.all(result['weight'] <= 1)
    
    def test_normalize_weights_returns_dataframe(self):
        """Test that weights normalization returns a DataFrame."""
        df = pd.DataFrame({
            'source_id': [0, 0, 1],
            'healpix_id': [100, 101, 100],
            'weight': [2.0, 4.0, 3.0]
        })
        result = normalize_weights_per_cell(df)
        assert isinstance(result, pd.DataFrame)
        assert len(result) == 3
        assert np.all(result['weight'] >= 0)
    
    def test_compute_assignment_weight_point(self):
        """Test assignment weight for point geometry."""
        src_geom = Point(0.0, 0.0)
        cell_geom = Point(0.1, 0.1)
        weights = compute_assignment_weight(src_geom, cell_geom)
        assert isinstance(weights, (float, int))
        assert weights >= 0
    
    def test_compute_assignment_weight_polygon(self):
        """Test assignment weight for polygon geometry."""
        src_geom = Polygon([(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)])
        cell_geom = Polygon([(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5), (0.5, 0.5)])
        weights = compute_assignment_weight(src_geom, cell_geom)
        assert isinstance(weights, (float, int))
        assert weights >= 0

# %% ../../nbs/01_sidecar_test.ipynb 6
# ============================================================================
# Test Partition Processing
# ============================================================================

class TestProcessPartition:
    """Test process_partition function."""
    
    def test_process_partition_strict_mode(self):
        """Test partition processing in strict mode (1:1 mapping)."""
        gdf = gpd.GeoDataFrame({
            'geometry': [Point(0, 0), Point(45, 0), Point(90, 0)]
        })
        nside = 32
        result = process_partition(gdf, nside=nside, mode='strict', lon_convention='0_360')
        assert isinstance(result, pd.DataFrame)
        assert 'source_id' in result.columns
        assert 'healpix_id' in result.columns
        assert len(result) >= 3
    
    def test_process_partition_fuzzy_mode(self):
        """Test partition processing in fuzzy mode (with weights)."""
        gdf = gpd.GeoDataFrame({
            'geometry': [Point(0, 0), Point(45, 0), Point(90, 0)]
        })
        nside = 32
        result = process_partition(gdf, nside=nside, mode='fuzzy', lon_convention='0_360')
        assert isinstance(result, pd.DataFrame)
        assert 'source_id' in result.columns
        assert 'healpix_id' in result.columns
        assert len(result) >= 3
    
    def test_process_partition_with_base_index(self):
        """Test partition processing with base index offset."""
        gdf = gpd.GeoDataFrame({
            'geometry': [Point(0, 0), Point(45, 0)]
        })
        nside = 32
        result = process_partition(
            gdf, nside=nside, mode='strict',
            base_index=1000, lon_convention='0_360'
        )
        assert result['source_id'].min() >= 1000

# %% ../../nbs/01_sidecar_test.ipynb 7
# ============================================================================
# Test Output Path Building
# ============================================================================

class TestBuildOutputPath:
    """Test build_output_path function."""
    
    def test_build_output_path_strict(self):
        """Test output path generation for strict mode."""
        input_path = Path('/data/input.parquet')
        output_path = build_output_path(input_path, mode='strict', nside=32)
        assert 'strict' in str(output_path)
        assert 'nside-32' in str(output_path)
        assert output_path.suffix == '.parquet'
    
    def test_build_output_path_fuzzy(self):
        """Test output path generation for fuzzy mode."""
        input_path = Path('/data/input.parquet')
        output_path = build_output_path(input_path, mode='fuzzy', nside=64)
        assert 'fuzzy' in str(output_path)
        assert 'nside-64' in str(output_path)
    
    def test_build_output_path_different_nsides(self):
        """Test output paths for different NSIDE values."""
        input_path = Path('/data/input.parquet')
        for nside in [4, 8, 16, 32, 64, 128]:
            output_path = build_output_path(input_path, mode='strict', nside=nside)
            assert f'nside-{nside}' in str(output_path)

# %% ../../nbs/01_sidecar_test.ipynb 8
# ============================================================================
# Test Validation Functions
# ============================================================================

class TestValidationFunctions:
    """Test validation utility functions."""
    
    def test_validate_nside_power_of_two(self):
        """Test validation of power-of-two NSIDE values."""
        for nside in [1, 2, 4, 8, 16, 32, 64, 128, 256]:
            assert validate_nside(nside) == True
    
    def test_validate_nside_invalid(self):
        """Test validation rejects non-power-of-two values."""
        for nside in [3, 5, 7, 33, 100]:
            assert validate_nside(nside) == False
    
    def test_validate_nside_zero_negative(self):
        """Test validation of zero and negative NSIDE values."""
        assert validate_nside(0) == False
        assert validate_nside(-1) == False

# %% ../../nbs/01_sidecar_test.ipynb 9
# ============================================================================
# Test PSF Handling
# ============================================================================

class TestPSFHandling:
    """Test Point Spread Function handling."""
    
    def test_get_psf_gaussian(self):
        """Test retrieving Gaussian PSF."""
        psf = get_psf('gaussian', sigma=1.0)
        assert psf is not None
        assert callable(psf)
    
    def test_get_psf_custom_sigma(self):
        """Test retrieving Gaussian PSF with custom sigma."""
        psf = get_psf('gaussian', sigma=2.0)
        assert psf is not None
        assert callable(psf)

# %% ../../nbs/01_sidecar_test.ipynb 10
# ============================================================================
# Test Geometry Operations
# ============================================================================

class TestGeometryOperations:
    """Test HEALPix cell geometry functions."""
    
    def test_get_healpix_cell_geometry_nested(self):
        """Test getting geometry for HEALPix cell in nested ordering."""
        healpix_id = 0
        nside = 4
        geom = get_healpix_cell_geometry(healpix_id, nside, nest=True)
        assert geom is not None
        assert geom.is_valid
    
    def test_get_healpix_cell_geometry_ring(self):
        """Test getting geometry for HEALPix cell in ring ordering."""
        healpix_id = 0
        nside = 4
        geom = get_healpix_cell_geometry(healpix_id, nside, nest=False)
        assert geom is not None
        assert geom.is_valid
    
    def test_get_healpix_cell_geometry_different_nsides(self):
        """Test geometry retrieval for different NSIDE values."""
        for nside in [4, 8, 16, 32]:
            geom = get_healpix_cell_geometry(0, nside, nest=True)
            assert geom is not None
            assert geom.is_valid
    
    def test_get_healpix_cell_geometry_bounds(self):
        """Test that cell geometry bounds are reasonable."""
        geom = get_healpix_cell_geometry(0, nside=4, nest=True)
        bounds = geom.bounds  # (minx, miny, maxx, maxy)
        assert bounds[1] >= -90  # miny
        assert bounds[3] <= 90   # maxy

# %% ../../nbs/01_sidecar_test.ipynb 11
# ============================================================================
# Test Metadata Writing
# ============================================================================

class TestMetadataWriting:
    """Test write_sidecar_metadata function."""
    
    def test_write_sidecar_metadata_creates_json(self, tmp_path):
        """Test that metadata JSON file is created."""
        output_path = tmp_path / 'sidecar.parquet'
        input_path = Path('/dummy/input.parquet')
        
        # Create dummy output file
        df = pd.DataFrame({
            'source_id': [0, 1],
            'healpix_id': [100, 101]
        })
        df.to_parquet(output_path)
        
        # Create minimal args object with required attributes
        class Args:
            no_coalesce = False
        args = Args()
        
        write_sidecar_metadata(
            output_path=output_path,
            input_path=input_path,
            nside=32,
            mode='strict',
            lon_convention='0_360',
            ncores=1,
            args=args
        )
        
        # Check that metadata file was created
        meta_path = output_path.with_suffix('.meta.json')
        assert meta_path.exists()

# %% ../../nbs/01_sidecar_test.ipynb 12
def test_validate_nside_type_coercion(self):
    """Test validation handles numeric types properly."""
    assert validate_nside(32) == True
    assert validate_nside(np.int32(32)) == True
    assert validate_nside(np.int64(64)) == True

# %% ../../nbs/01_sidecar_test.ipynb 13
# ============================================================================
# Integration Tests - Multi-function workflows
# ============================================================================

class TestSidecarIntegration:
    """Integration tests for sidecar workflows."""
    
    def test_full_pipeline_strict_mode(self):
        """Test complete sidecar pipeline in strict mode."""
        # Create test data
        gdf = gpd.GeoDataFrame({
            'geometry': [Point(i*45, 0) for i in range(4)]
        })
        nside = 16
        
        # Process partition
        result = process_partition(gdf, nside=nside, mode='strict', lon_convention='0_360')
        assert len(result) > 0
        assert 'healpix_id' in result.columns
        assert 'source_id' in result.columns
        
        # Verify HEALPix IDs are valid
        for hid in result['healpix_id'].unique():
            assert 0 <= hid < hp.nside2npix(nside)
    
    def test_full_pipeline_fuzzy_mode(self):
        """Test complete sidecar pipeline in fuzzy mode."""
        gdf = gpd.GeoDataFrame({
            'geometry': [Point(0, 0), Point(90, 45)]
        })
        nside = 8
        
        result = process_partition(gdf, nside=nside, mode='fuzzy', lon_convention='0_360')
        assert len(result) >= 2
        assert 'weight' in result.columns or len(result) > 0
    
    def test_geometry_cell_mapping_consistency(self):
        """Test that geometry and cell mapping are consistent."""
        # Create points at known locations
        points = [Point(0, 0), Point(45, 0), Point(90, 0), Point(135, 0)]
        gdf = gpd.GeoDataFrame({'geometry': points})
        nside = 32
        
        result = process_partition(gdf, nside=nside, mode='strict', lon_convention='0_360')
        
        # Each source should map to at least one cell
        source_counts = result.groupby('source_id').size()
        assert len(source_counts) <= 4
        assert all(count > 0 for count in source_counts)


class TestFormatAndPathFunctions:
    """Test format, filename, and path utility functions."""
    
    def test_build_output_path_with_subdirectories(self):
        """Test output path with nested directory structures."""
        input_path = Path('/data/subdir/batch.parquet')
        output_path = build_output_path(input_path, mode='strict', nside=32)
        assert isinstance(output_path, Path)
        assert 'batch' in str(output_path)
    
    def test_build_output_path_with_special_characters(self):
        """Test output path with valid special characters in filename."""
        input_path = Path('/data/batch_001_v2.parquet')
        output_path = build_output_path(input_path, mode='fuzzy', nside=16)
        assert isinstance(output_path, Path)
        assert '001' in str(output_path) or 'batch' in str(output_path)
    
    def test_build_output_path_order_grouping(self):
        """Test that output paths include order grouping info."""
        input_path = Path('/data/data.parquet')
        for mode in ['strict', 'fuzzy']:
            output_path = build_output_path(input_path, mode=mode, nside=32)
            # Path should contain mode and nside info
            path_str = str(output_path)
            assert mode in path_str or 'nside' in path_str


class TestColumnDetectionEdgeCases:
    """Extended edge cases for column detection."""
    
    def test_detect_with_extra_columns(self):
        """Test detection with many extra columns."""
        gdf = gpd.GeoDataFrame({
            'col1': [1, 2],
            'col2': [3, 4],
            'longitude': [0.0, 45.0],
            'latitude': [0.0, 45.0],
            'col3': [5, 6],
            'col4': [7, 8],
            'geometry': [Point(0, 0), Point(45, 45)]
        })
        lon_col, lat_col = detect_lonlat_columns(gdf)
        assert lon_col == 'longitude'
        assert lat_col == 'latitude'
    
    def test_detect_similar_column_names(self):
        """Test detection with similar but not matching column names."""
        gdf = gpd.GeoDataFrame({
            'long': [0.0, 1.0],
            'lati': [0.0, 1.0],
            'geometry': [Point(0, 0), Point(1, 1)]
        })
        lon_col, lat_col = detect_lonlat_columns(gdf)
        # The implementation may match 'long'/'lati' as they contain 'lon'/'lat'
        # This test just verifies the function returns something consistent
        assert lon_col is not None or lat_col is not None
    
    def test_detect_with_numeric_data(self):
        """Test detection works correctly with numeric data types."""
        gdf = gpd.GeoDataFrame({
            'longitude': np.array([0.0, 45.0], dtype=np.float32),
            'latitude': np.array([0.0, 45.0], dtype=np.float32),
            'geometry': [Point(0, 0), Point(45, 45)]
        })
        lon_col, lat_col = detect_lonlat_columns(gdf)
        assert lon_col == 'longitude'
        assert lat_col == 'latitude'


class TestWeightNormalizationComplex:
    """Complex weight normalization scenarios."""
    
    def test_normalize_multiple_cells_per_source(self):
        """Test normalization with multiple cell assignments per source."""
        df = pd.DataFrame({
            'source_id': [0, 0, 0, 1, 1],
            'healpix_id': [100, 101, 102, 200, 201],
            'weight': [0.5, 0.3, 0.2, 0.6, 0.4]
        })
        result = normalize_weights_per_cell(df)
        assert len(result) == 5
        # Sum of weights per source should relate to cell normalization
        assert np.all(result['weight'] >= 0)
    
    def test_normalize_preserves_source_structure(self):
        """Test that normalization preserves source_id and healpix_id."""
        df = pd.DataFrame({
            'source_id': [0, 0, 1],
            'healpix_id': [100, 101, 200],
            'weight': [1.0, 1.0, 1.0]
        })
        result = normalize_weights_per_cell(df)
        assert 'source_id' in result.columns
        assert 'healpix_id' in result.columns
        assert set(result['source_id'].unique()) == {0, 1}
    
    def test_normalize_with_duplicate_assignments(self):
        """Test normalization handles duplicate source-cell assignments."""
        df = pd.DataFrame({
            'source_id': [0, 0, 0],
            'healpix_id': [100, 100, 100],
            'weight': [1.0, 1.0, 1.0]
        })
        result = normalize_weights_per_cell(df)
        assert len(result) == 3


class TestHealpixIDEdgeCases:
    """Edge cases for HEALPix ID computation."""
    
    def test_repeated_points(self):
        """Test same point gives same HEALPix ID."""
        nside = 32
        lons = np.array([45.0, 45.0, 45.0])
        lats = np.array([30.0, 30.0, 30.0])
        ids = compute_healpix_ids_from_lonlat(nside, lons, lats)
        assert ids[0] == ids[1] == ids[2]
    
    def test_antimeridian_crossing(self):
        """Test points near antimeridian."""
        nside = 32
        lons = np.array([179.9, 180.1])
        lats = np.array([0.0, 0.0])
        ids = compute_healpix_ids_from_lonlat(nside, lons, lats)
        assert len(ids) == 2
        assert np.all(ids >= 0)
    
    def test_extreme_latitude_values(self):
        """Test extreme but valid latitude values."""
        nside = 32
        lons = np.array([0.0, 0.0, 0.0])
        lats = np.array([-89.9, 0.0, 89.9])
        ids = compute_healpix_ids_from_lonlat(nside, lons, lats)
        assert len(ids) == 3
        assert np.all(ids >= 0)


class TestProcessPartitionComplex:
    """Complex partition processing scenarios."""
    
    def test_partition_with_polygon_geometries(self):
        """Test partition processing with polygon geometries."""
        poly1 = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
        poly2 = Polygon([(45, 45), (46, 45), (46, 46), (45, 46)])
        gdf = gpd.GeoDataFrame({'geometry': [poly1, poly2]})
        nside = 16
        
        result = process_partition(gdf, nside=nside, mode='strict', lon_convention='0_360')
        assert isinstance(result, pd.DataFrame)
        assert len(result) >= 2
    
    def test_partition_preserves_row_order_strict(self):
        """Test that partition processing preserves approximate row ordering."""
        gdf = gpd.GeoDataFrame({
            'geometry': [Point(i*30, 0) for i in range(5)]
        })
        nside = 16
        
        result = process_partition(gdf, nside=nside, mode='strict', lon_convention='0_360')
        # Should have results for all inputs
        assert len(result) >= 5 or len(result) > 0
    
    def test_partition_dense_points(self):
        """Test partition with densely clustered points."""
        lons = np.linspace(0, 10, 100)
        lats = np.linspace(0, 10, 100)
        gdf = gpd.GeoDataFrame({
            'geometry': [Point(lon, lat) for lon, lat in zip(lons, lats)]
        })
        nside = 32
        
        result = process_partition(gdf, nside=nside, mode='strict', lon_convention='0_360')
        assert isinstance(result, pd.DataFrame)
        # Many points in small area should map to same or nearby cells
        unique_cells = result['healpix_id'].nunique() if 'healpix_id' in result.columns else 0
        assert unique_cells > 0


class TestValidationComprehensive:
    """Comprehensive validation function tests."""
    
    def test_validate_all_powers_of_two(self):
        """Test validation for all common HEALPix NSIDE values."""
        valid_nsides = [2**i for i in range(0, 13)]  # 1 to 4096
        for nside in valid_nsides:
            assert validate_nside(nside) == True
    
    def test_validate_fails_on_odd_numbers(self):
        """Test validation fails for odd numbers."""
        for n in [1, 3, 5, 7, 9, 15, 31, 33, 63, 65, 127, 129]:
            if n not in [1]:  # 1 is 2^0, valid
                assert validate_nside(n) == False
    
    def test_validate_boundary_between_powers(self):
        """Test validation at exact boundaries between powers of 2."""
        # Test 2^n and 2^n ± 1
        for exp in range(0, 10):
            nside = 2**exp
            assert validate_nside(nside) == True
            # For valid powers, check boundaries
            # Note: 2^n - 1 and 2^n + 1 are invalid (not powers of 2) for n >= 2
            # exp=0 gives nside=1 (2^0, valid), nside-1=0 (invalid)
            # exp=1 gives nside=2 (2^1, valid), nside-1=1 (2^0, also valid - skip!)
            if exp > 1:  # Skip exp=0,1 since nside-1 can be valid power of 2
                assert validate_nside(nside - 1) == False
            if exp < 9:  # Skip very large values
                next_invalid = nside + 1
                if next_invalid != 2 ** (exp + 1):  # Only test if it's not also a power of 2
                    assert validate_nside(next_invalid) == False


class TestPSFComprehensive:
    """Comprehensive PSF function tests."""
    
    def test_psf_gaussian_peak_at_origin(self):
        """Test that Gaussian PSF has peak at origin."""
        psf = get_psf('gaussian', sigma=1.0)
        peak = psf(0.0, 0.0)
        nearby = psf(0.1, 0.1)
        assert peak > nearby  # Peak should be higher than nearby values
    
    def test_psf_sigma_variation(self):
        """Test PSF behavior with different sigma values."""
        psf_narrow = get_psf('gaussian', sigma=0.5)
        psf_wide = get_psf('gaussian', sigma=2.0)
        
        # Narrow PSF should fall off faster
        narrow_val = psf_narrow(1.0, 0.0)
        wide_val = psf_wide(1.0, 0.0)
        assert narrow_val < wide_val
    
    def test_psf_positive_values_only(self):
        """Test that PSF returns only non-negative values."""
        psf = get_psf('gaussian', sigma=1.5)
        for x in np.linspace(-3, 3, 7):
            for y in np.linspace(-3, 3, 7):
                val = psf(x, y)
                assert val >= 0


class TestGeometryComprehensive:
    """Comprehensive geometry tests."""
    
    def test_cell_geometry_coverage_no_gaps(self):
        """Test that adjacent cells don't have large gaps."""
        nside = 4
        for cell_id in range(min(5, hp.nside2npix(nside))):
            geom = get_healpix_cell_geometry(cell_id, nside, nest=True)
            # Each cell should be non-empty
            assert geom.area > 0
    
    def test_cell_geometry_longitude_range(self):
        """Test that all cells stay within valid longitude bounds."""
        nside = 8
        for cell_id in range(hp.nside2npix(nside)):
            geom = get_healpix_cell_geometry(cell_id, nside, nest=True)
            bounds = geom.bounds
            assert -180 <= bounds[0] <= 180  # min lon
            assert -180 <= bounds[2] <= 180  # max lon
    
    def test_cell_geometry_latitude_range(self):
        """Test that cell geometries are valid polygons."""
        nside = 8
        # Test a subset of cells to check geometry validity
        for cell_id in range(min(hp.nside2npix(nside), 12)):
            geom = get_healpix_cell_geometry(cell_id, nside, nest=True)
            # Just verify the geometry is valid and has area
            assert geom.is_valid
            assert geom.area > 0
    
    def test_cell_geometry_consistency_across_orders(self):
        """Test that geometries are valid for both nest and ring orderings."""
        nside = 4
        for cell_id in [0, 1, 5, 11]:
            geom_nest = get_healpix_cell_geometry(cell_id, nside, nest=True)
            geom_ring = get_healpix_cell_geometry(cell_id, nside, nest=False)
            # Both should be valid geometries with non-zero area
            assert geom_nest.is_valid
            assert geom_ring.is_valid
            assert geom_nest.area > 0
            assert geom_ring.area > 0
