# Spec: https://raw.githubusercontent.com/communitiesuk/epb-data-warehouse/main/api/api.yml
from __future__ import annotations

from typing import Any, Optional

import httpx

from backend.epc_client.exceptions import (
    EpcApiError,
    EpcNotFoundError,
    EpcRateLimitError,
)
from backend.epc_client._retry import call_with_retry
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
from datatypes.epc.search import EpcSearchResult


class EpcClientService:
    BASE_URL = "https://api.get-energy-performance-data.communities.gov.uk"
    REQUEST_TIMEOUT = 10.0

    def __init__(self, auth_token: str) -> None:
        self._headers = {
            "Authorization": f"Bearer {auth_token}",
            "Accept": "application/json",
        }

    @staticmethod
    def _parse_retry_after(resp: httpx.Response) -> Optional[float]:
        header = resp.headers.get("Retry-After")
        if header is None:
            return None
        try:
            return float(header)
        except (TypeError, ValueError):
            return None

    def get_by_certificate_number(self, cert_num: str) -> EpcPropertyData:
        raw = call_with_retry(lambda: self._fetch_certificate(cert_num))
        return EpcPropertyDataMapper.from_api_response(raw)

    def get_by_uprn(self, uprn: int) -> Optional[EpcPropertyData]:
        results = call_with_retry(lambda: self._search(uprn=uprn))
        if not results:
            return None
        latest = max(results, key=lambda r: r.registration_date)
        return self.get_by_certificate_number(latest.certificate_number)

    def search_by_postcode(self, postcode: str) -> list[EpcSearchResult]:
        return call_with_retry(lambda: self._search(postcode=postcode))

    # ------------------------------------------------------------------
    # Private helperEpcRateLimpolarss
    # ------------------------------------------------------------------

    def _fetch_certificate(self, cert_num: str) -> dict[str, Any]:
        resp = httpx.get(
            f"{self.BASE_URL}/api/certificate",
            params={"certificate_number": cert_num},
            headers=self._headers,
            timeout=self.REQUEST_TIMEOUT,
        )
        if resp.status_code == 404:
            raise EpcNotFoundError(cert_num)
        if resp.status_code == 429:
            raise EpcRateLimitError(
                "Rate limited by EPC API",
                retry_after=self._parse_retry_after(resp),
            )
        if not resp.is_success:
            raise EpcApiError(f"EPC API error {resp.status_code}: {resp.text}")
        return resp.json()["data"]

    def _search(
        self,
        postcode: Optional[str] = None,
        uprn: Optional[int] = None,
    ) -> list[EpcSearchResult]:
        params: dict[str, str | int] = {}
        if postcode:
            params["postcode"] = postcode
        if uprn is not None:
            params["uprn"] = uprn

        resp = httpx.get(
            f"{self.BASE_URL}/api/domestic/search",
            params=params,
            headers=self._headers,
            timeout=self.REQUEST_TIMEOUT,
        )
        if resp.status_code == 404:
            return []
        if resp.status_code == 429:
            raise EpcRateLimitError(
                "Rate limited by EPC API",
                retry_after=self._parse_retry_after(resp),
            )
        if not resp.is_success:
            raise EpcApiError(f"EPC API error {resp.status_code}: {resp.text}")

        rows = resp.json().get("data", [])
        return [self._parse_search_result(r) for r in rows]

    @staticmethod
    def _parse_search_result(row: dict[str, Any]) -> EpcSearchResult:
        return EpcSearchResult(
            certificate_number=row["certificateNumber"],
            address_line_1=row["addressLine1"],
            address_line_2=row.get("addressLine2"),
            address_line_3=row.get("addressLine3"),
            address_line_4=row.get("addressLine4"),
            postcode=row["postcode"],
            post_town=row["postTown"],
            uprn=row.get("uprn"),
            current_energy_efficiency_band=row["currentEnergyEfficiencyBand"],
            registration_date=row["registrationDate"],
        )
