"""
Custom Criteria System - Easily Add Your Own Evaluation Criteria

Add any criterion you want:
- Climate change predictions
- Rainfall data
- Soil quality
- Distance to airports
- Population density
- Anything else!

Each criterion is a separate class - easy to add, remove, or modify.
"""
import pandas as pd
import requests
import json
import math
from datetime import datetime

# ============================================================================
# BASE CRITERION CLASS
# ============================================================================

class Criterion:
    """Base class for all criteria"""

    def __init__(self):
        self.name = "base_criterion"
        self.display_name = "Base Criterion"
        self.weight = 1.0
        self.description = "Base criterion class"

    def evaluate(self, property_data):
        """
        Evaluate this criterion for a property

        Args:
            property_data: dict with property info (lat, lon, land_size, etc.)

        Returns:
            dict with:
                - score (1-5)
                - reasoning (list of strings)
                - raw_data (dict of raw values)
        """
        return {
            'score': 3,
            'reasoning': ["Not implemented"],
            'raw_data': {}
        }

# ============================================================================
# CLIMATE & WEATHER CRITERIA
# ============================================================================

class RainfallCriterion(Criterion):
    """Evaluates annual rainfall (important for farming)"""

    def __init__(self):
        super().__init__()
        self.name = "rainfall"
        self.display_name = "🌧️ Rainfall"
        self.weight = 2.0
        self.description = "Annual precipitation for farming"

    def evaluate(self, property_data):
        """
        Uses Open-Meteo API (FREE!) to get climate data
        """
        lat = property_data.get('latitude')
        lon = property_data.get('longitude')

        if not lat or not lon:
            return {
                'score': 3,
                'reasoning': ["No coordinates - cannot assess rainfall"],
                'raw_data': {}
            }

        try:
            # Open-Meteo API - FREE, no API key needed!
            url = f"https://archive-api.open-meteo.com/v1/archive"
            params = {
                'latitude': lat,
                'longitude': lon,
                'start_date': '2023-01-01',
                'end_date': '2023-12-31',
                'daily': 'precipitation_sum',
                'timezone': 'auto'
            }

            response = requests.get(url, params=params, timeout=10)
            data = response.json()

            if 'daily' in data and 'precipitation_sum' in data['daily']:
                # Calculate annual rainfall
                daily_precip = data['daily']['precipitation_sum']
                annual_rainfall = sum(p for p in daily_precip if p is not None)

                # Score based on rainfall (optimal for regenerative farming: 700-1300mm/year)
                reasoning = []
                if 800 <= annual_rainfall <= 1300:
                    score = 5
                    reasoning.append(f"Excellent rainfall: {annual_rainfall:.0f}mm/year (ideal for farming)")
                elif 700 <= annual_rainfall < 800 or 1300 < annual_rainfall <= 1600:
                    score = 4.5
                    reasoning.append(f"Very good rainfall: {annual_rainfall:.0f}mm/year")
                elif 600 <= annual_rainfall < 700 or 1600 < annual_rainfall <= 1800:
                    score = 4
                    reasoning.append(f"Good rainfall: {annual_rainfall:.0f}mm/year")
                elif 500 <= annual_rainfall < 600:
                    score = 3.5
                    reasoning.append(f"Adequate rainfall: {annual_rainfall:.0f}mm/year")
                elif 400 <= annual_rainfall < 500 or 1800 < annual_rainfall <= 2200:
                    score = 3
                    reasoning.append(f"Moderate rainfall: {annual_rainfall:.0f}mm/year")
                elif 250 <= annual_rainfall < 400:
                    score = 2
                    reasoning.append(f"Low rainfall: {annual_rainfall:.0f}mm/year (irrigation likely needed)")
                else:
                    score = 1.5
                    reasoning.append(f"Very low rainfall: {annual_rainfall:.0f}mm/year (irrigation required)")

                return {
                    'score': score,
                    'reasoning': reasoning,
                    'raw_data': {
                        'annual_rainfall_mm': annual_rainfall,
                        'source': 'open-meteo'
                    }
                }
        except Exception as e:
            return {
                'score': 3,
                'reasoning': [f"Could not fetch rainfall data: {e}"],
                'raw_data': {}
            }

        return {
            'score': 3,
            'reasoning': ["Rainfall data unavailable"],
            'raw_data': {}
        }

