import numpy as np
# import pandas as pd
from pandas import Timestamp
from numpy import nan
import datetime

# import backend.app.assumptions as assumptions
# import recommendations.optimiser.optimiser_functions as optimiser_functions
#
# from backend.Funding import Funding
#
# project_scores_matrix = pd.read_csv("/Users/khalimconn-kowlessar/Downloads/ECO4 Full Project Scores Matrix.csv")
# partial_project_scores_matrix = pd.read_csv("backend/tests/test_data/ECO4_Partial_Project_Scores_Matrix_v6.csv")
# partial_project_scores_matrix.columns = ['Measure category', 'Measure_Type', 'Pre_Main_Heating_Source',
#                                          'Post_Main_Heating_Source', 'Total Floor Area Band', 'Starting Band',
#                                          'Average Treatable Factor', 'Cost Savings', 'SAP Savings']
# whlg_eligible_postcodes = pd.DataFrame([{"Postcode": "ab12cd"}])
#
# funding = Funding(
#     project_scores_matrix=project_scores_matrix,
#     partial_project_scores_matrix=partial_project_scores_matrix,
#     whlg_eligible_postcodes=whlg_eligible_postcodes,
#     eco4_social_cavity_abs_rate=13.5,
#     eco4_social_solid_abs_rate=17,
#     eco4_private_cavity_abs_rate=13.5,
#     eco4_private_solid_abs_rate=17,
#     gbis_social_cavity_abs_rate=21,
#     gbis_social_solid_abs_rate=25,
#     gbis_private_cavity_abs_rate=22,
#     gbis_private_solid_abs_rate=28,
#     tenure="Social"
# )
#
# # Assume these costs have been adjusted


#
# # Insert the funding uplifts
# for recs in property_recommendations:
#     for r in recs:
#         # Insert randomly
#         # Select one of 0, 0.25 or 0.45
#         r["uplift"] = np.random.choice([0, 0.25, 0.45])
#
# # We calculate the innovation uplift against each measure
# for recs in property_recommendations:
#     for r in recs:
#         if r["type"] in ["mechanical_ventilation", "low_energy_lighting", "secondary_heating"]:
#             r["innovation_uplift"] = 0
#             continue
#         r["innovation_uplift"] = funding.get_innovation_uplift(
#             measure=r,
#             starting_sap=p.data["current-energy-efficiency"],
#             floor_area=p.floor_area,
#             is_cavity=False,
#             current_wall_uvalue=1.7,
#             is_partial=False,
#             existing_li_thickness=150,
#             mainheating=p.main_heating,
#             main_fuel=p.main_fuel,
#             mainheat_energy_eff=p.data["mainheat-energy-eff"],
#         )
#         print(r["innovation_uplift"])
#
# property_measure_types = {rec["type"] for recs in property_recommendations for rec in recs}
# property_required_measures = [m for m in property_recommendations if m[0]["type"] in []]
# measures_to_optimise = [m for m in property_recommendations if m[0]["type"] not in []]
#
# # If a measure requiring ventilation is selected, and the property does not have ventilation, we enfore
# # its inclusion
# needs_ventilation = any(
#     x in property_measure_types for x in assumptions.measures_needing_ventilation
# ) and not p.has_ventilation
#
# input_measures = optimiser_functions.prepare_input_measures(
#     measures_to_optimise, "Increasing EPC", needs_ventilation, True
# )
#
# # ---- main wrapper around your optimiser ----------------------------------
#
# # Run inputs:
# target_gain = 18.5
#
# # Run the optimiser with these inouts


# tests/test_social_fabric_only.py
import numpy as np
import pandas as pd
import pytest
from copy import deepcopy

from recommendations.optimiser import optimiser_functions
from recommendations.optimiser.funding_optimiser import optimise_with_funding_paths  # wherever you defined it
from backend.Funding import Funding
from backend.app.plan.schemas import WALL_INSULATION_MEASURES, ROOF_INSULATION_MEASURES, ECO4_ELIGIBILE_FABRIC_MEASURES

ALLOWED_FABRIC_TYPES = set(WALL_INSULATION_MEASURES + ROOF_INSULATION_MEASURES + ECO4_ELIGIBILE_FABRIC_MEASURES)


