import logging
from datetime import datetime
from typing import ClassVar, List
from enum import Enum
from pydantic import BaseModel, validator, ValidationError
from pytz import UTC
from requests import RequestException

from idunn import settings
from idunn.datasources.recycling import recycling_client
from .base import BaseBlock

logger = logging.getLogger(__name__)

MAX_DISTANCE_AROUND_POI = settings["RECYCLING_MAX_DISTANCE_AROUND_POI"]


def is_poi_in_finistere(poi):
    admins = poi.get_raw_admins()
    return any(
        any(c.get("name") == "ISO3166-2" and c.get("value") == "FR-29" for c in a.get("codes", []))
        for a in admins
    )


class ContainerType(str, Enum):
    recyclable = "recyclable"
    glass = "glass"
    unknown = "unknown"


class RecyclingContainer(BaseModel):
    updated_at: datetime
    filling_level: int
    type: ContainerType
    place_description: str

    @validator("type", pre=True)
    def container_type_fallback(cls, value):
        if value not in (t.value for t in ContainerType):
            return ContainerType.unknown
        return value

    @validator("updated_at")
    def validate_datetime(cls, dt):
        dt = dt.replace(microsecond=0)
        if dt.tzinfo is None:
            return UTC.localize(dt)
        return dt.astimezone(UTC)


class RecyclingBlock(BaseBlock):
    BLOCK_TYPE: ClassVar = "recycling"

    containers: List[RecyclingContainer]

    @classmethod
    def from_es(cls, place, lang):
        if not recycling_client.enabled:
            # Data source is not configured
            return None

        if place.PLACE_TYPE != "poi":
            return None

        if place.get_class_name() != "recycling":
            return None

        if not is_poi_in_finistere(place):
            return None

        try:
            containers = cls.fetch_containers(place)
        except (RequestException, ValidationError):
            logger.warning("Failed to fetch recycling containers data", exc_info=True)
            return None

        if not containers:
            return None
        return cls(containers=containers)

    @classmethod
    def fetch_containers(cls, place):
        coord = place.get_coord()
        if not coord:
            return []

        lat = coord.get("lat")
        lon = coord.get("lon")

        if lat is None or lon is None:
            return []

        hits = recycling_client.get_latest_measures(
            lat=lat, lon=lon, max_distance=MAX_DISTANCE_AROUND_POI
        )
        containers = []
        for h in hits:
            doc = h["_source"]
            containers.append(
                RecyclingContainer(
                    type=doc.get("data", {}).get("pav", {}).get("wasteType"),
                    updated_at=doc.get("hour"),
                    filling_level=doc.get("volume"),
                    place_description=doc.get("metadata", {}).get("entity"),
                )
            )
        return containers