class TemperatureCriterion(Criterion):
    """Evaluates temperature range and growing season"""

    def __init__(self):
        super().__init__()
        self.name = "temperature"
        self.display_name = "🌡️ Temperature"
        self.weight = 1.5
        self.description = "Temperature range and growing season"

    def evaluate(self, property_data):
        lat = property_data.get('latitude')
        lon = property_data.get('longitude')

        if not lat or not lon:
            return {
                'score': 3,
                'reasoning': ["No coordinates"],
                'raw_data': {}
            }

        try:
            url = f"https://archive-api.open-meteo.com/v1/archive"
            params = {
                'latitude': lat,
                'longitude': lon,
                'start_date': '2023-01-01',
                'end_date': '2023-12-31',
                'daily': 'temperature_2m_max,temperature_2m_min',
                'timezone': 'auto'
            }

            response = requests.get(url, params=params, timeout=10)
            data = response.json()

            if 'daily' in data:
                max_temps = [t for t in data['daily']['temperature_2m_max'] if t is not None]
                min_temps = [t for t in data['daily']['temperature_2m_min'] if t is not None]

                avg_max = sum(max_temps) / len(max_temps)
                avg_min = sum(min_temps) / len(min_temps)
                avg_temp = (avg_max + avg_min) / 2

                # Count growing season days (>5°C)
                growing_days = sum(1 for t in min_temps if t > 5)

                reasoning = []
                # Score based on growing season and average temp (optimized for market garden)
                if growing_days >= 280 and 13 <= avg_temp <= 17:
                    score = 5
                    reasoning.append(f"Optimal climate: {growing_days} days, {avg_temp:.1f}°C avg (perfect for year-round production)")
                elif growing_days >= 260 and 11 <= avg_temp <= 19:
                    score = 4.5
                    reasoning.append(f"Excellent climate: {growing_days} days, {avg_temp:.1f}°C avg")
                elif growing_days >= 240:
                    score = 4
                    reasoning.append(f"Very good growing season: {growing_days} days")
                elif growing_days >= 210:
                    score = 3.5
                    reasoning.append(f"Good growing season: {growing_days} days")
                elif growing_days >= 180:
                    score = 3
                    reasoning.append(f"Moderate growing season: {growing_days} days")
                elif growing_days >= 160:
                    score = 2.5
                    reasoning.append(f"Limited growing season: {growing_days} days")
                elif growing_days >= 140:
                    score = 2
                    reasoning.append(f"Short growing season: {growing_days} days")
                else:
                    score = 1.5
                    reasoning.append(f"Very short growing season: {growing_days} days (seasonal only)")

                return {
                    'score': score,
                    'reasoning': reasoning,
                    'raw_data': {
                        'avg_temperature': avg_temp,
                        'growing_days': growing_days,
                        'avg_max': avg_max,
                        'avg_min': avg_min
                    }
                }
        except Exception as e:
            return {
                'score': 3,
                'reasoning': [f"Temperature data unavailable: {e}"],
                'raw_data': {}
            }

        return {
            'score': 3,
            'reasoning': ["Temperature data unavailable"],
            'raw_data': {}
        }

