"""Kiểm tra đồng ý từ máy chủ: gọi /api/v1/ext/consents/check trước khi gửi sự kiện cho Meta, Google... Chỉ dùng thư viện chuẩn (Python 3.8+).

Đóng khi lỗi: chỉ `allowed` là True khi API trả đúng `granted: true` và không kèm `expired: true`. Chưa có bản ghi còn hiệu lực (`granted` là None: chưa có, đã bị thay bằng mức hỏi lại mới hay quá hạn; `expired` là True khi có bản ghi nhưng đã hết hiệu lực), khoá sai, hết giờ, lỗi mạng,
chuyển hướng, trả lời lạ... đều cho `allowed` False. Hàm không bao giờ ném lỗi (không làm hỏng đơn hàng của bạn); lý do nằm ở `error`:
None | 'invalid_arguments' | 'network' | 'timeout' | 'http' | 'invalid_response'.
"""
import json
import os
import socket
import time
import urllib.error
import urllib.parse
import urllib.request

CATEGORIES = ("necessary", "functional", "analytics", "marketing")
REF_TYPES = ("email", "phone", "customer_id")
MAX_BODY = 65536
LOOPBACK = ("localhost", "127.0.0.1", "::1")


def _closed(error):
    return {"granted": None, "allowed": False, "consent_id": None, "recorded_at": None, "config_version": None, "expired": False, "error": error}


class _NoRedirect(urllib.request.HTTPRedirectHandler):
    """Không bao giờ đi theo chuyển hướng (khoá API sẽ đi theo): 3xx thành HTTPError."""

    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def check_consent(api_key, site_id, category, consent_id=None, ref_type=None, ref=None, base_url=None, timeout=3.0):
    """Trả {'granted', 'allowed', 'consent_id', 'recorded_at', 'config_version', 'expired', 'error'}.

    Dùng `consent_id` (mã đồng ý của khách) HOẶC `ref_type` + `ref` (email, số điện thoại hay mã khách hàng đã liên kết).
    `base_url` mặc định lấy từ biến môi trường WDL_BASE_URL; phải là https (http chỉ cho 127.0.0.1/localhost khi thử).
    `timeout` tính bằng giây, áp dụng cho cả lời gọi.
    """
    try:
        if base_url is None:
            base_url = os.environ.get("WDL_BASE_URL")
        has_id = isinstance(consent_id, str) and consent_id != ""
        has_ref = ref_type is not None or ref is not None
        valid_ref = ref_type in REF_TYPES and isinstance(ref, str) and ref != ""
        if (
            not isinstance(api_key, str) or api_key == ""
            or not isinstance(site_id, str) or site_id == ""
            or category not in CATEGORIES
            or not isinstance(base_url, str)
            or has_id == has_ref
            or (has_ref and not valid_ref)
        ):
            return _closed("invalid_arguments")
        parts = urllib.parse.urlsplit(base_url)
        if not parts.hostname or not (parts.scheme == "https" or (parts.scheme == "http" and parts.hostname in LOOPBACK)):
            return _closed("invalid_arguments")
        # consent_id đi trên URL (GET); ref (email, số điện thoại, mã khách hàng) không bao giờ nằm trên URL: đi trong thân JSON của POST.
        query = urllib.parse.urlencode({"siteId": site_id, "category": category, "consentId": consent_id}, quote_via=urllib.parse.quote) if has_id else ""
        url = urllib.parse.urlunsplit((parts.scheme, parts.netloc, "/api/v1/ext/consents/check", query, ""))
        limit = float(timeout) if isinstance(timeout, (int, float)) and timeout > 0 else 3.0
        deadline = time.monotonic() + limit
        headers = {"Authorization": "Bearer " + api_key, "Accept": "application/json"}
        data = None
        if not has_id:
            headers["Content-Type"] = "application/json"
            data = json.dumps({"siteId": site_id, "category": category, "refType": ref_type, "ref": ref}).encode("utf-8")
        request = urllib.request.Request(url, data=data, method="GET" if has_id else "POST", headers=headers)
        opener = urllib.request.build_opener(_NoRedirect)
        try:
            with opener.open(request, timeout=limit) as response:
                if response.status < 200 or response.status > 299:
                    return _closed("http")
                chunks, size = [], 0
                while True:
                    if time.monotonic() > deadline:
                        return _closed("timeout")
                    chunk = response.read(4096)
                    if not chunk:
                        break
                    size += len(chunk)
                    if size > MAX_BODY:
                        return _closed("invalid_response")
                    chunks.append(chunk)
        except urllib.error.HTTPError:
            return _closed("http")
        except (socket.timeout, TimeoutError):
            return _closed("timeout")
        except urllib.error.URLError as e:
            return _closed("timeout" if isinstance(e.reason, (socket.timeout, TimeoutError)) else "network")
        except OSError:
            return _closed("network")
        try:
            body = json.loads(b"".join(chunks).decode("utf-8"))
        except ValueError:
            return _closed("invalid_response")
        if not isinstance(body, dict) or "granted" not in body or not (body["granted"] is None or isinstance(body["granted"], bool)):
            return _closed("invalid_response")
        text = lambda v: v if isinstance(v, str) else None
        return {
            "granted": body["granted"],
            "allowed": body["granted"] is True and body.get("expired") is not True,
            "consent_id": text(body.get("consentId")),
            "recorded_at": text(body.get("recordedAt")),
            "config_version": body["configVersion"] if isinstance(body.get("configVersion"), int) and not isinstance(body.get("configVersion"), bool) else None,
            "expired": body.get("expired") is True,
            "error": None,
        }
    except Exception:
        return _closed("network")