@pytest.fixture
def mock_project_scores_matrix():
    data = []
    floor_segments = ["0-72", "73-97", "98-199", "200"]
    bands = [
        "Low_G", "High_G", "Low_F", "High_F", "Low_E", "High_E", "Low_D", "High_D", "Low_C", "High_C", "Low_B",
        "High_B", "Low_A", "High_A"
    ]

    cost = 50.0
    for floor in floor_segments:
        for start in bands:
            for finish in bands:
                if start != finish:  # skip identical start/finish (no SAP movement)
                    data.append({
                        "Floor Area Segment": floor,
                        "Starting Band": start,
                        "Finishing Band": finish,
                        "Cost Savings": cost
                    })
                    cost += 5.0  # increment to create variety

    return pd.DataFrame(data)


@pytest.fixture
def mock_partial_scores_matrix():
    df = pd.read_csv("backend/tests/test_data/ECO4_Partial_Project_Scores_Matrix_v6.csv")
    df.columns = ['Measure category', 'Measure_Type', 'Pre_Main_Heating_Source',
                  'Post_Main_Heating_Source', 'Total Floor Area Band', 'Starting Band',
                  'Average Treatable Factor', 'Cost Savings', 'SAP Savings']
    return df


class DummyProp:
    """Minimal property stub exposing just what your code reads."""

    def __init__(self):
        self.data = {
            "current-energy-rating": "E",  # or "D" for the special Social+D path
            "current-energy-efficiency": 55,  # numeric SAP points used in eligibility calc
            "mainheat-energy-eff": "Very Good",
        }
        self.has_ventilation = False
        self.floor_area = 70.0
        self.main_heating_controls = {"clean_description": "time and temperature zone control"}

        self.main_heating = {
            'original_description': 'Boiler and radiators, mains gas',
            'clean_description': 'Boiler and radiators, mains gas',
            'has_radiators': True, 'has_fan_coil_units': False, 'has_pipes_in_screed_above_insulation': False,
            'has_pipes_in_insulated_timber_floor': False, 'has_pipes_in_concrete_slab': False, 'has_boiler': True,
            'has_air_source_heat_pump': False, 'has_room_heaters': False, 'has_electric_storage_heaters': False,
            'has_warm_air': False, 'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
            'has_community_scheme': False, 'has_ground_source_heat_pump': False, 'has_no_system_present': False,
            'has_portable_electric_heaters': False, 'has_water_source_heat_pump': False, 'has_electric_heat_pump':
                False,
            'has_micro-cogeneration': False, 'has_solar_assisted_heat_pump': False, 'has_exhaust_source_heat_pump':
                False,
            'has_community_heat_pump': False, 'has_hot-water-only': False, 'has_electric': False, 'has_mains_gas':
                True,
            'has_wood_logs': False, 'has_coal': False, 'has_oil': False, 'has_wood_pellets': False,
            'has_anthracite': False,
            'has_dual_fuel_mineral_and_wood': False, 'has_smokeless_fuel': False, 'has_lpg': False, 'has_b30k': False,
            'has_mineral_and_wood': False, 'has_dual_fuel_appliance': False, 'has_assumed': False,
            'has_electricaire': False,
            'has_assumed_for_most_rooms': False, 'has_underfloor_heating': False
        }

        self.main_fuel = {
            'original_description': 'mains gas (not community)', 'clean_description': 'Mains gas not community',
            'fuel_type': 'mains gas', 'tariff_type': None, 'is_community': False,
            'no_individual_heating_or_community_network': False, 'complex_fuel_type': None
        }


@pytest.fixture
def p():
    return DummyProp()


@pytest.fixture
def funding(monkeypatch, mock_partial_scores_matrix, mock_project_scores_matrix):
    """Simple Funding that returns zero uplift so costs stay as provided."""
    # Build the Funding with tiny in-memory frames (avoid test I/O)

    f = Funding(
        project_scores_matrix=mock_project_scores_matrix,
        partial_project_scores_matrix=mock_partial_scores_matrix,
        whlg_eligible_postcodes=pd.DataFrame([{"Postcode": "ab12cd"}]),
        eco4_social_cavity_abs_rate=13.5, eco4_social_solid_abs_rate=17,
        eco4_private_cavity_abs_rate=13.5, eco4_private_solid_abs_rate=17,
        gbis_social_cavity_abs_rate=21, gbis_social_solid_abs_rate=25,
        gbis_private_cavity_abs_rate=22, gbis_private_solid_abs_rate=28,
        tenure="Social"
    )

    # Keep innovation_uplift simple for the first test
    # monkeypatch.setattr(f, "get_innovation_uplift", lambda *args, **kwargs: 0.0)

    # If your solar precondition matters, you can force True/False here:
    # monkeypatch.setattr(
    #     __import__("backend").Funding, "check_solar_eligible_heating_system",
    #     staticmethod(lambda mainheat_description, heating_control_description: False)
    # )

    return f