class ClimateChangeCriterion(Criterion):
    """Evaluates climate change risk for the location"""

    def __init__(self):
        super().__init__()
        self.name = "climate_change_risk"
        self.display_name = "🌍 Climate Risk"
        self.weight = 2.5
        self.description = "Climate change vulnerability and adaptation potential"

    def evaluate(self, property_data):
        lat = property_data.get('latitude')
        lon = property_data.get('longitude')
        country = property_data.get('country', '')

        if not lat or not lon:
            return {
                'score': 3,
                'reasoning': ["No coordinates - cannot assess climate risk"],
                'raw_data': {}
            }

        reasoning = []
        risk_factors = []
        score = 5  # Start optimistic

        # Factor 1: Latitude-based assessment
        abs_lat = abs(lat)
        if abs_lat < 30:  # Tropical/subtropical
            risk_factors.append("tropical_heat")
            score -= 1
            reasoning.append("Subtropical location - increasing heat stress risk")
        elif abs_lat > 50:  # Northern regions
            risk_factors.append("northern_warming")
            reasoning.append("Northern location - rapid warming expected")

        # Factor 2: Coastal proximity (sea level rise)
        # Check if near coast (simplified - within 50km of sea level)
        elevation = property_data.get('elevation', None)
        if elevation is not None and elevation < 10 and abs_lat > 30:
            risk_factors.append("sea_level_rise")
            score -= 1
            reasoning.append("Low elevation - sea level rise risk")

        # Factor 3: Mediterranean region (drought risk)
        if 35 <= abs_lat <= 45 and -10 <= lon <= 45:
            risk_factors.append("mediterranean_drought")
            score -= 1
            reasoning.append("Mediterranean region - increasing drought risk")

        # Factor 4: Country-specific known risks
        high_risk_regions = {
            'Spain': 'Severe drought risk, desertification',
            'Portugal': 'Wildfire and drought risk',
            'Greece': 'Extreme heat and water scarcity',
            'Italy': 'Heat waves and water stress'
        }

        if country in high_risk_regions:
            risk_factors.append("country_risk")
            score -= 1
            reasoning.append(high_risk_regions[country])

        # Factor 5: Positive factors (adaptation potential)
        if 43 <= abs_lat <= 52:  # Temperate Europe - good adaptation potential
            score += 1
            reasoning.append("Temperate zone - good adaptation potential")

        # Final score
        score = max(1, min(5, score))

        if score >= 4:
            reasoning.append("Low climate risk")
        elif score >= 3:
            reasoning.append("Moderate climate risk")
        else:
            reasoning.append("High climate risk - adaptation measures needed")

        return {
            'score': score,
            'reasoning': reasoning,
            'raw_data': {
                'risk_factors': risk_factors,
                'latitude': lat,
                'longitude': lon
            }
        }

# ============================================================================
# LOCATION & ACCESSIBILITY CRITERIA
# ============================================================================

class AirportDistanceCriterion(Criterion):
    """Evaluates distance to nearest major airport"""

    def __init__(self):
        super().__init__()
        self.name = "airport_distance"
        self.display_name = "✈️ Airport Access"
        self.weight = 1.5
        self.description = "Distance to nearest major airport"

    def evaluate(self, property_data):
        lat = property_data.get('latitude')
        lon = property_data.get('longitude')

        if not lat or not lon:
            return {
                'score': 3,
                'reasoning': ["No coordinates"],
                'raw_data': {}
            }

        # Major European airports (add more as needed)
        major_airports = {
            'Amsterdam Schiphol': (52.3105, 4.7683),
            'Paris CDG': (49.0097, 2.5479),
            'Frankfurt': (50.0379, 8.5622),
            'Madrid': (40.4983, -3.5676),
            'Barcelona': (41.2974, 2.0833),
            'Brussels': (50.9010, 4.4856),
            'London Heathrow': (51.4700, -0.4543),
            'Munich': (48.3537, 11.7750),
            'Rome': (41.8003, 12.2389),
            'Lisbon': (38.7742, -9.1342)
        }

        # Calculate distance to nearest airport
        def haversine_distance(lat1, lon1, lat2, lon2):
            R = 6371  # Earth radius in km
            dlat = math.radians(lat2 - lat1)
            dlon = math.radians(lon2 - lon1)
            a = math.sin(dlat/2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon/2)**2
            c = 2 * math.asin(math.sqrt(a))
            return R * c

        nearest_airport = None
        min_distance = float('inf')

        for airport_name, (airport_lat, airport_lon) in major_airports.items():
            distance = haversine_distance(lat, lon, airport_lat, airport_lon)
            if distance < min_distance:
                min_distance = distance
                nearest_airport = airport_name

        # Score based on distance
        reasoning = []
        if min_distance < 50:
            score = 5
            reasoning.append(f"Excellent: {min_distance:.0f}km to {nearest_airport}")
        elif min_distance < 100:
            score = 4
            reasoning.append(f"Good: {min_distance:.0f}km to {nearest_airport}")
        elif min_distance < 200:
            score = 3
            reasoning.append(f"Moderate: {min_distance:.0f}km to {nearest_airport}")
        elif min_distance < 400:
            score = 2
            reasoning.append(f"Far: {min_distance:.0f}km to {nearest_airport}")
        else:
            score = 1
            reasoning.append(f"Very far: {min_distance:.0f}km to {nearest_airport}")

        return {
            'score': score,
            'reasoning': reasoning,
            'raw_data': {
                'nearest_airport': nearest_airport,
                'distance_km': min_distance
            }
        }

