#!/usr/bin/env python3
"""Ghala Developer API (v2) - runnable send example.

    pip install requests python-dotenv
    python send_message.py
    python send_message.py templates
    python send_message.py template hello_world en_US
    python send_message.py buttons

There is no sandbox: every send reaches a real handset. Send to your own number
first, and message the Ghala number from that handset beforehand so the 24-hour
window is open.
"""

from __future__ import annotations

import os
import random
import sys
import time

import requests

try:
    from dotenv import load_dotenv

    load_dotenv()
except ImportError:  # python-dotenv is optional; env vars work just as well
    pass

BASE_URL = os.environ.get("GHALA_BASE_URL", "https://v2.ghala.io/api/v2")
ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN")
TO = os.environ.get("TO")


class GhalaError(Exception):
    def __init__(self, message: str, status: int, code: str | None = None):
        super().__init__(message)
        self.status = status
        self.code = code


def request(path: str, method: str = "GET", body: dict | None = None,
            idempotency_key: str | None = None) -> tuple[dict, bool]:
    """Returns (payload, replayed). Retries only what is safe to retry."""
    headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key

    for attempt in range(5):
        resp = requests.request(
            method, f"{BASE_URL}{path}", headers=headers, json=body, timeout=30
        )

        if resp.ok:
            replayed = resp.headers.get("Idempotency-Replayed") == "true"
            return resp.json(), replayed

        error: dict = {}
        try:
            error = resp.json()
        except ValueError:
            pass

        # 429 and 5xx are transient, and so is a first attempt still in flight.
        retryable = (
            resp.status_code == 429
            or resp.status_code >= 500
            or error.get("code") == "idempotency_in_progress"
        )
        if not retryable or attempt == 4:
            detail = error.get("detail")
            message = error.get("message") or (
                detail[0]["msg"] if isinstance(detail, list) and detail else resp.reason
            )
            raise GhalaError(message, resp.status_code, error.get("code"))

        backoff = min(2**attempt * 0.5, 8.0)
        time.sleep(backoff + random.random() * 0.25)  # jitter, so retries do not sync

    raise GhalaError("exhausted retries", 0, None)


def list_templates(sendable_only: bool = True) -> list[dict]:
    """Proves the token works without messaging anybody."""
    query = "?sendable=true&limit=25" if sendable_only else "?limit=25"
    data, _ = request(f"/templates{query}")
    return data["items"]


def send(message: dict, idempotency_key: str | None = None) -> tuple[dict, bool]:
    return request("/messages", method="POST", body=message,
                   idempotency_key=idempotency_key)


def send_text_or_template(to: str, text: str, idempotency_key: str | None = None):
    """Free-form text, falling back to a template when the window has shut."""
    try:
        return send({"to": to, "type": "text", "text": text}, idempotency_key)
    except GhalaError as error:
        if error.code != "outside_messaging_window":
            raise

        print("24-hour window is shut; falling back to a template.", file=sys.stderr)
        templates = list_templates()
        if not templates:
            raise

        template = templates[0]
        return send(
            {
                "to": to,
                "type": "template",
                "template_name": template["name"],
                "template_language": template["language"],
            },
            f"{idempotency_key}-template" if idempotency_key else None,
        )


def example_templates() -> None:
    items = list_templates(sendable_only=False)
    if not items:
        print("No templates on this number yet. Create one in the dashboard.")
        return
    for t in items:
        mark = "sendable" if t["sendable"] else f"blocked: {t.get('unsendable_reason')}"
        print(
            f"{t['name']} ({t['language']})  {t['status']}  "
            f"quality={t.get('quality_score') or '-'}  {mark}"
        )


def example_text() -> None:
    data, replayed = send_text_or_template(
        TO, "Habari! Hii ni ujumbe wa majaribio.", "example-first-message"
    )
    print("replayed (nothing sent)" if replayed else "sent", data)


def example_template(name: str | None = None, language: str | None = None) -> None:
    if not name:
        templates = list_templates()
        if not templates:
            raise SystemExit("No sendable template on this number.")
        name, language = templates[0]["name"], templates[0]["language"]

    data, _ = send(
        {
            "to": TO,
            "type": "template",
            "template_name": name,
            "template_language": language,
        },
        f"example-template-{name}-{language}",
    )
    print("sent", data)


def example_image(url: str = "https://example.com/product.png") -> None:
    data, _ = send(
        {
            "to": TO,
            "type": "image",
            "media_url": url,
            "media_caption": "Bidhaa yetu mpya",
        },
        "example-image",
    )
    print("sent", data)


def example_buttons() -> None:
    data, _ = send(
        {
            "to": TO,
            "type": "interactive",
            "interactive": {
                "type": "buttons",
                "body": "Oda yako #1042 iko tayari. Ungependa vipi kuipata?",
                "footer": "Duka la Amina",
                "buttons": [
                    {"id": "delivery", "title": "Niletewe"},
                    {"id": "pickup", "title": "Nitakuja kuchukua"},
                ],
            },
        },
        "example-buttons",
    )
    print("sent", data)


def example_list() -> None:
    data, _ = send(
        {
            "to": TO,
            "type": "interactive",
            "interactive": {
                "type": "list",
                "body": "Chagua bidhaa",
                "button_text": "Ona bidhaa",
                "sections": [
                    {
                        "title": "Vinywaji",
                        "rows": [
                            {"id": "sku-101", "title": "Chai", "description": "TZS 1,500"},
                            {"id": "sku-102", "title": "Kahawa", "description": "TZS 2,000"},
                        ],
                    }
                ],
            },
        },
        "example-list",
    )
    print("sent", data)


EXAMPLES = {
    "templates": example_templates,
    "text": example_text,
    "template": example_template,
    "image": example_image,
    "buttons": example_buttons,
    "list": example_list,
}


def main() -> None:
    if not ACCESS_TOKEN:
        raise SystemExit("ACCESS_TOKEN is not set. Copy env.example to .env.")

    name, *args = sys.argv[1:] or ["text"]
    example = EXAMPLES.get(name)
    if example is None:
        raise SystemExit(f"Unknown example: {name}. Available: {', '.join(EXAMPLES)}")

    if name != "templates" and not TO:
        raise SystemExit("TO is not set. Put your own number in .env, no leading +.")

    try:
        example(*args)
    except GhalaError as error:
        print(f"\n{error.status} {error.code or ''} - {error}", file=sys.stderr)
        if error.status == 401:
            print("Reconnecting a number invalidates its old token. Re-copy it.",
                  file=sys.stderr)
        if error.status == 402:
            print("This team's plan does not include API access.", file=sys.stderr)
        if error.code == "outside_messaging_window":
            print("Message the Ghala number from your handset, then retry.",
                  file=sys.stderr)
        raise SystemExit(1)


if __name__ == "__main__":
    main()