@pytest.fixture
def property_recommendations():
    """Short sample; replace with your full block if you want."""
    recs = [
        [{'phase': 0, 'parts': [{'id': 2466, 'type': 'external_wall_insulation',
                                 'description': 'EWI Pro EPS external wall insulation system with '
                                                'Brick Slip finish',
                                 'depth': 150.0, 'depth_unit': 'mm', 'cost': None,
                                 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.02631579,
                                 'r_value_unit': 'square_meter_kelvin_per_watt',
                                 'thermal_conductivity': 0.038,
                                 'thermal_conductivity_unit': 'watt_per_meter_kelvin',
                                 'link': 'SCIS',
                                 'created_at': Timestamp('2025-03-16 15:26:22.379496'),
                                 'is_active': True, 'prime_material_cost': None,
                                 'material_cost': 0.0, 'labour_cost': 0.0,
                                 'labour_hours_per_unit': 0.0, 'plant_cost': 0.0,
                                 'total_cost': 298.35,
                                 'notes': 'This is the quoted value from SCIS',
                                 'is_installer_quote': True, 'quantity': 63.98796761892035,
                                 'quantity_unit': 'm2', 'total': 19090.810139104888,
                                 'labour_hours': 0.0, 'labour_days': 0.0}],
          'type': 'external_wall_insulation', 'measure_type': 'external_wall_insulation',
          'description': 'Install 150mm EWI Pro EPS external wall insulation system with Brick '
                         'Slip finish on external walls',
          'starting_u_value': 1.7, 'new_u_value': 0.32, 'already_installed': False,
          'sap_points': np.float64(9.6),
          'simulation_config': {'is_as_built_ending': False, 'walls_is_assumed_ending': False,
                                'walls_insulation_thickness_ending': 'average',
                                'external_insulation_ending': True,
                                'walls_energy_eff_ending': 'Good',
                                'walls_thermal_transmittance_ending': 0.23},
          'description_simulation': {'walls-description': 'Solid brick, with external insulation',
                                     'walls-energy-eff': 'Good'}, 'total': 19090.810139104888,
          'labour_hours': 0.0, 'labour_days': 0.0, 'survey': False,
          'recommendation_id': '0_phase=0', 'efficiency': 11229.568317120522,
          'co2_equivalent_savings': np.float64(0.5), 'heat_demand': np.float64(37.099999999999994),
          'kwh_savings': np.float64(1827.8999999999996),
          'energy_cost_savings': np.float64(136.1247882352941)}, {'phase': 0, 'parts': [
            {'id': 2373, 'type': 'internal_wall_insulation', 'description': 'SWIP EcoBatt & Plastered finish',
             'depth': 95.0,
             'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.03125,
             'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.032,
             'thermal_conductivity_unit': None,
             'link': 'SCIS', 'created_at': Timestamp('2025-03-16 15:26:22.379496'), 'is_active': True,
             'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0, 'labour_hours_per_unit': 2.1,
             'plant_cost': 0.0, 'total_cost': 89.0, 'notes': None, 'is_installer_quote': True,
             'quantity': 63.98796761892035,
             'quantity_unit': 'm2', 'total': 5694.929118083911, 'labour_hours': 134.37473199973275,
             'labour_days': 4.199210374991648}], 'type': 'internal_wall_insulation',
                                                                  'measure_type': 'internal_wall_insulation',
                                                                  'description': 'Install 95mm '
                                                                                 'SWIP EcoBatt & '
                                                                                 'Plastered '
                                                                                 'finish on '
                                                                                 'internal walls',
                                                                  'starting_u_value': 1.7,
                                                                  'new_u_value': 0.32,
                                                                  'already_installed': False,
                                                                  'sap_points': 6,
                                                                  'simulation_config': {
                                                                      'is_as_built_ending': False,
                                                                      'walls_is_assumed_ending':
                                                                          False,
                                                                      'walls_insulation_thickness_ending': 'average',
                                                                      'internal_insulation_ending': True,
                                                                      'walls_energy_eff_ending':
                                                                          'Good',
                                                                      'walls_thermal_transmittance_ending': 0.29},
                                                                  'description_simulation': {
                                                                      'walls-description': 'Solid '
                                                                                           'brick, with internal '
                                                                                           'insulation',
                                                                      'walls-energy-eff': 'Good'},
                                                                  'total': 5694.929118083911,
                                                                  'labour_hours': 134.37473199973275,
                                                                  'labour_days': 4.199210374991648,
                                                                  'survey': True,
                                                                  'recommendation_id': '1_phase=0',
                                                                  'efficiency': 3349.6383047552417,
                                                                  'co2_equivalent_savings': np.float64(
                                                                      0.5),
                                                                  'heat_demand': np.float64(
                                                                      35.30000000000001),
                                                                  'kwh_savings': np.float64(
                                                                      1432.3999999999996),
                                                                  'energy_cost_savings': np.float64(
                                                                      106.67167058823532)}], [
            {'phase': 1, 'parts': [{'id': 2351, 'type': 'loft_insulation',
                                    'description': 'Knauf Loft Roll 44 glass fibre roll',
                                    'depth': 300.0, 'depth_unit': 'mm', 'cost': None,
                                    'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273,
                                    'r_value_unit': 'square_meter_kelvin_per_watt',
                                    'thermal_conductivity': 0.044,
                                    'thermal_conductivity_unit': 'watt_per_meter_kelvin',
                                    'link': 'SCIS',
                                    'created_at': Timestamp('2025-03-16 15:26:22.379496'),
                                    'is_active': True, 'prime_material_cost': None,
                                    'material_cost': 0.0, 'labour_cost': 0.0,
                                    'labour_hours_per_unit': 0.11, 'plant_cost': 0.0,
                                    'total_cost': 15.0,
                                    'notes': 'This is the cost if there is less than 100mm '
                                             'existing insulation',
                                    'is_installer_quote': True, 'quantity': 63.98796761892035,
                                    'quantity_unit': 'm2', 'total': 645.0, 'labour_hours': 8,
                                    'labour_days': 1}], 'type': 'loft_insulation',
             'measure_type': 'loft_insulation',
             'description': 'Install 300mm of Knauf Loft Roll 44 glass fibre roll in your loft',
             'starting_u_value': 2.3, 'new_u_value': 2.3, 'sap_points': np.float64(2.4),
             'already_installed': False,
             'simulation_config': {'is_loft_ending': True, 'roof_is_assumed_ending': False,
                                   'roof_insulation_thickness_ending': '300',
                                   'roof_thermal_transmittance_ending': 2.3,
                                   'roof_energy_eff_ending': 'Very Good'},
             'description_simulation': {'roof-description': 'Pitched, 300mm loft insulation',
                                        'roof-energy-eff': 'Very Good'}, 'total': 645.0,
             'labour_hours': 8, 'labour_days': 1, 'survey': False, 'recommendation_id': '2_phase=1',
             'efficiency': 278.1347826086957,
             'co2_equivalent_savings': np.float64(0.10000000000000009),
             'heat_demand': np.float64(1.5), 'kwh_savings': np.float64(566.1499999999996),
             'energy_cost_savings': np.float64(42.16152352941185)}], [{'phase': 2, 'parts': [
            {'id': 2329, 'type': 'mechanical_ventilation', 'description': 'Mechanical Extract Ventilation',
             'depth': 0.0,
             'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': nan,
             'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
             'thermal_conductivity_unit': None,
             'link': 'SCIS', 'created_at': datetime.datetime(2025, 3, 16, 15, 26, 22, 379496), 'is_active': True,
             'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0, 'labour_hours_per_unit': 0.0,
             'plant_cost': 0.0, 'total_cost': 350.0, 'notes': None, 'is_installer_quote': True, 'total': 700.0,
             'quantity': 2,
             'quantity_unit': 'part'}], 'type': 'mechanical_ventilation', 'measure_type': 'mechanical_ventilation',
                                                                       'description': 'Install 2 '
                                                                                      'Mechanical '
                                                                                      'Extract '
                                                                                      'Ventilation units',
                                                                       'starting_u_value': None,
                                                                       'new_u_value': None,
                                                                       'already_installed': False,
                                                                       'sap_points': np.float64(
                                                                           -0.10000000000000142),
                                                                       'heat_demand': np.float64(
                                                                           -3.3999999999999773),
                                                                       'kwh_savings': np.float64(
                                                                           -53.80000000000018),
                                                                       'co2_equivalent_savings': np.float64(
                                                                           0.0),
                                                                       'energy_cost_savings': np.float64(
                                                                           -4.0065176470588995),
                                                                       'total': 700.0,
                                                                       'labour_hours': 8,
                                                                       'labour_days': 1.0,
                                                                       'simulation_config': {
                                                                           'mechanical_ventilation_ending':
                                                                               'mechanical, '
                                                                               'extract '
                                                                               'only'},
                                                                       'description_simulation': {
                                                                           'mechanical-ventilation': 'mechanical, '
                                                                                                     'extract only'},
                                                                       'recommendation_id': '3_phase=2',
                                                                       'efficiency': 0}], [
            {'phase': 3, 'parts': [{'id': 2409, 'type': 'suspended_floor_insulation',
                                    'description': 'Q-bot underfloor insulation', 'depth': 75.0,
                                    'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2',
                                    'r_value_per_mm': 0.045454547,
                                    'r_value_unit': 'square_meter_kelvin_per_watt',
                                    'thermal_conductivity': 0.022,
                                    'thermal_conductivity_unit': 'watt_per_meter_kelvin',
                                    'link': 'SCIS',
                                    'created_at': Timestamp('2025-03-16 15:26:22.379496'),
                                    'is_active': True, 'prime_material_cost': None,
                                    'material_cost': 0.0, 'labour_cost': 0.0,
                                    'labour_hours_per_unit': 1.63, 'plant_cost': 0.0,
                                    'total_cost': 93.75,
                                    'notes': 'Linearly interpolated based on Qbot costs',
                                    'is_installer_quote': True, 'quantity': 43.0,
                                    'quantity_unit': 'm2', 'total': 4031.25,
                                    'labour_hours': 70.08999999999999,
                                    'labour_days': 2.920416666666666}],
             'type': 'suspended_floor_insulation', 'measure_type': 'suspended_floor_insulation',
             'description': 'Install 75mm Q-bot underfloor insulation insulation in suspended '
                            'floor',
             'starting_u_value': 0.83, 'new_u_value': 0.22, 'sap_points': 2, 'survey': True,
             'already_installed': False, 'simulation_config': {'floor_is_assumed_ending': False,
                                                               'floor_insulation_thickness_ending': 'average',
                                                               'floor_thermal_transmittance_ending': 0.685593},
             'description_simulation': {'floor-description': 'Suspended, insulated'},
             'total': 4031.25, 'labour_hours': 70.08999999999999, 'labour_days': 2.920416666666666,
             'recommendation_id': '4_phase=3', 'efficiency': 4856.707710843373,
             'co2_equivalent_savings': np.float64(0.20000000000000018),
             'heat_demand': np.float64(33.5), 'kwh_savings': np.float64(1021.1999999999998),
             'energy_cost_savings': np.float64(76.04936470588231)}], [
            {'phase': 4, 'parts': [], 'type': 'low_energy_lighting',
             'measure_type': 'low_energy_lighting',
             'description': 'Install low energy lighting in -886 outlets', 'starting_u_value': None,
             'new_u_value': None, 'already_installed': False, 'sap_points': 2,
             'kwh_savings': -48508.5, 'energy_cost_savings': -12481.237049999998,
             'co2_equivalent_savings': -7.858377,
             'description_simulation': {'lighting-energy-eff': 'Very Good',
                                        'lighting-description': 'Low energy lighting in all fixed'
                                                                ' outlets',
                                        'low-energy-lighting': 100}, 'total': -3411.1000000000004,
             'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
             'recommendation_id': '5_phase=4', 'efficiency': -1705.5500000000002,
             'heat_demand': np.float64(5.099999999999994)}], [
            {'type': 'heating', 'phase': 5, 'measure_type': 'time_temperature_zone_control',
             'parts': [],
             'description': 'Upgrade heating controls to Smart Thermostats, room sensors and '
                            'smart radiator valves (time & temperature zone control)',
             'total': 739.576, 'subtotal': 700.48, 'vat': 39.096000000000004,
             'labour_hours': 3.6199999999999997, 'labour_days': np.float64(1.0),
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(2.9),
             'already_installed': False, 'simulation_config': {
                'thermostatic_control_ending': 'time and temperature zone control',
                'switch_system_ending': None, 'trvs_ending': None,
                'mainheatc_energy_eff_ending': 'Very Good'}, 'description_simulation': {
                'mainheatcont-description': 'Time and temperature zone control',
                'mainheatc-energy-eff': 'Very Good'}, 'recommendation_id': '6_phase=5',
             'efficiency': 739.576, 'co2_equivalent_savings': np.float64(0.30000000000000027),
             'heat_demand': np.float64(6.599999999999994),
             'kwh_savings': np.float64(876.8000000000002),
             'energy_cost_savings': np.float64(65.29581176470589)}], [
            {'phase': 6, 'parts': [], 'type': 'secondary_heating',
             'measure_type': 'secondary_heating',
             'description': 'Remove the secondary heating system', 'starting_u_value': None,
             'new_u_value': None, 'sap_points': np.float64(3.6), 'already_installed': False,
             'total': 30.0, 'subtotal': 25.0, 'vat': 5.0, 'labour_hours': 3.0,
             'labour_days': np.float64(1.0),
             'simulation_config': {'secondheat_description_ending': 'None'},
             'description_simulation': {'secondheat-description': 'None'},
             'recommendation_id': '7_phase=6', 'efficiency': 30.0,
             'co2_equivalent_savings': np.float64(0.10000000000000009),
             'heat_demand': np.float64(15.400000000000006),
             'kwh_savings': np.float64(196.29999999999927),
             'energy_cost_savings': np.float64(14.61857647058821)}], [
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 4.0 kilowatt-peak (kWp) solar panel system.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(13.0),
             'already_installed': False, 'total': 6013.139999999999, 'subtotal': 5010.95, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(65.0),
             'has_battery': False, 'initial_ac_kwh_per_year': np.float64(4081.7132614999996),
             'description_simulation': {'photo-supply': np.float64(65.0)},
             'recommendation_id': '8_phase=7', 'efficiency': np.float64(462.54923076923075),
             'co2_equivalent_savings': np.float64(0.47347873833399995),
             'heat_demand': np.float64(88.69999999999999),
             'kwh_savings': np.float64(2040.8566307499998),
             'energy_cost_savings': np.float64(525.1124110919749)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 4.0 kilowatt-peak (kWp) solar panel system, with a battery.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(13.0),
             'already_installed': False, 'total': 10537.008, 'subtotal': 8780.84, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(65.0),
             'has_battery': True, 'initial_ac_kwh_per_year': np.float64(4081.7132614999996),
             'description_simulation': {'photo-supply': np.float64(65.0)},
             'recommendation_id': '9_phase=7', 'efficiency': np.float64(810.5390769230769),
             'co2_equivalent_savings': np.float64(0.6628702336675999),
             'heat_demand': np.float64(88.69999999999999),
             'kwh_savings': np.float64(2857.1992830499994),
             'energy_cost_savings': np.float64(735.1573755287648)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 3.6 kilowatt-peak (kWp) solar panel system.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(12.0),
             'already_installed': False, 'total': 5826.491999999999, 'subtotal': 4855.41, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(60.0),
             'has_battery': False, 'initial_ac_kwh_per_year': np.float64(3692.66794),
             'description_simulation': {'photo-supply': np.float64(60.0)},
             'recommendation_id': '10_phase=7', 'efficiency': np.float64(485.54099999999994),
             'co2_equivalent_savings': np.float64(0.42834948104),
             'heat_demand': np.float64(83.69999999999999), 'kwh_savings': np.float64(1846.33397),
             'energy_cost_savings': np.float64(475.0617304809999)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 3.6 kilowatt-peak (kWp) solar panel system, with a battery.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(12.0),
             'already_installed': False, 'total': 10350.359999999999, 'subtotal': 8625.3, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(60.0),
             'has_battery': True, 'initial_ac_kwh_per_year': np.float64(3692.66794),
             'description_simulation': {'photo-supply': np.float64(60.0)},
             'recommendation_id': '11_phase=7', 'efficiency': np.float64(862.5299999999999),
             'co2_equivalent_savings': np.float64(0.599689273456),
             'heat_demand': np.float64(83.69999999999999), 'kwh_savings': np.float64(2584.867558),
             'energy_cost_savings': np.float64(665.0864226734)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 3.2 kilowatt-peak (kWp) solar panel system.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(11.0),
             'already_installed': False, 'total': 5642.604, 'subtotal': 4702.17, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(55.0),
             'has_battery': False, 'initial_ac_kwh_per_year': np.float64(3300.5416548),
             'description_simulation': {'photo-supply': np.float64(55.0)},
             'recommendation_id': '12_phase=7', 'efficiency': np.float64(512.964),
             'co2_equivalent_savings': np.float64(0.3828628319568), 'heat_demand': np.float64(78.3),
             'kwh_savings': np.float64(1650.2708274),
             'energy_cost_savings': np.float64(424.61468389001993)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 3.2 kilowatt-peak (kWp) solar panel system, with a battery.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(11.0),
             'already_installed': False, 'total': 10166.472, 'subtotal': 8472.06, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(55.0),
             'has_battery': True, 'initial_ac_kwh_per_year': np.float64(3300.5416548),
             'description_simulation': {'photo-supply': np.float64(55.0)},
             'recommendation_id': '13_phase=7', 'efficiency': np.float64(924.2247272727273),
             'co2_equivalent_savings': np.float64(0.53600796473952),
             'heat_demand': np.float64(78.3), 'kwh_savings': np.float64(2310.3791583599996),
             'energy_cost_savings': np.float64(594.4605574460278)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 2.8 kilowatt-peak (kWp) solar panel system.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(9.0),
             'already_installed': False, 'total': 5458.727999999999, 'subtotal': 4548.94, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(45.0),
             'has_battery': False, 'initial_ac_kwh_per_year': np.float64(2907.1867812),
             'description_simulation': {'photo-supply': np.float64(45.0)},
             'recommendation_id': '14_phase=7', 'efficiency': np.float64(606.5253333333333),
             'co2_equivalent_savings': np.float64(0.3372336666192), 'heat_demand': np.float64(64.0),
             'kwh_savings': np.float64(1453.5933906),
             'energy_cost_savings': np.float64(374.00957940138)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 2.8 kilowatt-peak (kWp) solar panel system, with a battery.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(9.0),
             'already_installed': False, 'total': 9982.596, 'subtotal': 8318.83, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(45.0),
             'has_battery': True, 'initial_ac_kwh_per_year': np.float64(2907.1867812),
             'description_simulation': {'photo-supply': np.float64(45.0)},
             'recommendation_id': '15_phase=7', 'efficiency': np.float64(1109.1773333333333),
             'co2_equivalent_savings': np.float64(0.47212713326688),
             'heat_demand': np.float64(64.0), 'kwh_savings': np.float64(2035.03074684),
             'energy_cost_savings': np.float64(523.6134111619319)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 2.4 kilowatt-peak (kWp) solar panel system.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(8.0),
             'already_installed': False, 'total': 5274.852, 'subtotal': 4395.71, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(40.0),
             'has_battery': False, 'initial_ac_kwh_per_year': np.float64(2510.25188),
             'description_simulation': {'photo-supply': np.float64(40.0)},
             'recommendation_id': '16_phase=7', 'efficiency': np.float64(659.3565),
             'co2_equivalent_savings': np.float64(0.29118921808), 'heat_demand': np.float64(54.3),
             'kwh_savings': np.float64(1255.12594),
             'energy_cost_savings': np.float64(322.94390436199996)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 2.4 kilowatt-peak (kWp) solar panel system, with a battery.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(8.0),
             'already_installed': False, 'total': 9798.72, 'subtotal': 8165.6, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(40.0),
             'has_battery': True, 'initial_ac_kwh_per_year': np.float64(2510.25188),
             'description_simulation': {'photo-supply': np.float64(40.0)},
             'recommendation_id': '17_phase=7', 'efficiency': np.float64(1224.84),
             'co2_equivalent_savings': np.float64(0.40766490531199995),
             'heat_demand': np.float64(54.3), 'kwh_savings': np.float64(1757.1763159999998),
             'energy_cost_savings': np.float64(452.1214661067999)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 2.0 kilowatt-peak (kWp) solar panel system.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(7.0),
             'already_installed': False, 'total': 5090.976, 'subtotal': 4242.48, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(35.0),
             'has_battery': False, 'initial_ac_kwh_per_year': np.float64(2096.682636),
             'description_simulation': {'photo-supply': np.float64(35.0)},
             'recommendation_id': '18_phase=7', 'efficiency': np.float64(727.2822857142856),
             'co2_equivalent_savings': np.float64(0.243215185776), 'heat_demand': np.float64(48.5),
             'kwh_savings': np.float64(1048.341318),
             'energy_cost_savings': np.float64(269.7382211214)},
            {'phase': 7, 'parts': [], 'type': 'solar_pv', 'measure_type': 'solar_pv',
             'description': 'Install a 2.0 kilowatt-peak (kWp) solar panel system, with a battery.',
             'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(7.0),
             'already_installed': False, 'total': 9614.844, 'subtotal': 8012.369999999999, 'vat': 0,
             'labour_hours': 48, 'labour_days': 2, 'photo_supply': np.float64(35.0),
             'has_battery': True, 'initial_ac_kwh_per_year': np.float64(2096.682636),
             'description_simulation': {'photo-supply': np.float64(35.0)},
             'recommendation_id': '19_phase=7', 'efficiency': np.float64(1373.5491428571427),
             'co2_equivalent_savings': np.float64(0.3405012600864), 'heat_demand': np.float64(48.5),
             'kwh_savings': np.float64(1467.6778451999999),
             'energy_cost_savings': np.float64(377.6335095699599)}]
    ]
    return recs