class PopulationDensityCriterion(Criterion):
    """Evaluates population density (prefer lower for rural properties)"""

    def __init__(self):
        super().__init__()
        self.name = "population_density"
        self.display_name = "🏘️ Rural Character"
        self.weight = 1.0
        self.description = "Population density (lower = more rural)"

    def evaluate(self, property_data):
        # This would use a population density API
        # For now, use country as proxy
        country = property_data.get('country', '')

        # Rough estimates (people per km²)
        density_estimates = {
            'Netherlands': 508,  # Very dense
            'Belgium': 383,
            'Germany': 240,
            'France': 119,
            'Spain': 94,
            'Portugal': 111,
            'Italy': 206
        }

        density = density_estimates.get(country, 100)

        # Score (inverse - lower density is better for rural)
        if density < 50:
            score = 5
            reasoning = [f"Very rural area (~{density} people/km²)"]
        elif density < 100:
            score = 4
            reasoning = [f"Rural area (~{density} people/km²)"]
        elif density < 200:
            score = 3
            reasoning = [f"Semi-rural area (~{density} people/km²)"]
        elif density < 400:
            score = 2
            reasoning = [f"Populated area (~{density} people/km²)"]
        else:
            score = 1
            reasoning = [f"Densely populated (~{density} people/km²)"]

        return {
            'score': score,
            'reasoning': reasoning,
            'raw_data': {
                'population_density': density,
                'country': country
            }
        }

# ============================================================================
# ENVIRONMENTAL CRITERIA
# ============================================================================

class SoilQualityCriterion(Criterion):
    """Evaluates soil quality based on location data"""

    def __init__(self):
        super().__init__()
        self.name = "soil_quality"
        self.display_name = "🌱 Soil Quality"
        self.weight = 2.0
        self.description = "Soil fertility and agricultural potential"

    def evaluate(self, property_data):
        # This could use SoilGrids API or similar
        # For now, use regional knowledge

        country = property_data.get('country', '')
        lat = property_data.get('latitude', 0)
        lon = property_data.get('longitude', 0)

        reasoning = []
        score = 3  # Default

        # Regional soil knowledge
        # Netherlands/Belgium - excellent soils
        if country in ['Netherlands', 'België', 'Belgium']:
            score = 5
            reasoning.append("Excellent Dutch/Belgian soils - highly fertile")

        # Northern France - good soils
        elif country in ['France', 'Frankrijk'] and lat > 46:
            score = 4
            reasoning.append("Good Northern French soils")

        # Mediterranean - variable
        elif country in ['Spain', 'Portugal', 'Italy']:
            if lat < 40:  # Southern Spain/Portugal
                score = 2
                reasoning.append("Dry Mediterranean soils - may need amendment")
            else:
                score = 3
                reasoning.append("Mediterranean soils - moderate quality")

        else:
            reasoning.append("Soil quality unknown - site assessment needed")

        return {
            'score': score,
            'reasoning': reasoning,
            'raw_data': {
                'country': country,
                'latitude': lat
            }
        }

