"""PixorAds advertiser confirmations (Python 3.11+, standard library).

Import into your SERVER. Persist the complete event in an outbox in the same
transaction as completing the goal; resend that event until receive_event returns.
Never construct telegram_id, first_seen_at or goal completion from an untrusted
browser request. The Telegram ID comes from an authenticated Bot API update or
validated Mini App initData. first_seen_at is the ORIGINAL service registration
timestamp, kept even when an account is deleted and re-created.

BOT: read px_... from the real /start update, bind it to update.from_user.id, and
use the account's original creation time. A repeat /start is NOT a new user.
MINI APP: validate initData at login, save start_param with that account, and send
an event after your server commits registration/tutorial/custom completion.
initData authenticates the user; it does not prove a completed goal.

Required environment: PIXORADS_POSTBACK_URL, PIXORADS_CAMPAIGN_ID,
PIXORADS_ADVERTISER_SECRET. No token or key belongs in browser JavaScript.
See /developers#offerwall-actions for sandbox and revocation semantics.
"""

import hashlib
import hmac
import json
import os
import time
from datetime import datetime
from http.client import HTTPMessage
from typing import IO, Any, TypedDict
from urllib.parse import parse_qsl, urlsplit
from urllib.request import Request


class MiniAppIdentity(TypedDict):
    telegram_id: int
    attempt_token: str


class AdvertiserEvent(TypedDict):
    campaign_id: str
    attempt_token: str
    telegram_id: int
    event_id: str
    event_name: str
    occurred_at: str
    first_seen_at: str
    status: str
    reason: str | None


def validated_miniapp_identity(
    init_data: str, bot_token: str, max_age: int = 300
) -> MiniAppIdentity:
    """Validate at login; store resulting identity in your server session."""
    if len(init_data) > 8192:
        raise ValueError("initData too long")
    pairs = parse_qsl(init_data, keep_blank_values=True, strict_parsing=True)
    if len({k for k, _ in pairs}) != len(pairs):
        raise ValueError("Duplicate initData fields")
    fields = dict(pairs)
    supplied = fields.pop("hash", "")
    check = "\n".join(f"{k}={v}" for k, v in sorted(fields.items()))
    secret = hmac.digest(b"WebAppData", bot_token.encode(), "sha256")
    expected = hmac.new(secret, check.encode(), "sha256").hexdigest()
    if not hmac.compare_digest(expected.encode(), supplied.encode()):
        raise ValueError("Invalid Telegram signature")
    age = time.time() - int(fields["auth_date"])
    if age < -30 or age > max_age:
        raise ValueError("Expired Telegram identity")
    user: dict[str, Any] = json.loads(fields["user"])
    if type(user.get("id")) is not int or user["id"] <= 0:
        raise ValueError("Invalid Telegram user")
    return {"telegram_id": user["id"], "attempt_token": fields.get("start_param", "")}


def make_event(
    *,
    attempt_token: str,
    telegram_id: int,
    event_id: str,
    event_name: str,
    occurred_at: datetime,
    first_seen_at: datetime,
    status: str = "completed",
    reason: str | None = None,
) -> AdvertiserEvent:
    if occurred_at.tzinfo is None or first_seen_at.tzinfo is None:
        raise ValueError("Use timezone-aware timestamps from your database")
    return {
        "campaign_id": os.environ["PIXORADS_CAMPAIGN_ID"],
        "attempt_token": attempt_token,
        "telegram_id": telegram_id,
        "event_id": event_id,
        "event_name": event_name,
        "occurred_at": occurred_at.isoformat(),
        "first_seen_at": first_seen_at.isoformat(),
        "status": status,
        "reason": reason,
    }


def receive_event(event: AdvertiserEvent) -> dict[str, Any]:
    """Send a PERSISTED event. Keep event_id and body unchanged on every retry.

    HTTP/network failure: retain event and retry from your durable outbox.
    HTTP 200 + accepted=false: business rejection; store reason, do not retry
    with altered timestamps or a different identity. No local reward is implied.
    HTTP 409: reused event_id with a different payload; fix your outbox.
    """
    endpoint = os.environ["PIXORADS_POSTBACK_URL"]
    if urlsplit(endpoint).scheme != "https":
        raise ValueError("Use the HTTPS endpoint from your PixorAds dashboard")
    raw = json.dumps(event, separators=(",", ":"), ensure_ascii=False).encode()
    timestamp = str(int(time.time()))
    signature = hmac.new(
        os.environ["PIXORADS_ADVERTISER_SECRET"].encode(),
        timestamp.encode() + b"." + raw,
        hashlib.sha256,
    ).hexdigest()
    request = Request(
        endpoint,
        data=raw,
        method="POST",
        headers={
            "Content-Type": "application/json",
            "X-PixorAds-Timestamp": timestamp,
            "X-PixorAds-Signature": signature,
        },
    )
    # No redirects: signing credentials must never follow an unexpected Location.
    from urllib.request import HTTPRedirectHandler, build_opener

    class NoRedirect(HTTPRedirectHandler):
        def redirect_request(
            self,
            req: Request,
            fp: IO[bytes],
            code: int,
            msg: str,
            headers: HTTPMessage,
            newurl: str,
        ) -> None:
            return None

    with build_opener(NoRedirect).open(request, timeout=15) as response:
        return json.load(response)
