#!/usr/bin/env python3

import datetime as dt
import os
import time
from dataclasses import dataclass

import requests
from prometheus_client import Gauge, Info, start_http_server


@dataclass(frozen=True)
class Window:
    label: str
    days: int


WINDOWS = [
    Window("today", 0),
    Window("1d", 1),
    Window("7d", 7),
    Window("30d", 30),
]

API_BASE = os.environ.get("POSTMARK_API_BASE", "https://api.postmarkapp.com").rstrip("/")
POLL_INTERVAL_SECONDS = int(os.environ.get("POLL_INTERVAL_SECONDS", "60"))
LISTEN_ADDRESS = os.environ.get("LISTEN_ADDRESS", "0.0.0.0")
LISTEN_PORT = int(os.environ.get("LISTEN_PORT", "8000"))

PRIMARY_TOKEN = os.environ.get("POSTMARK_SERVER_TOKEN", "").strip()
FALLBACK_TOKEN = os.environ.get("POSTMARK_SERVER_TOKEN_FALLBACK", "").strip()
LIMIT_WINDOW = os.environ.get("POSTMARK_SENDING_LIMIT_WINDOW", "30d").strip()
LIMIT_RAW = os.environ.get("POSTMARK_SENDING_LIMIT", "").strip()
try:
    SENDING_LIMIT = float(LIMIT_RAW) if LIMIT_RAW else 0.0
except ValueError:
    SENDING_LIMIT = 0.0

EXPORTER_INFO = Info("postmark_exporter", "Exporter build info")
EXPORTER_INFO.info(
    {
        "api_base": API_BASE,
        "windows": ",".join(window.label for window in WINDOWS),
    }
)

POSTMARK_API_UP = Gauge("postmark_api_up", "Whether Postmark API is reachable (1) or not (0)")
POSTMARK_LAST_SUCCESS = Gauge(
    "postmark_last_success_timestamp_seconds",
    "Unix timestamp of the last successful Postmark stats refresh",
)
POSTMARK_REQUEST_ERRORS = Gauge(
    "postmark_request_errors_total",
    "Total Postmark stats request errors since exporter start",
)

POSTMARK_OUTBOUND_SENT = Gauge(
    "postmark_outbound_sent",
    "Outbound emails sent within the selected window",
    labelnames=("window",),
)
POSTMARK_OUTBOUND_BOUNCED = Gauge(
    "postmark_outbound_bounced",
    "Outbound emails bounced within the selected window",
    labelnames=("window",),
)
POSTMARK_OUTBOUND_BOUNCE_RATE = Gauge(
    "postmark_outbound_bounce_rate",
    "Outbound bounce rate percentage within the selected window",
    labelnames=("window",),
)
POSTMARK_SENDING_LIMIT_GAUGE = Gauge(
    "postmark_sending_limit",
    "Configured Postmark sending limit for the active account",
)
POSTMARK_SENDING_LIMIT_USED = Gauge(
    "postmark_sending_limit_used",
    "Messages sent within the configured send limit window",
)
POSTMARK_SENDING_LIMIT_USED_PERCENT = Gauge(
    "postmark_sending_limit_used_percent",
    "Percent of the configured send limit used within the limit window",
)


def fetch_outbound_stats(token: str, window: Window) -> dict:
    today = dt.date.today()
    fromdate = today - dt.timedelta(days=window.days)
    params = {"fromdate": fromdate.isoformat(), "todate": today.isoformat()}
    headers = {
        "Accept": "application/json",
        "X-Postmark-Server-Token": token,
    }
    response = requests.get(
        f"{API_BASE}/stats/outbound",
        headers=headers,
        params=params,
        timeout=15,
    )
    response.raise_for_status()
    return response.json()


def update_metrics(token: str) -> None:
    sent_by_window = {}
    for window in WINDOWS:
        data = fetch_outbound_stats(token, window)
        sent = int(data.get("Sent", 0) or 0)
        bounced = int(data.get("Bounced", 0) or 0)
        rate = (bounced / sent * 100.0) if sent else 0.0
        sent_by_window[window.label] = sent
        POSTMARK_OUTBOUND_SENT.labels(window=window.label).set(sent)
        POSTMARK_OUTBOUND_BOUNCED.labels(window=window.label).set(bounced)
        POSTMARK_OUTBOUND_BOUNCE_RATE.labels(window=window.label).set(rate)

    POSTMARK_SENDING_LIMIT_GAUGE.set(SENDING_LIMIT)
    limit_window_sent = sent_by_window.get(LIMIT_WINDOW, 0)
    POSTMARK_SENDING_LIMIT_USED.set(limit_window_sent)
    if SENDING_LIMIT:
        POSTMARK_SENDING_LIMIT_USED_PERCENT.set(limit_window_sent / SENDING_LIMIT * 100.0)
    else:
        POSTMARK_SENDING_LIMIT_USED_PERCENT.set(0.0)


def main() -> None:
    if not PRIMARY_TOKEN and not FALLBACK_TOKEN:
        raise SystemExit("POSTMARK_SERVER_TOKEN or POSTMARK_SERVER_TOKEN_FALLBACK is required")

    start_http_server(LISTEN_PORT, addr=LISTEN_ADDRESS)

    tokens = [token for token in (PRIMARY_TOKEN, FALLBACK_TOKEN) if token]
    token_index = 0

    while True:
        token = tokens[token_index % len(tokens)]
        token_index += 1
        try:
            update_metrics(token)
            POSTMARK_API_UP.set(1)
            POSTMARK_LAST_SUCCESS.set(time.time())
        except Exception as exc:  # noqa: BLE001
            POSTMARK_API_UP.set(0)
            POSTMARK_REQUEST_ERRORS.inc()
            print(f"postmark_exporter: refresh failed: {exc}", flush=True)
        time.sleep(POLL_INTERVAL_SECONDS)


if __name__ == "__main__":
    main()