class WaterAvailabilityCriterion(Criterion):
    """Evaluates water source availability"""

    def __init__(self):
        super().__init__()
        self.name = "water_availability"
        self.display_name = "💧 Water Access"
        self.weight = 2.5
        self.description = "Natural water sources and rainfall"

    def evaluate(self, property_data):
        # Combines rainfall with regional water stress data

        # Get rainfall if available from another criterion
        rainfall = property_data.get('annual_rainfall_mm')
        country = property_data.get('country', '')
        lat = property_data.get('latitude', 0)

        reasoning = []
        score = 3

        # If no rainfall data available, return neutral score
        if rainfall is None:
            return {
                'score': 3,
                'reasoning': ["Rainfall data unavailable"],
                'raw_data': {}
            }

        # Convert to float if it's a string (when loaded from CSV)
        try:
            rainfall = float(rainfall)
        except (ValueError, TypeError):
            return {
                'score': 3,
                'reasoning': ["Invalid rainfall data"],
                'raw_data': {}
            }

        # Water-stressed regions
        water_stress_regions = {
            'Spain': (lat < 41),  # Southern Spain
            'Portugal': True,
            'Greece': True,
            'Italy': (lat < 43)  # Southern Italy
        }

        is_water_stressed = water_stress_regions.get(country, False)
        if callable(is_water_stressed):
            is_water_stressed = is_water_stressed

        if rainfall > 800:
            score = 5
            reasoning.append(f"Abundant rainfall: {rainfall:.0f}mm/year")
        elif rainfall > 600:
            score = 4
            reasoning.append(f"Good rainfall: {rainfall:.0f}mm/year")
        elif rainfall > 400:
            score = 3
            reasoning.append(f"Moderate rainfall: {rainfall:.0f}mm/year")
        elif is_water_stressed:
            score = 2
            reasoning.append("Water-stressed region - irrigation needed")
        else:
            score = 2
            reasoning.append("Low rainfall - water source critical")

        return {
            'score': score,
            'reasoning': reasoning,
            'raw_data': {
                'water_stressed_region': is_water_stressed,
                'annual_rainfall_mm': rainfall
            }
        }

