from __future__ import print_function
import json
import logging
import time
try:
    from urllib.parse import urlencode
    from urllib.request import Request, urlopen
    from urllib.error import HTTPError, URLError
except ImportError:
    from urllib import urlencode
    from urllib2 import Request, urlopen, HTTPError, URLError
from .models import Alert
from .source_result import FetchResult

LOG = logging.getLogger(__name__)


class NWSClient(object):
    def __init__(self, config):
        self.config = config

    def _request(self, area_codes):
        base = self.config.get("api_base", "https://api.weather.gov").rstrip("/")
        query = urlencode({"zone": ",".join(area_codes)})
        url = base + "/alerts/active?" + query
        headers = {
            "User-Agent": self.config.get("user_agent"),
            "Accept": "application/geo+json",
            "Cache-Control": "no-cache",
        }
        request = Request(url, headers=headers)
        response = urlopen(request, timeout=int(self.config.get("timeout_seconds", 15)))
        raw = response.read()
        if not isinstance(raw, str):
            raw = raw.decode("utf-8")
        return json.loads(raw)

    def _with_retries(self, area_codes):
        attempts = int(self.config.get("retries", 2)) + 1
        last = None
        for attempt in range(attempts):
            try:
                return self._request(area_codes)
            except Exception as exc:
                last = exc
                if attempt + 1 < attempts:
                    time.sleep(min(2 ** attempt, 5))
        raise last

    def fetch(self, area_codes):
        result = FetchResult()
        codes = [str(code).upper() for code in area_codes]
        # The API accepts comma-separated zone values. Chunking prevents oversized URLs.
        for offset in range(0, len(codes), 25):
            chunk = codes[offset:offset + 25]
            try:
                payload = self._with_retries(chunk)
                for code in chunk:
                    result.successful_areas.add(code)
                self._consume(payload, chunk, result)
            except Exception as exc:
                # Some proxies reject a multi-zone query. Fall back to one request per code.
                if len(chunk) > 1:
                    for code in chunk:
                        try:
                            payload = self._with_retries([code])
                            result.successful_areas.add(code)
                            self._consume(payload, [code], result)
                        except Exception as single_exc:
                            result.failed_areas.add(code)
                            result.errors.append("%s: %s" % (code, single_exc))
                else:
                    result.failed_areas.update(chunk)
                    result.errors.append("%s: %s" % (",".join(chunk), exc))
        return result

    def _consume(self, payload, requested_codes, result):
        for feature in payload.get("features") or []:
            matched = []
            properties = feature.get("properties") or {}
            affected = properties.get("affectedZones") or []
            geocode = properties.get("geocode") or {}
            ugc = geocode.get("UGC") or geocode.get("ugc") or []
            joined = " ".join([str(x) for x in affected] + [str(x) for x in ugc])
            for code in requested_codes:
                if code in joined:
                    matched.append(code)
            if not matched and len(requested_codes) == 1:
                matched = list(requested_codes)
            alert = Alert.from_feature(feature, matched)
            result.add_alert(alert)
