import requests
import math
from dataclasses import dataclass
from typing import List, Dict

CENSUS_GEOCODER = "https://geocoding.geo.census.gov/geocoder/locations/onelineaddress"
ACS_API = "https://api.census.gov/data"
TIGER_ARCGIS = "https://tigerweb.geo.census.gov/arcgis/rest/services/TIGERweb/Tracts_Blocks/MapServer/8/query"


@dataclass
class Result:
    address: str
    radius_miles: float
    estimated_median_home_value: float
    tracts_used: int

    def to_dict(self):
        return self.__dict__


def geocode(address: str):
    params = {
        "address": address,
        "benchmark": "Public_AR_Current",
        "format": "json"
    }
    r = requests.get(CENSUS_GEOCODER, params=params)
    r.raise_for_status()
    matches = r.json()["result"]["addressMatches"]
    if not matches:
        raise ValueError("Address not found")

    coords = matches[0]["coordinates"]
    return coords["y"], coords["x"]  # lat, lon


def miles_to_degrees(miles):
    return miles / 69.0


def get_tracts(lat, lon, radius_miles):
    radius_deg = miles_to_degrees(radius_miles)

    geom = f"{lon},{lat}"
    params = {
        "geometry": geom,
        "geometryType": "esriGeometryPoint",
        "inSR": 4326,
        "spatialRel": "esriSpatialRelIntersects",
        "distance": radius_miles * 1609.34,
        "units": "esriSRUnit_Meter",
        "outFields": "STATE,COUNTY,TRACT",
        "f": "json"
    }

    r = requests.get(TIGER_ARCGIS, params=params)
    r.raise_for_status()

    features = r.json().get("features", [])
    tracts = []
    for f in features:
        attr = f["attributes"]
        tracts.append((attr["STATE"], attr["COUNTY"], attr["TRACT"]))

    return list(set(tracts))


def get_acs_data(tracts: List[tuple], year: int):
    results = []

    for state, county, tract in tracts:
        url = f"{ACS_API}/{year}/acs/acs5"
        params = {
            "get": "B25077_001E,B25003_002E",
            "for": f"tract:{tract}",
            "in": f"state:{state} county:{county}"
        }

        r = requests.get(url, params=params)
        if r.status_code != 200:
            continue

        data = r.json()
        if len(data) < 2:
            continue

        row = data[1]
        try:
            median_value = int(row[0])
            owner_units = int(row[1])
        except:
            continue

        if median_value > 0 and owner_units > 0:
            results.append((median_value, owner_units))

    return results


def weighted_median(values: List[tuple]):
    values = sorted(values, key=lambda x: x[0])
    total_weight = sum(w for _, w in values)
    cumulative = 0

    for value, weight in values:
        cumulative += weight
        if cumulative >= total_weight / 2:
            return value

    return values[-1][0]


def estimate_median_home_value(address: str, radius_miles: float = 50, year: int = 2024):
    lat, lon = geocode(address)
    tracts = get_tracts(lat, lon, radius_miles)
    data = get_acs_data(tracts, year)

    if not data:
        raise ValueError("No data found")

    median = weighted_median(data)

    return Result(
        address=address,
        radius_miles=radius_miles,
        estimated_median_home_value=median,
        tracts_used=len(data)
    )