class AirbnbRentabilityCriterion(Criterion):
    """Evaluates Airbnb/short-term rental potential"""

    def __init__(self):
        super().__init__()
        self.name = "airbnb_rentability"
        self.display_name = "🏠 Airbnb Potential"
        self.weight = 2.0
        self.description = "Short-term rental income potential"

        # Define high-demand tourist regions
        self.tourist_hotspots = {
            'France': {
                'regions': ['Provence', 'Côte d\'Azur', 'Dordogne', 'Brittany', 'Loire Valley', 'Alps'],
                'coastal': True,
                'wine_regions': ['Bordeaux', 'Burgundy', 'Champagne', 'Loire']
            },
            'Spain': {
                'regions': ['Costa Brava', 'Costa del Sol', 'Balearic', 'Canary', 'Andalusia', 'Catalonia'],
                'coastal': True,
                'islands': True
            },
            'Portugal': {
                'regions': ['Algarve', 'Lisbon', 'Porto', 'Douro Valley'],
                'coastal': True
            },
            'Italy': {
                'regions': ['Tuscany', 'Umbria', 'Sicily', 'Sardinia', 'Amalfi Coast', 'Lake Como'],
                'coastal': True,
                'wine_regions': ['Tuscany', 'Piedmont', 'Veneto']
            },
            'Netherlands': {
                'regions': ['Amsterdam area', 'Zeeland', 'Friesland'],
                'coastal': True
            },
            'Belgium': {
                'regions': ['Ardennes', 'Bruges', 'Coast'],
                'coastal': True
            }
        }

    def evaluate(self, property_data):
        lat = property_data.get('latitude')
        lon = property_data.get('longitude')
        country = property_data.get('country', '')

        # Get location context if available
        location_text = property_data.get('Locatie', '').lower()

        reasoning = []
        score = 3  # Default moderate potential

        # Base score on country tourist appeal
        if country not in self.tourist_hotspots:
            score = 2
            reasoning.append(f"{country}: Moderate tourist market")
        else:
            score = 3

        # Check proximity to coast (high Airbnb demand)
        if lat and lon:
            # Simplified coastal proximity check
            # High-value coastal zones
            coastal_zones = {
                'Mediterranean': (lat > 37 and lat < 44 and lon > -5 and lon < 20),
                'Atlantic_France': (lat > 43 and lat < 48 and lon < -1),
                'Atlantic_Portugal': (lat > 37 and lat < 42 and lon < -8),
                'North_Sea': (lat > 51 and lat < 54 and (lon > 2 and lon < 7))
            }

            for zone_name, in_zone in coastal_zones.items():
                if in_zone:
                    score = max(score, 4)
                    reasoning.append(f"Coastal location ({zone_name.replace('_', ' ')})")
                    break

        # Check tourist keywords in location
        tourist_keywords = [
            'provence', 'dordogne', 'algarve', 'tuscany', 'toscana',
            'umbria', 'costa', 'playa', 'beach', 'mer', 'zee',
            'alps', 'mountain', 'lake', 'lac', 'lago', 'ski',
            'wine', 'vin', 'vineyard', 'vignoble'
        ]

        for keyword in tourist_keywords:
            if keyword in location_text:
                score = min(5, score + 1)
                reasoning.append(f"Tourist keyword: {keyword}")
                break

        # Check climate - warm climate = better rental potential
        avg_temp = property_data.get('avg_temperature')
        if avg_temp:
            try:
                avg_temp = float(avg_temp)
                if avg_temp > 18:
                    score = min(5, score + 1)
                    reasoning.append(f"Warm climate ({avg_temp:.1f}°C avg) - year-round appeal")
                elif avg_temp > 15:
                    reasoning.append(f"Mild climate ({avg_temp:.1f}°C avg)")
                else:
                    reasoning.append(f"Cool climate ({avg_temp:.1f}°C avg) - seasonal rentals")
            except (ValueError, TypeError):
                pass

        # Check airport distance - closer = easier for tourists
        airport_distance = property_data.get('distance_to_nearest_airport_km')
        if airport_distance:
            try:
                airport_distance = float(airport_distance)
            except (ValueError, TypeError):
                airport_distance = None

        if airport_distance:
            if airport_distance < 50:
                score = min(5, score + 1)
                reasoning.append(f"Excellent airport access: {airport_distance:.0f}km")
            elif airport_distance < 100:
                reasoning.append(f"Good airport access: {airport_distance:.0f}km")
            elif airport_distance > 200:
                score = max(1, score - 1)
                reasoning.append(f"Remote location: {airport_distance:.0f}km from airport")

        # Building size matters for rental capacity
        building_size = property_data.get('building_size_m2')
        if building_size and building_size > 200:
            score = min(5, score + 1)
            reasoning.append(f"Large building ({building_size}m²) - multi-unit potential")
        elif building_size and building_size > 100:
            reasoning.append(f"Medium building ({building_size}m²) - good rental size")
        elif building_size and building_size > 0:
            reasoning.append(f"Small building ({building_size}m²)")

        # Land size for outdoor appeal
        land_size = property_data.get('land_size_m2') or 0
        if land_size > 10000:
            reasoning.append(f"Large land ({land_size/10000:.1f} ha) - outdoor activities")

        # Rural character - very rural can be a plus for nature tourism
        rural_score = property_data.get('rural_character_score', 3)
        if rural_score >= 4:
            reasoning.append("Rural setting - nature tourism appeal")

        # Final reasoning summary
        if score >= 4:
            reasoning.insert(0, "🌟 Strong Airbnb potential")
        elif score >= 3:
            reasoning.insert(0, "✓ Moderate Airbnb potential")
        else:
            reasoning.insert(0, "⚠️ Limited Airbnb potential")

        return {
            'score': score,
            'reasoning': reasoning,
            'raw_data': {
                'airbnb_score': score,
                'country_tourist_market': country in self.tourist_hotspots
            }
        }

# ============================================================================
# CRITERIA MANAGER
# ============================================================================