def _attach_costs_and_uplifts(recs, funding, p):
    """Mimic what your script did: add cost fields & innovation uplift."""
    out = deepcopy(recs)
    for group in out:
        for r in group:
            if r["type"] in ["mechanical_ventilation", "low_energy_lighting", "secondary_heating"]:
                r["innovation_uplift"] = 0
                continue
            r["uplift"] = 0.0  # fixed for determinism in test
            r["innovation_uplift"] = funding.get_innovation_uplift(
                measure=r,
                starting_sap=55,
                floor_area=70.0,
                is_cavity=False,
                current_wall_uvalue=1.7,
                is_partial=False,
                existing_li_thickness=150,
                mainheating=p.main_heating,
                main_fuel=p.main_fuel,
                mainheat_energy_eff="Very Good",
            )
            # the optimiser_functions.prepare_input_measures will translate these to input format; but
            # for safety add explicit cost fields some downstream code expects:
            r["total"] = float(r["total"])
    return out


def _to_input_measures(recs, p):
    """Use your own helper so we test the full pipeline."""
    property_measure_types = {rec["type"] for grp in recs for rec in grp}
    needs_ventilation = any(
        x in property_measure_types for x in optimiser_functions.assumptions.measures_needing_ventilation
    ) and not getattr(p, "has_ventilation", False)

    # goal="Increasing EPC", add_uplift=True for Social path
    return optimiser_functions.prepare_input_measures(
        recs, goal="Increasing EPC", needs_ventilation=needs_ventilation, funding=True
    )


