from recommendations.Costs import Costs
from unittest.mock import Mock
import datetime
import pytest


class TestCosts:

    def test_cavity_wall_insulation(self):
        mock_property = Mock()
        mock_property.data = {
            "county": "Northamptonshire"
        }

        costs = Costs(mock_property)

        cwi_material = {
            "description": "cwi",
            "depth": 75,
            "thermal_conductivity": 0.037,
            "prime_cost": 5.17,
            "material_cost": 5.62,
            "labour_cost": 1.125,
            "labour_hours_per_unit": 0.065,
        }

        cwi_results = costs.cavity_wall_insulation(
            wall_area=95.9104281347967,
            material=cwi_material,
        )

        assert cwi_results == {
            'total': 1065.0661223512907, 'subtotal': 887.5551019594088, 'vat': 177.51102039188177,
            'contingency': 63.396792997100626, 'preliminaries': 63.396792997100626, 'material': 539.0166061175574,
            'profit': 126.79358599420125, 'labour_hours': 6.234177828761786, 'labour_cost': 94.95132385344874,
            'labour_days': 0.38963611429761164
        }

    def test_loft_insulation(self):
        mock_property = Mock()
        mock_property.data = {
            "county": "Northamptonshire"
        }

        costs = Costs(mock_property)
        loft_material = {
            "description": "Crown Loft Roll 44 glass fibre roll",
            "depth": 270,
            "thermal_conductivity": 0.044,
            "prime_cost": None,
            "material_cost": 5.91938,
            "labour_cost": 1.96,
            "labour_hours_per_unit": 0.11
        }

        loft_results = costs.loft_insulation(
            floor_area=33.5,
            material=loft_material,
        )

        assert loft_results == {
            'total': 639.4133610000001, 'subtotal': 532.8444675000001, 'vat': 106.56889350000002,
            'contingency': 71.045929, 'preliminaries': 35.5229645, 'material': 297.448845, 'profit': 71.045929,
            'labour_hours': 3.685, 'labour_cost': 57.7808, 'labour_days': 0.460625
        }

    def test_internal_wall_insulation(self):
        mock_property = Mock()
        mock_property.data = {
            "county": "Northamptonshire"
        }

        costs = Costs(mock_property)
        iwi_non_insulation_materials = [
            {'type': 'iwi_wall_demolition',
             'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping hammer; plaster to walls.',
             'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
             'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 10.27,
             'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55, 'link': 'SPONs', 'Notes': 0.0},
            {'type': 'iwi_wall_demolition',
             'description': 'Stud walls: Remove wall linings including battening behind; plasterboard and skim',
             'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
             'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 6.23,
             'labour_hours_per_unit': 0.2, 'plant_cost': 1.25, 'total_cost': 7.48, 'link': 'SPONs', 'Notes': 0.0},
            {'type': 'iwi_wall_demolition',
             'description': 'Lathe and Plaster walls: Remove wall linings including battening behind; wood lath and '
                            'plaster',
             'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
             'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 6.85,
             'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94, 'link': 'SPONs', 'Notes': 0.0},
            {'Notes': "",
             'cost_unit': "",
             'depth': "",
             'depth_unit': "",
             'description': 'Visqueen High Performance Vapour Barrier',
             'labour_cost': 0.48,
             'labour_hours_per_unit': 0.02,
             'link': 'SPONs',
             'material_cost': 1.21,
             'plant_cost': 0,
             'prime_material_cost': 0.58,
             'thermal_conductivity': "",
             'thermal_conductivity_unit': "",
             'total_cost': 1.69,
             'type': 'iwi_vapour_barrier'},
            {'Notes': "",
             'cost_unit': "",
             'depth': "",
             'depth_unit': "",
             'description': 'Plaster; one coat Thistle board finish or other equal; steel trowelled; 3 mm thick work '
                            'to walls or ceilings; one coat; to plasterboard base; over 600mm wide',
             'labour_cost': 6.58,
             'labour_hours_per_unit': 0.25,
             'link': "",
             'material_cost': 0.06,
             'plant_cost': 0,
             'prime_material_cost': 0.0,
             'thermal_conductivity': "",
             'thermal_conductivity_unit': "",
             'total_cost': 6.64,
             'type': 'iwi_redecoration'},
            {'Notes': "",
             'cost_unit': "",
             'depth': "",
             'depth_unit': "",
             'description': 'Two coats emulsion paint on plaster, over 40mm girth; 3.5m - '
                            '5m high',
             'labour_cost': 0.0,
             'labour_hours_per_unit': 0.21,
             'link': "",
             'material_cost': 0.41,
             'plant_cost': 0,
             'prime_material_cost': "",
             'thermal_conductivity': "",
             'thermal_conductivity_unit': "",
             'total_cost': 4.34,
             'type': 'iwi_redecoration'},
            {'Notes': "",
             'cost_unit': "",
             'depth': "",
             'depth_unit': "",
             'description': 'Fitting existing softwood skirting or architrave to new '
                            'frames; 150mm high',
             'labour_cost': 4.87,
             'labour_hours_per_unit': 0.01,
             'link': "",
             'material_cost': 4.86,
             'plant_cost': 0,
             'prime_material_cost': "",
             'thermal_conductivity': "",
             'thermal_conductivity_unit': "",
             'total_cost': 4.88,
             'type': 'iwi_redecoration'}
        ]

        iwi_material = {
            "type": "internal_wall_insulation",
            "description": "Ecotherm Eco-Versal PIR Insulation Board",
            "depth": 150,
            "depth_unit": "mm",
            "cost_unit": "gbp_per_m2",
            "thermal_conductivity": 0.022,
            "thermal_conductivity_unit": "watt_per_meter_kelvin",
            "prime_material_cost": "",
            "material_cost": 11.68,
            "labour_cost": 3.12,
            "labour_hours_per_unit": 0.18,
            "plant_cost": "",
            "total_cost": 14.8,
            "link": "SPONs"
        }

        iwi_results = costs.internal_wall_insulation(
            wall_area=95.9104281347967,
            material=iwi_material,
            non_insulation_materials=iwi_non_insulation_materials
        )

        assert iwi_results == {
            'total': 6880.2304726777775, 'subtotal': 5733.525393898148, 'vat': 1146.7050787796295,
            'contingency': 764.470052519753, 'preliminaries': 382.2350262598765, 'material': 1747.488000615996,
            'profit': 764.470052519753, 'labour_hours': 88.23759388401297, 'labour_days': 2.757424808875405,
            'labour_cost': 1927.1602026551818
        }

    def test_suspended_floor_insulation(self):
        mock_property = Mock()
        mock_property.data = {
            "county": "Northamptonshire"
        }

        costs = Costs(mock_property)

        sus_floor_material = {
            'type': 'suspended_floor_insulation', 'description': 'Thermafleece CosyWool Roll',
            'depth': 140.0,
            'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.039,
            'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 0,
            'material_cost': 11.68, 'labour_cost': 1.78, 'labour_hours_per_unit': 0.1,
            'plant_cost': 0,
            'total_cost': 13.46, 'link': 'SPONs',
            'Notes': 'Spons did not contain labour costs so we use values for similar insulations. '
                     'We use the '
                     'same values as in Crown loft roll 44, since it is also an insulation roll'
        }

        sus_floor_non_insulation_materials = [
            {'type': 'suspended_floor_demolition', 'description': 'Removal of carpet and underfelt', 'depth': 0,
             'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 3.32, 'labour_hours_per_unit': 0.11,
             'plant_cost': 0, 'total_cost': 3.32, 'link': 'SPONs',
             'Notes': 'We ignore the plant cost that is in SPONs because we assume the carpet is not scrapped and '
                      'therefore there is no need for a skip'},
            {'type': 'suspended_floor_demolition',
             'description': 'Remove boarding; withdraw nails; set aside for reuse; ground level', 'depth': 0,
             'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 9.34, 'labour_hours_per_unit': 0.3,
             'plant_cost': 0, 'total_cost': 9.34, 'link': 'SPONs', 'Notes': 0},
            {'type': 'suspended_floor_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier',
             'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
             'thermal_conductivity_unit': 0, 'prime_material_cost': 0.58, 'material_cost': 1.21, 'labour_cost': 0.48,
             'labour_hours_per_unit': 0.02, 'plant_cost': 0, 'total_cost': 1.69, 'link': 'SPONs', 'Notes': 0},
            {'type': 'suspended_floor_redecoration', 'description': 'refix floorboards previously set aside',
             'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
             'thermal_conductivity_unit': 0, 'prime_material_cost': 0, 'material_cost': 1.54, 'labour_cost': 24.98,
             'labour_hours_per_unit': 0.74, 'plant_cost': 0, 'total_cost': 26.52, 'link': 'SPONs', 'Notes': 0},
            {'type': 'suspended_floor_redecoration', 'description': 'Fitting carpet', 'depth': 0, 'depth_unit': 0,
             'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 6.59, 'labour_hours_per_unit': 0.37,
             'plant_cost': 0, 'total_cost': 6.59, 'link': 'SPONs',
             'Notes': 'SPONs does not have data on re-fitting the carpet so we use the data in Fitted carpeting; '
                      'Gradus woven polypropylene tufted loop\n\n as a baseline. We assume re-use of carpets, '
                      'therefore we need just labour rates'}]

        sus_floor_results = costs.suspended_floor_insulation(
            insulation_floor_area=33.5,
            material=sus_floor_material,
            non_insulation_materials=sus_floor_non_insulation_materials
        )

        assert sus_floor_results == {
            'total': 3337.07436, 'subtotal': 2780.8953, 'vat': 556.17906, 'contingency': 370.78604,
            'preliminaries': 185.39302, 'material': 483.405, 'profit': 370.78604, 'labour_hours': 54.940000000000005,
            'labour_days': 2.289166666666667, 'labour_cost': 1370.5252
        }

    def test_solid_floor_insulation(self):
        mock_property = Mock()
        mock_property.data = {
            "county": "Northamptonshire"
        }

        costs = Costs(mock_property)
        sol_floor_material = {
            'type': 'solid_floor_insulation', 'description': 'Kay-Cel Expanded Polystyrene Board',
            'depth': 100.0, 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.033,
            'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 0,
            'material_cost': 12.02, 'labour_cost': 4.4, 'labour_hours_per_unit': 0.19, 'plant_cost': 0,
            'total_cost': 16.42, 'link': 'SPONs', 'Notes': 0
        }

        sol_floor_non_insulation_materials = [
            {'type': 'solid_floor_demolition', 'description': 'Removal of carpet and underfelt', 'depth': 0,
             'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 3.32, 'labour_hours_per_unit': 0.11,
             'plant_cost': 0, 'total_cost': 3.32, 'link': 'SPONs',
             'Notes': 'We ignore the plant cost that is in SPONs because we assume the carpet is not scrapped and '
                      'therefore there is no need for a skip'},
            {'type': 'solid_floor_preparation',
             'description': 'clean surface of concrete to receive new damp-proof membrane', 'depth': 0,
             'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 4.36, 'labour_hours_per_unit': 0.14,
             'plant_cost': 0, 'total_cost': 4.36, 'link': 0, 'Notes': 0}, {
                'type': 'solid_floor_preparation',
                'description': 'Clean out crack to '
                               'form a 20mm×20mm '
                               'groove and fill with '
                               'cement: mortar mixed '
                               'with bonding agent',
                'depth': 0, 'depth_unit': 0,
                'cost_unit': 0,
                'thermal_conductivity': 0,
                'thermal_conductivity_unit': 0,
                'prime_material_cost': 0,
                'material_cost': 6.91,
                'labour_cost': 18.99,
                'labour_hours_per_unit': 0.61,
                'plant_cost': 0.16,
                'total_cost': 26.06, 'link': 0,
                'Notes': 'This step is the '
                         'assessment and repair of '
                         'any damage to the concrete '
                         'floor such as filling '
                         'cracks or levelling uneven '
                         'areas'},
            {'type': 'solid_floor_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier',
             'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
             'thermal_conductivity_unit': 0, 'prime_material_cost': 0.58, 'material_cost': 1.21, 'labour_cost': 0.48,
             'labour_hours_per_unit': 0.02, 'plant_cost': 0, 'total_cost': 1.69, 'link': 'SPONs', 'Notes': 0},
            {'type': 'solid_floor_redecoration',
             'description': 'Screeded beds; protection to compressible formwork exceeding 600mm wide', 'depth': 0,
             'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 9.6, 'material_cost': 9.89, 'labour_cost': 2.67, 'labour_hours_per_unit': 0.15,
             'plant_cost': 0, 'total_cost': 12.56, 'link': 'SPONs',
             'Notes': 'This is the screed layer, placed on top of the insulation'},
            {'type': 'solid_floor_redecoration', 'description': 'Fitting carpet', 'depth': 0, 'depth_unit': 0,
             'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 6.59, 'labour_hours_per_unit': 0.37,
             'plant_cost': 0, 'total_cost': 6.59, 'link': 'SPONs',
             'Notes': 'SPONs does not have data on re-fitting the carpet so we use the data in Fitted carpeting; '
                      'Gradus woven polypropylene tufted loop\n\n as a baseline. We assume re-use of carpets, '
                      'therefore we need just labour rates'},
            {'type': 'solid_floor_redecoration',
             'description': 'Fitting existing softwood skirting or architrave to new frames; 150mm high', 'depth': 0,
             'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0.01, 'labour_cost': 4.87, 'labour_hours_per_unit': 0.12,
             'plant_cost': 0, 'total_cost': 4.88, 'link': 'SPONs', 'Notes': 0}
        ]

        sol_floor_results = costs.solid_floor_insulation(
            insulation_floor_area=33.5,
            material=sol_floor_material,
            non_insulation_materials=sol_floor_non_insulation_materials
        )

        assert sol_floor_results == {
            'total': 4245.023520000001, 'subtotal': 3537.5196, 'vat': 707.5039200000001, 'contingency': 471.66928,
            'preliminaries': 235.83464, 'material': 1006.3399999999999, 'profit': 471.66928, 'labour_hours': 57.285,
            'labour_days': 2.386875, 'labour_cost': 1346.6464
        }

    def test_external_wall_insulation(self):
        mock_property = Mock()
        mock_property.data = {
            "county": "Northamptonshire",
            "property-type": "House",
            "built-form": 'End-Terrace'
        }

        costs = Costs(mock_property)

        ewi_material = {
            'type': 'external_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board',
            'depth': 150.0, 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.022,
            'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 23.53,
            'material_cost': 34.62, 'labour_cost': 33.06, 'labour_hours_per_unit': 1.4, 'plant_cost': 0,
            'total_cost': 67.68, 'link': 'SPONs', 'Notes': 0
        }
        ewi_non_insulation_materials = [
            {'type': 'ewi_wall_demolition',
             'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping '
                            'hammer; plaster to walls.',
             'depth': 0, 'depth_unit': 0, 'cost_unit': 'gbp_per_m2',
             'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 10.27,
             'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55,
             'link': 'SPONs', 'Notes': 0}, {'type': 'ewi_wall_demolition',
                                            'description': 'Stud walls: Remove wall linings '
                                                           'including battening behind; '
                                                           'plasterboard and skim',
                                            'depth': 0, 'depth_unit': 0,
                                            'cost_unit': 'gbp_per_m2',
                                            'thermal_conductivity': 0,
                                            'thermal_conductivity_unit': 0,
                                            'prime_material_cost': 0, 'material_cost': 0,
                                            'labour_cost': 6.23, 'labour_hours_per_unit': 0.2,
                                            'plant_cost': 1.25, 'total_cost': 7.48,
                                            'link': 'SPONs', 'Notes': 0},
            {'type': 'ewi_wall_demolition',
             'description': 'Lathe and Plaster walls: Remove wall linings including battening '
                            'behind; wood lath and plaster',
             'depth': 0, 'depth_unit': 0, 'cost_unit': 'gbp_per_m2',
             'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
             'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 6.85,
             'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94,
             'link': 'SPONs', 'Notes': 0}, {'type': 'ewi_wall_preparation',
                                            'description': 'Clean and prepare surfaces, '
                                                           'one coat Keim dilution, '
                                                           'one coat primer and two coats '
                                                           'of Keim Ecosil paint; Brick or '
                                                           'block walls; over 300 mm girth',
                                            'depth': 0, 'depth_unit': 0, 'cost_unit': 0,
                                            'thermal_conductivity': 0,
                                            'thermal_conductivity_unit': 0,
                                            'prime_material_cost': 0, 'material_cost': 7.3,
                                            'labour_cost': 5.62, 'labour_hours_per_unit': 0.3,
                                            'plant_cost': 0, 'total_cost': 12.92,
                                            'link': 'SPONs',
                                            'Notes': 'This work covers the preparation and '
                                                     'priming of the wall before insulating'},
            {'type': 'ewi_wall_redecoration',
             'description': 'EPS insulation fixed with adhesive to SFS structure (measured '
                            'separately) with horizontal PVC intermediate track and vertical '
                            'T-spines; with glassfibre mesh reinforcement embedded in Sto '
                            'Armat Classic Basecoat Render and Stolit K 1.5 Decorative '
                            'Topcoat Render (white)',
             'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
             'thermal_conductivity_unit': 0, 'prime_material_cost': 0, 'material_cost': 0,
             'labour_cost': 0, 'labour_hours_per_unit': 0, 'plant_cost': 0,
             'total_cost': 69.94, 'link': 'SPONs',
             'Notes': 'This material in SPONs is for 70mm EPS insulation, which comes in at a '
                      'cost of 99.17 per meter square. This includes the cost of insulation. '
                      'To get the costing for just the works and not the insulation, '
                      'we subtract the cost of EPS insulation, using Ravathem 75mm insulation '
                      'as an example, which costs £29.23 per meter square, giving us the cost '
                      'of the remaining works without insulation. This material gives us a '
                      'cost for basecoat, mesh application and a render finish'}]

        ewi_results = costs.external_wall_insulation(
            wall_area=95.9104281347967,
            material=ewi_material,
            non_insulation_materials=ewi_non_insulation_materials
        )

        assert ewi_results == {
            'total': 15047.078622131372, 'subtotal': 12539.232185109477, 'vat': 2507.8464370218953,
            'contingency': 808.9827216199662, 'preliminaries': 2022.4568040499155, 'material': 4020.565147410677,
            'profit': 1617.9654432399325, 'labour_hours': 187.02533486285358, 'labour_days': 5.8445417144641745,
            'labour_cost': 3921.5600094613983
        }

    def test_flat_roof_insulation(self):
        mock_property = Mock()
        mock_property.data = {
            "county": "Northamptonshire"
        }

        costs = Costs(mock_property)
        flat_roof_material = {'id': 1225, 'type': 'flat_roof_insulation',
                              'description': 'Kingspan Thermaroof TR21 zero OPD '
                                             'urethene insulation board',
                              'depth': 100.0, 'depth_unit': 'mm', 'cost': None,
                              'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04,
                              'r_value_unit': 'square_meter_kelvin_per_watt',
                              'thermal_conductivity': 0.025,
                              'thermal_conductivity_unit': 'watt_per_meter_kelvin',
                              'link': 'SPONs',
                              'created_at': "now", 'is_active': True,
                              'prime_material_cost': None, 'material_cost': 50.95,
                              'labour_cost': 10.66, 'labour_hours_per_unit': 0.48,
                              'plant_cost': 0.0, 'total_cost': 61.61,
                              'notes': "SPONs didn't have a labour hours so we use "
                                       "0.48 which is similar to other materials"}

        flat_roof_non_insulation_materials = [
            {'id': 17, 'type': 'mechanical_ventilation', 'description': 'Mechanical Extract Ventilation', 'depth': None,
             'depth_unit': None, 'cost': 500, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': None, 'r_value_unit': None,
             'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': None,
             'created_at': datetime.datetime(2023, 10, 18, 16, 39, 9, 827188), 'is_active': True,
             'prime_material_cost': None,
             'material_cost': None, 'labour_cost': None, 'labour_hours_per_unit': None, 'plant_cost': None,
             'total_cost': None,
             'notes': None},
            {'id': 1221, 'type': 'flat_roof_preparation',
             'description': 'clean surface to receive new damp-proof membrane',
             'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None,
             'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
             'thermal_conductivity_unit': None,
             'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True,
             'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 4.36, 'labour_hours_per_unit': 0.14,
             'plant_cost': 0.0, 'total_cost': 4.36,
             'notes': 'This data is based on concrete however forms a decent baseline for a Bituminous Felt flat roof'},
            {'id': 1223, 'type': 'flat_roof_preparation',
             'description': 'One coat primer; on wood surfaces before fixing; General surfaces; over 300 mm girth',
             'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None,
             'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
             'thermal_conductivity_unit': None,
             'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True,
             'prime_material_cost': None, 'material_cost': 2.49, 'labour_cost': 1.5, 'labour_hours_per_unit': 0.08,
             'plant_cost': 0.0, 'total_cost': 3.99, 'notes': 'SPONs data gives us a baseline for a wood surface'},
            {'id': 1224, 'type': 'flat_roof_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier',
             'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None,
             'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
             'thermal_conductivity_unit': None,
             'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True,
             'prime_material_cost': 0.58, 'material_cost': 1.21, 'labour_cost': 0.48, 'labour_hours_per_unit': 0.02,
             'plant_cost': 0.0, 'total_cost': 1.69, 'notes': None},
            {'id': 1234, 'type': 'flat_roof_waterproofing',
             'description': '20 mm thick two coat coverings; felt isolating membrane; to concrete (or '
                            'timber) base; flat or to falls or slopes not exceeding 10° from horizontal',
             'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None,
             'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
             'thermal_conductivity_unit': None, 'link': 'SPONs',
             'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True,
             'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0,
             'labour_hours_per_unit': 0.5, 'plant_cost': 0.0, 'total_cost': 31.13, 'notes': None}
        ]

        flat_roof_floor_results = costs.flat_roof_insulation(
            floor_area=33.5,
            material=flat_roof_material,
            non_insulation_materials=flat_roof_non_insulation_materials
        )

        assert flat_roof_floor_results == {'total': 5325.327767999999, 'subtotal': 4437.773139999999,
                                           'vat': 887.5546279999999, 'contingency': 459.07998,
                                           'preliminaries': 306.05332, 'material': 1830.775, 'profit': 612.10664,
                                           'labour_hours': 24.79, 'labour_days': 1.549375, 'labour_cost': 186.9032}

        assert costs.labour_adjustment_factor == 0.88

    # Mock property instance for regional tests
    @pytest.fixture(params=[
        ("Northamptonshire", "East Midlands", 7927.44),
        ("Greater London Authority", "Inner London", 10475.0),
        ("Adur", "South East England", 8333.32),
        ("Bournemouth", "South West England", 8452),
        ("Basildon", "East of England", 7895.44),
        ("Birmingham", "West Midlands", 7706.2),
        ("County Durham", "North East England", 8113.96),
        ("Allerdale", "North West England", 6481.68),
        ("York", "Yorkshire and the Humber", 8243.6),
        ("Cardiff", "Wales", 7595.32),
        ("Glasgow City", "Scotland", 7871.88),
        ("Belfast", "Northern Ireland", 8504.36)
    ])
    def mock_property_with_region(self, request):
        county, region, expected_cost = request.param
        mock_property = Mock()
        mock_property.data = {"county": county}
        return mock_property, region, expected_cost

    # Test for different wattages
    @pytest.mark.parametrize("wattage, expected_cost", [
        (3000, 5945.58),
        (4000, 7927.44),
        (5000, 9909.3),
        (6000, 11891.16),
    ])
    def test_solar_pv_different_wattages(self, wattage, expected_cost):
        mock_property = Mock()
        mock_property.data = {"county": "Mansfield"}
        costs = Costs(mock_property)
        result = costs.solar_pv(wattage)
        assert result['total'] == pytest.approx(expected_cost, rel=0.01)

    def test_solar_pv_regional_variation(self, mock_property_with_region):
        # Test for regional cost variations
        property_instance, expected_region, expected_cost = mock_property_with_region
        costs = Costs(property_instance)

        assert costs.region == expected_region

        result = costs.solar_pv(4000)  # Testing with a fixed wattage of 4000
        assert result['total'] == pytest.approx(expected_cost, rel=0.01)
