import numpy as np
import pandas as pd


class PropertyValuation:
    """
    This is a placeholder class for the property valuation model
    """

    UPRN_VALUE_LOOKUP = {
        15038202: 202000,
        37024763: 213000,
        100070478545: 212000,
        100070297696: 662000,  # Based on Zoopla's estimation of nearby house, 8 bloomfield road
        100070476394: 222000,  # Based on Zoopla's estimation of next door, 20 Parkside
        100071264896: 128000,
        # Based on next door neighbour: https://themovemarket.com/tools/propertyprices/flat-2-queens-wood-house-219
        # -brandwood-road-birmingham-b14-6pu
        100070533688: 218000,  # Based on Zoopla's estimation of 95 Tenby Road, which is also mid terrace
        100070505235: 344000,  # Based on Zoopla's estimation of 131 School road, which is also semi-detached
        100070513306: 182000,  # Based on Zoopla's estimation of 61 Simmons Drive
        100071306896: 77000,  # Based on Flat 2 of 44 Wedgewood Road on Zoopla
        100021192109: 650000,  # Based on Zoopla
        766249482: 358000,  # Based on Zoopla estimate for 19 Spring Lane, 3 bedroom semi-detached
        100120703802: 277000,  # Based on Zoopla
        10014469685: 286000,  # Based on Zoopla
        10001328782: 196000,  # Based on Zoopla
        # Urban Splash - valuations from The Move Market
        10023345430: 74_000,
        10023345435: 99_000,
        10023345436: 62_000,
        10023345441: 62_000,
        10094183503: 2_988_000,
        10094183499: 123_000,
        10070056824: 70_000,
        110070056242: 100_000,
        10070056243: 130_000,
        10070056817: 130_000,
        10094183501: 185_000,
        10070056250: 71_000,
        10094183500: 185_000,
        10070056843: 67_000,
        10070056844: 67_000,
        10070056241: 76_000,
        10070056834: 63_000,
        10023345439: 62_000,
        10070056815: 101_000,
        10070056816: 101_000,
        10094183498: 101_000,
        10070056840: 673_000,
        10070056848: 76_000,
        10070056849: 76_000,
        10070056829: 76_000,
        10070056920: 76_000,
        10023345463: 76_000,
        # IMMO Dudley Pilot - search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
        90070461: 172_000,  # Based on Zoopla
        90022227: 181_000,  # Based on Zoopla
        90106884: 180_000,  # Based on Zoopla
        90051858: 201_000,  # Based on Zoopla
        90060989: 172_000,  # Based on Zoopla
        90048026: 196_000,  # Based on Zoopla
        90077535: 192_000,  # Based on Zoopla
        90093693: 279_000,  # Based on Zoopla
        90055152: 149_000,  # Based on Zoopla
        90028499: 238_000,  # Based on Zoopla
        # IMMO Dudley Pilot 2- search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
        90039318: 177_000,  # Based on Zoopla
        90038384: 170_000,  # Based on Zoopla
        90105380: 185_000,  # Based on Zoopla
        90124001: 165_000,  # Based on Zoopla
        90013980: 148_000,  # Based on Zoopla
        90087154: 184_000,  # Based on Zoopla
        90046817: 167_000,  # Based on Zoopla
        # Goldman Sachs Pilot for inrto - search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
        100070358888: 153_000,  # Based on Zoopla
        10090436544: 282_000,  # Based on Zoopla
        100070365751: 177_000,  # Based on Zoopla
        10095952767: 168_000,  # Based on Zoopla
        100070520130: 177_000,  # Based on Zoopla
        100070333957: 185_000,  # Based on Zoopla
        100070543258: 211_000,  # Based on Zoopla
        # Vander Elliot Pilot - search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
        41018850: 104_000,  # Based on Zoopla
        38237316: 74_000,  # Based on Zoopla
        38237317: 74_000,  # Based on Zoopla
        41052320: 70_000,  # Based on Zoopla
        41052321: 70_000,  # Based on Zoopla
        41052322: 38_000,  # Based on Zoopla
        41222759: 38_000,  # Based on Zoopla
        41222760: 46_000,  # Based on Zoopla
        41222761: 270_000,  # Based on Zoopla
        41212534: 38_000,  # Based on Zoopla
        # Northern Group Pilot - search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
        10070868263: 194_000,  # Based on Zoopla
        10070868244: 195_000,  # Based on Zoopla
        # Places For People Pilot
        200140644: 385_000,
        200140645: 481_000,
        200140646: 372_000,
        200140647: 481_000,
        200140648: 373_000,
        200140649: 373_000,
        # Vander Elliot Intrusive surveys
        12103116: 1_537_000,
        12103117: 1_404_000,
        # GLA Proposal
        100020606627: 409_000
    }

    # We base our valuation uplifts on a number of sources
    # https://www.moneysupermarket.com/gas-and-electricity/value-of-efficiency/
    MSM_MAPPING = [
        {"start": "G", "end": "F", "increase_percentage": 0.06},
        {"start": "F", "end": "E", "increase_percentage": 0.01},
        {"start": "E", "end": "D", "increase_percentage": 0.01},
        {"start": "D", "end": "C", "increase_percentage": 0.02},
        {"start": "C", "end": "B", "increase_percentage": 0.04},
        {"start": "B", "end": "A", "increase_percentage": 0.0},
    ]

    # https://www.lloydsbankinggroup.com/media/press-releases/2021/halifax/homebuyers-pay-a-green-premium-of-40000
    # -for-the-most-energy-efficient-properties.html
    LLOYDS_MAPPING = [
        {"start": "G", "end": "F", "increase_percentage": 0.038},
        {"start": "F", "end": "E", "increase_percentage": 0.029},
        {"start": "E", "end": "D", "increase_percentage": 0.024},
        {"start": "D", "end": "C", "increase_percentage": 0.02},
        {"start": "C", "end": "B", "increase_percentage": 0.02},
        {"start": "B", "end": "A", "increase_percentage": 0.018},
    ]

    KNIGHT_FRANK_MAPPING = [
        {"start": "D", "end": "C", "increase_percentage": 0.03},
        {"start": "D", "end": "B", "increase_percentage": 0.088},
        {"start": "D", "end": "A", "increase_percentage": 0.088},
    ]

    NATIONWIDE_MAPPING = [
        # {"start": "G", "end": "D", "increase_percentage": 0.035},
        # {"start": "F", "end": "D", "increase_percentage": 0.035},
        # {"start": "D", "end": "B", "increase_percentage": 0.017},
        # {"start": "D", "end": "A", "increase_percentage": 0.017},
    ]

    # Found here: https://www.rightmove.co.uk/news/articles/property-news/green-premium-epc-ratings/
    # F -> C is + 15%
    # E -> C is +7%
    # D -> C is +3%
    RIGHTMOVE_MAPPING = [
        {"start": "G", "end": "C", "increase_percentage": 0.15},
        {"start": "G", "end": "B", "increase_percentage": 0.15},
        {"start": "G", "end": "A", "increase_percentage": 0.15},

        {"start": "F", "end": "C", "increase_percentage": 0.15},
        {"start": "F", "end": "B", "increase_percentage": 0.15},
        {"start": "F", "end": "A", "increase_percentage": 0.15},

        {"start": "E", "end": "C", "increase_percentage": 0.07},
        {"start": "E", "end": "B", "increase_percentage": 0.07},
        {"start": "E", "end": "A", "increase_percentage": 0.07},

        {"start": "D", "end": "C", "increase_percentage": 0.03},
        {"start": "D", "end": "B", "increase_percentage": 0.03},
        {"start": "D", "end": "A", "increase_percentage": 0.03},

    ]

    # Additional sources:
    # https://superhomes.org.uk/wp-content/uploads/2024/05/The-Impact-of-Retrofit-on-Residential-Property-Market
    # -Values-7-rotated-1.pdf

    EPC_BANDS = ["G", "F", "E", "D", "C", "B", "A"]

    @classmethod
    def get_increase(cls, epc_band_range):

        increases = []
        for i in range(len(epc_band_range)):

            if i == len(epc_band_range) - 1:
                break

            current = epc_band_range[i]
            next = epc_band_range[i + 1]

            msm_increase = [x for x in cls.MSM_MAPPING if x["start"] == current and x["end"] == next][0]
            lloyds_increase = [x for x in cls.LLOYDS_MAPPING if x["start"] == current and x["end"] == next][0]

            increases.append(
                {
                    "start": current,
                    "end": next,
                    "msm_increase": msm_increase["increase_percentage"],
                    "lloyds_increase": lloyds_increase["increase_percentage"],
                }
            )

        # We now aggregate the increases. The should be compound increases so we multiply them together
        msm_increase = np.prod([1 + x["msm_increase"] for x in increases]) - 1
        lloyds_increase = np.prod([1 + x["lloyds_increase"] for x in increases]) - 1

        return msm_increase, lloyds_increase

    @classmethod
    def estimate(cls, property_instance, target_epc, total_cost=None):
        """
        This function estimates the value of a property based on the current EPC rating and the target EPC rating
        :param property_instance: An instance of the Property class
        :param target_epc: The target EPC rating
        :param total_cost: The total cost of the retrofit
        :return:
        """
        current_value = (
            property_instance.valuation if property_instance.valuation else
            cls.UPRN_VALUE_LOOKUP.get(property_instance.uprn)
        )

        current_epc = property_instance.epc_record.current_energy_rating

        if not current_value:
            # In this case, we return a % improvement rather than an absolute
            relative_improvement = cls.estimate_valuation_improvement(
                current_value=1,
                current_epc=current_epc,
                target_epc=target_epc,
                total_cost=1
            )
            return {
                "current_value": 0,
                "lower_bound_increased_value": relative_improvement["lower_bound_increased_value"] - 1,
                "upper_bound_increased_value": relative_improvement["upper_bound_increased_value"] - 1,
                "average_increased_value": relative_improvement["average_increased_value"] - 1,
                "average_increase": relative_improvement["average_increase"]
            }

        return cls.estimate_valuation_improvement(current_value, current_epc, target_epc, total_cost)

    @classmethod
    def estimate_valuation_improvement(cls, current_value, current_epc, target_epc, total_cost=None):
        """
        This function estimates the value of a property based on the current EPC rating and the target EPC rating
        :param current_value:
        :param current_epc:
        :param target_epc:
        :param total_cost:
        :return:
        """

        if not current_value:
            return {
                "current_value": 0,
                "lower_bound_increased_value": 0,
                "upper_bound_increased_value": 0,
                "average_increased_value": 0,
                "average_increase": 0
            }

        # We get the spectrum of ratings between the current and target EPC
        epc_band_range = cls.EPC_BANDS[cls.EPC_BANDS.index(current_epc): cls.EPC_BANDS.index(target_epc) + 1]

        msm_increase, lloyds_increase = cls.get_increase(epc_band_range)

        # We now use the knight frank, nationwide and Rightmove data to get further valuation evidence, if we have it
        kf_increase = [x for x in cls.KNIGHT_FRANK_MAPPING if x["start"] == current_epc and x["end"] == target_epc]
        nw_increase = [x for x in cls.NATIONWIDE_MAPPING if x["start"] == current_epc and x["end"] == target_epc]
        rm_increase = [x for x in cls.RIGHTMOVE_MAPPING if x["start"] == current_epc and x["end"] == target_epc]

        kf_increase = kf_increase[0]["increase_percentage"] if kf_increase else None
        nw_increase = nw_increase[0]["increase_percentage"] if nw_increase else None
        rm_increase = rm_increase[0]["increase_percentage"] if rm_increase else None

        all_increases = [
            x for x in [msm_increase, lloyds_increase, kf_increase, nw_increase, rm_increase] if x is not None
        ]

        max_increase = max(all_increases)
        min_increase = min(all_increases)

        avg_increase = np.mean(all_increases)

        if total_cost is not None:
            # We CAP the retrofit ROI at 2
            avg_increase_value = current_value * avg_increase
            if avg_increase_value / total_cost > 2:
                # We re-scale the % so that the average value increase is no more than 2 times the total cost
                double_cost = 2 * total_cost
                new_avg_increase = double_cost / current_value
                scalar = new_avg_increase / avg_increase
                # We scale the min and max increases by the same scalar
                min_increase *= scalar
                max_increase *= scalar
                avg_increase = new_avg_increase

        return {
            "current_value": current_value,
            "lower_bound_increased_value": float(current_value * (1 + min_increase)),
            "upper_bound_increased_value": float(current_value * (1 + max_increase)),
            "average_increased_value": float(current_value * (1 + avg_increase)),
            "average_increase": float(current_value * (1 + avg_increase) - current_value)
        }