def _types_of(picked_items):
    return {item["type"] for item in picked_items}


def test_social_fabric_only_returns_only_fabric_types(p, funding, property_recommendations, monkeypatch):
    # 1) prepare data like your script
    recs = _attach_costs_and_uplifts(property_recommendations, funding, p)
    input_measures = _to_input_measures(recs, p)

    # 2) run optimiser wrapper (budget and target_gain can be modest for the test)
    budget = 30000.0
    target_gain = 8.0

    solutions = optimise_with_funding_paths(
        p=p,
        input_measures=input_measures,
        housing_type="Social",
        budget=budget,
        target_gain=target_gain,
        funding=funding
    )

    # 3) basic shape assertions
    assert isinstance(solutions, pd.DataFrame)
    assert not solutions.empty

    # 4) find the fabric-only ECO4 row
    fabric_rows = solutions[
        solutions["path"].apply(lambda x: isinstance(x, dict) and x.get("reference") == "fabric-only:eco4")]
    assert not fabric_rows.empty, "Expected a fabric-only:eco4 solution for Social tenure"

    # 5) ensure only fabric measure types are present in that solution
    picked_types = _types_of(fabric_rows.iloc[0]["items"])
    assert picked_types == {'internal_wall_insulation+mechanical_ventilation',
                            'suspended_floor_insulation'}, "incorrect types selected"

    # 6) respect budget
    assert fabric_rows.iloc[0]["total_cost"] <= budget + 1e-9

    # (optional) ensure unfunded baseline also appears
    unfunded_rows = solutions[
        solutions["path"].apply(lambda x: isinstance(x, dict) and x.get("reference") == "unfunded:all")]
    assert not unfunded_rows.empty