class CriteriaManager:
    """Manages all criteria and evaluates properties"""

    def __init__(self):
        self.criteria = []
        self.load_default_criteria()

    def load_default_criteria(self):
        """Load all available criteria"""
        self.criteria = [
            RainfallCriterion(),
            TemperatureCriterion(),
            ClimateChangeCriterion(),
            AirportDistanceCriterion(),
            PopulationDensityCriterion(),
            SoilQualityCriterion(),
            WaterAvailabilityCriterion(),
            AirbnbRentabilityCriterion()
        ]

    def add_criterion(self, criterion):
        """Add a custom criterion"""
        self.criteria.append(criterion)

    def remove_criterion(self, name):
        """Remove a criterion by name"""
        self.criteria = [c for c in self.criteria if c.name != name]

    def evaluate_property(self, property_data):
        """Evaluate a property against all criteria"""
        results = {}

        for criterion in self.criteria:
            print(f"  → Evaluating {criterion.display_name}...")
            result = criterion.evaluate(property_data)

            # Safety check - ensure result is a dict
            if result is None:
                result = {
                    'score': 3,
                    'reasoning': [f"{criterion.name} returned None"],
                    'raw_data': {}
                }

            results[criterion.name] = result

            # Add raw_data to property_data so subsequent criteria can use it
            if 'raw_data' in result and result['raw_data']:
                property_data.update(result['raw_data'])

        return results

    def calculate_weighted_score(self, results):
        """Calculate overall weighted score"""
        total_weighted = 0
        total_weight = 0

        for criterion in self.criteria:
            if criterion.name in results:
                score = results[criterion.name]['score']
                weight = criterion.weight
                total_weighted += score * weight
                total_weight += weight

        if total_weight > 0:
            return total_weighted / total_weight
        return 3.0

    def get_criteria_list(self):
        """Get list of all active criteria"""
        return [(c.name, c.display_name, c.weight) for c in self.criteria]

# ============================================================================
# MAIN EVALUATION FUNCTION
# ============================================================================

def evaluate_all_properties(csv_file="analysis_output.csv"):
    """Evaluate all properties with custom criteria"""

    print("=" * 70)
    print("🎯 CUSTOM CRITERIA EVALUATION")
    print("=" * 70)
    print()

    # Initialize manager
    manager = CriteriaManager()

    print("📋 Active Criteria:")
    for name, display, weight in manager.get_criteria_list():
        print(f"  • {display} (weight: {weight})")
    print()

    # Load data
    df = pd.read_csv(csv_file)

    # Add columns for each criterion
    for criterion in manager.criteria:
        col_name = f"custom_{criterion.name}"
        if col_name not in df.columns:
            df[col_name] = None

        # Also add raw data column
        raw_col = f"{col_name}_data"
        if raw_col not in df.columns:
            df[raw_col] = None

    if 'custom_overall_score' not in df.columns:
        df['custom_overall_score'] = None

    # Evaluate each property
    for idx, row in df.iterrows():
        print(f"\n🔍 [{idx+1}/{len(df)}] {row.get('URL', 'Unknown')}")

        # Prepare property data
        property_data = {
            'latitude': row.get('Latitude'),
            'longitude': row.get('Longitude'),
            'country': row.get('Country'),
            'land_size_m2': row.get('land_size_m2'),
            'building_size_m2': row.get('building_size_m2'),
            'annual_rainfall_mm': row.get('custom_rainfall_data')  # Use if available
        }

        if not property_data['latitude']:
            print("  ⚠️ No coordinates - skipping custom criteria")
            continue

        # Evaluate
        results = manager.evaluate_property(property_data)

        # Store results
        for criterion_name, result in results.items():
            col_name = f"custom_{criterion_name}"
            df.at[idx, col_name] = result['score']

            # Store raw data as JSON
            raw_col = f"{col_name}_data"
            df.at[idx, raw_col] = json.dumps(result['raw_data'])

        # Calculate overall score
        overall = manager.calculate_weighted_score(results)
        df.at[idx, 'custom_overall_score'] = overall

        print(f"  ✓ Overall custom score: {overall:.2f}")

        # Save progress every 10
        if (idx + 1) % 10 == 0:
            df.to_csv(csv_file, index=False, encoding='utf-8')
            print(f"  💾 Progress saved")

        # Rate limiting
        import time
        time.sleep(1)

    # Final save
    df.to_csv(csv_file, index=False, encoding='utf-8')

    print("\n" + "=" * 70)
    print("✅ EVALUATION COMPLETE")
    print("=" * 70)
    print(f"Custom criteria scores saved to {csv_file}")

if __name__ == "__main__":
    evaluate_all_properties()
