#!/usr/bin/env python3
"""Morning email: Facebook and Instagram ad leads for the last 7 days.

Runs once a day in GitHub Actions. It reads two numbers from the Meta Marketing
API with a read-only (ads_read) system user token: leads and spend, for the last
7 full days and the 7 days before them. It then emails a short Lithuanian
summary: leads, cost per lead and the change in percent.

Rules this file keeps:
- Only GET requests to graph.facebook.com: the ad account node (time zone and
  currency) and its /insights edge. No writing call, no lead form data.
- The token goes in the Authorization header, never in a printed URL.
- No HTTP redirect is followed, so no request leaves the address it was built for.
- A successful answer is checked too: wrong shape, a missing or bad number or a
  bad currency code stops the run with a fixed error text.
- Nothing prints a secret, a URL, a header or a raw API response. Errors print
  only the HTTP status, the API error code and a short cleaned message.
- Python 3.9+ standard library only. Do not edit this file during install:
  the installer kit tests these exact bytes.

Modes:
  python3 meta_rytas.py --savitikra
      Offline self-test with sample numbers. No network, no secrets.
  python3 meta_rytas.py --rasyk-eiga --sha <40 hex> --zyme <vN.N.N> --siuntimas resend|gmail
          --paskyra <digits> --siuntejas <address> --gavejas <address>
          [--tvarkarastis] [--utc] [--raktas-adrese]
      Writes .github/workflows/meta-rytas.yml next to this file.
  python3 meta_rytas.py --gmail-raktas
      Gmail only. The owner runs it himself in his own terminal. It gets a
      gmail.send refresh token and copies it to the clipboard.
  python3 meta_rytas.py [--raktas-adrese]
      The real run. Works only inside GitHub Actions.
"""
import argparse
import base64
import calendar
import hashlib
import json
import os
import re
import secrets
import subprocess
import sys
import urllib.error
import urllib.parse
import urllib.request
from datetime import date, datetime, timedelta, timezone
from decimal import ROUND_HALF_UP, Decimal
from pathlib import Path

META_BAZE = "https://graph.facebook.com/v26.0"
RESEND_ADRESAS = "https://api.resend.com/emails"
GOOGLE_AUTH = "https://accounts.google.com/o/oauth2/v2/auth"
GOOGLE_RAKTU_ADRESAS = "https://oauth2.googleapis.com/token"
GMAIL_SIUNTIMAS = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
GMAIL_TEISE = "https://www.googleapis.com/auth/gmail.send"
NARSYKLE = "meta-rytas/1.0"
MINUSAS = "−"

TVARKARASTIS_VILNIUS = "17 7 * * *"
TVARKARASTIS_VASARA = "17 4 * * *"
TVARKARASTIS_ZIEMA = "17 5 * * *"


class BePeradresavimo(urllib.request.HTTPRedirectHandler):
    """Never follow a redirect: a token must not travel to another address."""

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


# Tests replace this with a fake opener. Nothing else opens a connection.
# A redirect is not followed; it ends as an HTTP error with safe text only.
ATIDARYK = urllib.request.build_opener(BePeradresavimo()).open


class Klaida(Exception):
    """A failure whose text is safe to print: status, code and a cleaned message."""

    def __init__(self, saltinis, statusas, kodas, zinute):
        super().__init__(saltinis)
        self.saltinis = saltinis
        self.statusas = statusas
        self.kodas = kodas
        self.zinute = zinute

    def eilute(self):
        dalys = [self.saltinis]
        if self.statusas:
            dalys.append(f"HTTP {self.statusas}")
        if self.kodas not in (None, ""):
            dalys.append(f"kodas {self.kodas}")
        tekstas = ", ".join(dalys)
        return tekstas + (f": {self.zinute}" if self.zinute else "")


# ------------------------------------------------------------------ formatting

def grupuok(skaicius):
    """Integer with a normal space every three digits: 1 250."""
    return f"{int(skaicius):,}".replace(",", " ")


def uzklausos(n):
    """Lithuanian plural: 1 užklausa, 2 užklausos, 10 užklausų, 21 užklausa."""
    n = int(n)
    if n % 10 == 1 and n % 100 != 11:
        zodis = "užklausa"
    elif 2 <= n % 10 <= 9 and not 11 <= n % 100 <= 19:
        zodis = "užklausos"
    else:
        zodis = "užklausų"
    return f"{grupuok(n)} {zodis}"


def pinigai(suma, valiuta="EUR"):
    """84 -> „84,00 €“; other currencies keep their code: „84,00 USD“."""
    suma = Decimal(suma).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    sveika, trupmena = f"{abs(suma):.2f}".split(".")
    zenklas = MINUSAS if suma < 0 else ""
    zyme = "€" if valiuta == "EUR" else valiuta
    return f"{zenklas}{grupuok(sveika)},{trupmena} {zyme}"


def pokytis(dabar, anksciau):
    """Change in whole percent: „+20 %“, „−22 %“, „0 %“. None when anksciau is 0."""
    dabar, anksciau = Decimal(dabar), Decimal(anksciau)
    if anksciau == 0:
        return None
    p = ((dabar - anksciau) * 100 / anksciau).quantize(Decimal("1"), rounding=ROUND_HALF_UP)
    if p > 0:
        return f"+{grupuok(p)} %"
    if p < 0:
        return f"{MINUSAS}{grupuok(-p)} %"
    return "0 %"


def sudaryk_laiska(dabar, anksciau, valiuta="EUR"):
    """dabar, anksciau: dicts with nuo, iki (ISO dates), islaidos (Decimal), uzklausos (int).

    Returns (subject, body). Only counts and money, never names or contacts.
    """
    d_n, a_n = int(dabar["uzklausos"]), int(anksciau["uzklausos"])
    d_isl, a_isl = Decimal(dabar["islaidos"]), Decimal(anksciau["islaidos"])
    a_kaina = a_isl / a_n if a_n else None

    p = pokytis(d_n, a_n)
    if p is None:
        eil_uzklausos = f"{uzklausos(d_n)} (anksčiau 0)"
    else:
        eil_uzklausos = f"{uzklausos(d_n)}, {p} (anksčiau {grupuok(a_n)})"

    if d_n:
        d_kaina = d_isl / d_n
        tema = f"Reklama per 7 dienas: {uzklausos(d_n)}, {pinigai(d_kaina, valiuta)} už užklausą"
        if a_kaina is None:
            eil_kaina = f"{pinigai(d_kaina, valiuta)} už užklausą (anksčiau užklausų nebuvo)"
        else:
            pk = pokytis(d_kaina, a_kaina)
            if pk is None:
                eil_kaina = f"{pinigai(d_kaina, valiuta)} už užklausą (anksčiau {pinigai(a_kaina, valiuta)})"
            else:
                eil_kaina = f"{pinigai(d_kaina, valiuta)} už užklausą, {pk} (anksčiau {pinigai(a_kaina, valiuta)})"
    else:
        if d_isl > 0:
            pradzia = f"Išleista {pinigai(d_isl, valiuta)}"
            tema = f"Reklama per 7 dienas: {uzklausos(0)}, išleista {pinigai(d_isl, valiuta)}"
        else:
            pradzia = "Išlaidų nebuvo"
            tema = f"Reklama per 7 dienas: {uzklausos(0)}, išlaidų nebuvo"
        if a_kaina is None:
            eil_kaina = f"{pradzia}, kainos už užklausą nėra"
        else:
            eil_kaina = f"{pradzia}, kainos už užklausą nėra (anksčiau {pinigai(a_kaina, valiuta)} už užklausą)"

    tekstas = (
        f"Facebook ir Instagram reklama nuo {dabar['nuo']} iki {dabar['iki']}:\n"
        "\n"
        f"{eil_uzklausos}\n"
        f"{eil_kaina}\n"
        "\n"
        f"Lyginama su ankstesnėmis 7 dienomis nuo {anksciau['nuo']} iki {anksciau['iki']}.\n"
    )
    return tema, tekstas


def klaidos_laiskas(kodas):
    """Fixed text only. kodas is an int or None; no API message goes into the email."""
    tema = "Reklamos laiškas šiandien nesudarytas"
    if kodas == 190:
        tekstas = (
            "Meta raktas nebegalioja, todėl šiandien skaičių nėra.\n"
            "\n"
            "Reikia susikurti naują Meta raktą, kuris leidžia tik skaityti, ir įklijuoti jį į GitHub Secrets kaip naują META_TOKEN reikšmę.\n"
        )
    elif isinstance(kodas, int):
        tekstas = (
            f"Meta šiandien skaičių nepateikė (klaidos kodas {kodas}).\n"
            "\n"
            "Jei taip bus kelias dienas iš eilės, parodyk šį laišką asistentui.\n"
        )
    else:
        tekstas = (
            "Šiandien skaičių gauti nepavyko.\n"
            "\n"
            "Jei taip bus kelias dienas iš eilės, parodyk šį laišką asistentui.\n"
        )
    return tema, tekstas


# ------------------------------------------------------------------ dates

def langai(siandien):
    """Last 7 full days ending yesterday, and the 7 days before them."""
    iki = siandien - timedelta(days=1)
    dabar = (iki - timedelta(days=6), iki)
    anksciau = (iki - timedelta(days=13), iki - timedelta(days=7))
    return dabar, anksciau


def _paskutinis_sekmadienis(metai, menuo):
    diena = calendar.monthrange(metai, menuo)[1]
    while date(metai, menuo, diena).weekday() != 6:
        diena -= 1
    return diena


def vilniaus_poslinkis(utc_dabar):
    """Hours ahead of UTC in Vilnius: 3 in summer, 2 otherwise (EU rule, 01:00 UTC switch)."""
    m = utc_dabar.year
    pradzia = datetime(m, 3, _paskutinis_sekmadienis(m, 3), 1, tzinfo=timezone.utc)
    pabaiga = datetime(m, 10, _paskutinis_sekmadienis(m, 10), 1, tzinfo=timezone.utc)
    return 3 if pradzia <= utc_dabar < pabaiga else 2


def ar_vykdyti(tvarkarastis, utc_dabar):
    """Two UTC fallback lines fire every day; only the one matching Vilnius time runs."""
    if tvarkarastis == TVARKARASTIS_VASARA:
        return vilniaus_poslinkis(utc_dabar) == 3
    if tvarkarastis == TVARKARASTIS_ZIEMA:
        return vilniaus_poslinkis(utc_dabar) == 2
    return True


def paskyros_juosta(info):
    """Time zone of the ad account: IANA name first, the hour offset if that fails."""
    vardas = info.get("timezone_name") if isinstance(info, dict) else None
    if vardas:
        try:
            from zoneinfo import ZoneInfo
            return ZoneInfo(vardas)
        except Exception:
            pass
    poslinkis = info.get("timezone_offset_hours_utc") if isinstance(info, dict) else None
    if poslinkis is not None:
        try:
            return timezone(timedelta(hours=float(poslinkis)))
        except Exception:
            pass
    raise Klaida("Meta", None, None, "paskyra negrąžino laiko juostos")


# ------------------------------------------------------------------ safe HTTP

def isvalyk(tekstas, slapti=()):
    """Remove secrets and token-like strings, flatten and cut to 200 characters."""
    tekstas = str(tekstas or "")
    for s in slapti:
        if s:
            for forma in {s, urllib.parse.quote(s, safe=""), urllib.parse.quote_plus(s)}:
                tekstas = tekstas.replace(forma, "***")
    tekstas = re.sub(r"(?i)(access_token|refresh_token|client_secret|code)=[^&\s\"']+", r"\1=***", tekstas)
    tekstas = re.sub(r"(?i)bearer\s+\S+", "Bearer ***", tekstas)
    tekstas = re.sub(r"\bEAA[A-Za-z0-9]{8,}", "***", tekstas)
    tekstas = re.sub(r"\bre_[A-Za-z0-9_]{8,}", "***", tekstas)
    tekstas = re.sub(r"\bya29\.[A-Za-z0-9._-]+", "***", tekstas)
    tekstas = re.sub(r"\b1//[A-Za-z0-9._-]+", "***", tekstas)
    tekstas = re.sub(r"https?://\S+", "<adresas>", tekstas)
    tekstas = " ".join(tekstas.split())
    return tekstas[:200]


def _kviesk(uzklausa, saltinis, slapti):
    """Opens the request; returns parsed JSON. Errors become Klaida with safe text only."""
    try:
        atsakymas = ATIDARYK(uzklausa, timeout=30)
    except urllib.error.HTTPError as e:
        kodas, zinute = None, ""
        try:
            kunas = json.loads(e.read(65536).decode("utf-8", "replace"))
            klaida = kunas.get("error") if isinstance(kunas, dict) else None
            if isinstance(klaida, dict):
                kodas = klaida.get("code") if isinstance(klaida.get("code"), (int, str)) else None
                zinute = klaida.get("message", "")
            elif isinstance(klaida, str):
                kodas = klaida
                zinute = kunas.get("error_description", "")
            elif isinstance(kunas, dict):
                kodas = kunas.get("name") if isinstance(kunas.get("name"), str) else None
                zinute = kunas.get("message", "")
        except Exception:
            pass
        raise Klaida(saltinis, e.code, isvalyk(kodas, slapti) if isinstance(kodas, str) else kodas,
                     isvalyk(zinute, slapti)) from None
    except urllib.error.URLError:
        raise Klaida(saltinis, None, None, "nepavyko prisijungti") from None
    try:
        kunas = atsakymas.read(1048576)
    finally:
        atsakymas.close()
    try:
        return json.loads(kunas.decode("utf-8"))
    except Exception:
        raise Klaida(saltinis, None, None, "atsakymas ne JSON") from None


def meta_get(kelias, parametrai, raktas, raktas_adrese=False):
    """GET only, only on graph.facebook.com/v26.0/act_<digits>[/insights]."""
    if not re.fullmatch(r"/act_[0-9]+(/insights)?", kelias):
        raise Klaida("Meta", None, None, "neleistinas kelias")
    parametrai = dict(parametrai)
    antrastes = {"User-Agent": NARSYKLE, "Accept": "application/json"}
    if raktas_adrese:
        parametrai["access_token"] = raktas
    else:
        antrastes["Authorization"] = "Bearer " + raktas
    adresas = META_BAZE + kelias + "?" + urllib.parse.urlencode(parametrai)
    uzklausa = urllib.request.Request(adresas, headers=antrastes, method="GET")
    return _kviesk(uzklausa, "Meta", (raktas,))


def _skaicius(reiksme):
    """Number from a Meta answer. Malformed, infinite, NaN or negative stops the run; the value is not shown."""
    try:
        d = Decimal(str(reiksme))
    except Exception:
        d = None
    if d is None or not d.is_finite() or d < 0:
        raise Klaida("Meta", None, None, "neteisingas skaičius atsakyme")
    return d


def nuskaityk_langa(eilutes, nuo, iki):
    """Spend and the single 'lead' action for one window.

    A missing window row or a missing 'lead' action means 0. A matching row must carry spend.
    """
    for e in eilutes:
        if isinstance(e, dict) and e.get("date_start") == nuo and e.get("date_stop") == iki:
            uzk = 0
            veiksmai = e.get("actions")
            if veiksmai is None:
                veiksmai = []
            elif not isinstance(veiksmai, list):
                raise Klaida("Meta", None, None, "neteisinga atsakymo sandara")
            for veiksmas in veiksmai:
                if isinstance(veiksmas, dict) and veiksmas.get("action_type") == "lead":
                    uzk = int(_skaicius(veiksmas.get("value")).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
                    break
            return {"nuo": nuo, "iki": iki, "islaidos": _skaicius(e.get("spend")),
                    "uzklausos": uzk, "valiuta": e.get("account_currency")}
    return {"nuo": nuo, "iki": iki, "islaidos": Decimal(0), "uzklausos": 0, "valiuta": None}


def gauk_skaicius(paskyra, raktas, utc_dabar, raktas_adrese=False):
    info = meta_get(f"/act_{paskyra}", {"fields": "timezone_name,timezone_offset_hours_utc,currency"},
                    raktas, raktas_adrese)
    siandien = utc_dabar.astimezone(paskyros_juosta(info)).date()
    (d_nuo, d_iki), (a_nuo, a_iki) = langai(siandien)
    d_nuo, d_iki, a_nuo, a_iki = (x.isoformat() for x in (d_nuo, d_iki, a_nuo, a_iki))
    parametrai = {
        "level": "account",
        "fields": "spend,actions,account_currency,date_start,date_stop",
        "time_ranges": json.dumps([{"since": d_nuo, "until": d_iki}, {"since": a_nuo, "until": a_iki}],
                                  separators=(",", ":")),
        "use_unified_attribution_setting": "true",
    }
    ats = meta_get(f"/act_{paskyra}/insights", parametrai, raktas, raktas_adrese)
    if not isinstance(ats, dict) or not isinstance(ats.get("data"), list):
        raise Klaida("Meta", None, None, "neteisinga atsakymo sandara")
    eilutes = ats["data"]
    dabar = nuskaityk_langa(eilutes, d_nuo, d_iki)
    anksciau = nuskaityk_langa(eilutes, a_nuo, a_iki)
    valiuta = dabar["valiuta"] or anksciau["valiuta"] or info.get("currency")
    if not isinstance(valiuta, str) or not re.fullmatch(r"[A-Z]{3}", valiuta):
        raise Klaida("Meta", None, None, "neteisinga valiuta atsakyme")
    return dabar, anksciau, valiuta


# ------------------------------------------------------------------ sending

def siusk_resend(aplinka, tema, tekstas):
    raktas = aplinka["RESEND_API_KEY"]
    kunas = json.dumps({
        "from": aplinka["LAISKO_SIUNTEJAS"],
        "to": [a.strip() for a in aplinka["LAISKO_GAVEJAS"].split(",") if a.strip()],
        "subject": tema,
        "text": tekstas,
    }).encode("utf-8")
    uzklausa = urllib.request.Request(RESEND_ADRESAS, data=kunas, method="POST", headers={
        "Authorization": "Bearer " + raktas, "Content-Type": "application/json", "User-Agent": NARSYKLE})
    _kviesk(uzklausa, "Resend", (raktas,))


def siusk_gmail(aplinka, tema, tekstas):
    # MIMEText with Header: Python 3.9's EmailMessage folding drops a space in the subject.
    from email.header import Header
    from email.mime.text import MIMEText

    slapti = (aplinka["GMAIL_CLIENT_SECRET"], aplinka["GMAIL_REFRESH_TOKEN"], aplinka["GMAIL_CLIENT_ID"])
    forma = urllib.parse.urlencode({
        "client_id": aplinka["GMAIL_CLIENT_ID"],
        "client_secret": aplinka["GMAIL_CLIENT_SECRET"],
        "refresh_token": aplinka["GMAIL_REFRESH_TOKEN"],
        "grant_type": "refresh_token",
    }).encode("ascii")
    uzklausa = urllib.request.Request(GOOGLE_RAKTU_ADRESAS, data=forma, method="POST", headers={
        "Content-Type": "application/x-www-form-urlencoded", "User-Agent": NARSYKLE})
    ats = _kviesk(uzklausa, "Google", slapti)
    zetonas = ats.get("access_token") if isinstance(ats, dict) else None
    if not zetonas:
        raise Klaida("Google", None, None, "negrąžino prieigos rakto")
    # Not printed anywhere, not even as ::add-mask::. Error text loses it in isvalyk().
    laiskas = MIMEText(tekstas, "plain", "utf-8")
    laiskas["To"] = aplinka["LAISKO_GAVEJAS"]
    if aplinka.get("LAISKO_SIUNTEJAS"):
        laiskas["From"] = aplinka["LAISKO_SIUNTEJAS"]
    laiskas["Subject"] = Header(tema, "utf-8")
    zalias = base64.urlsafe_b64encode(laiskas.as_bytes()).decode("ascii")
    uzklausa = urllib.request.Request(GMAIL_SIUNTIMAS, data=json.dumps({"raw": zalias}).encode("utf-8"),
                                      method="POST", headers={
                                          "Authorization": "Bearer " + zetonas,
                                          "Content-Type": "application/json", "User-Agent": NARSYKLE})
    _kviesk(uzklausa, "Gmail", slapti + (zetonas,))


def siusk(aplinka, tema, tekstas):
    if aplinka.get("LAISKO_SIUNTIMAS") == "gmail":
        siusk_gmail(aplinka, tema, tekstas)
    else:
        siusk_resend(aplinka, tema, tekstas)


def reikalingi(aplinka):
    vardai = ["META_TOKEN", "META_PASKYROS_ID", "LAISKO_SIUNTIMAS", "LAISKO_GAVEJAS"]
    if aplinka.get("LAISKO_SIUNTIMAS") == "gmail":
        vardai += ["GMAIL_CLIENT_ID", "GMAIL_CLIENT_SECRET", "GMAIL_REFRESH_TOKEN"]
    else:
        vardai += ["RESEND_API_KEY", "LAISKO_SIUNTEJAS"]
    return vardai


def paleisk(aplinka=None, utc_dabar=None, raktas_adrese=False):
    """The real run. Returns the exit code."""
    aplinka = os.environ if aplinka is None else aplinka
    if aplinka.get("GITHUB_ACTIONS") != "true":
        print("Ši programa siunčia laišką tik GitHub Actions serveryje. Patikrai paleisk ją su --savitikra.")
        return 2
    utc_dabar = utc_dabar or datetime.now(timezone.utc)
    if not ar_vykdyti(aplinka.get("SCHEDULE", ""), utc_dabar):
        print("Šis paleidimas praleistas: jis skirtas kitam sezonui. Laiškas eina kitu paleidimu.")
        return 0
    if aplinka.get("LAISKO_SIUNTIMAS") not in ("resend", "gmail"):
        print("LAISKO_SIUNTIMAS turi būti resend arba gmail.")
        return 2
    truksta = [v for v in reikalingi(aplinka) if not aplinka.get(v)]
    if truksta:
        print("Trūksta nustatymų: " + ", ".join(truksta))
        return 2
    paskyra = aplinka["META_PASKYROS_ID"].strip()
    paskyra = paskyra[4:] if paskyra.startswith("act_") else paskyra
    if not paskyra.isdigit():
        print("META_PASKYROS_ID turi būti vien skaitmenys.")
        return 2
    try:
        dabar, anksciau, valiuta = gauk_skaicius(paskyra, aplinka["META_TOKEN"], utc_dabar, raktas_adrese)
    except Exception as e:
        if isinstance(e, Klaida):
            print(e.eilute())
            kodas = e.kodas if isinstance(e.kodas, int) else None
        else:
            print("Netikėta klaida: " + type(e).__name__)
            kodas = None
        try:
            siusk(aplinka, *klaidos_laiskas(kodas))
            print("Išsiųstas laiškas apie klaidą.")
        except Exception as e2:
            print(e2.eilute() if isinstance(e2, Klaida) else "Netikėta klaida: " + type(e2).__name__)
        return 1
    tema, tekstas = sudaryk_laiska(dabar, anksciau, valiuta)
    try:
        siusk(aplinka, tema, tekstas)
    except Exception as e:
        print(e.eilute() if isinstance(e, Klaida) else "Netikėta klaida: " + type(e).__name__)
        return 1
    print(f"Laiškas išsiųstas, laikotarpis nuo {dabar['nuo']} iki {dabar['iki']}.")
    return 0


# ------------------------------------------------------------------ workflow file

ADRESO_RX = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}")


def eigos_tekstas(sha, zyme, siuntimas, paskyra, siuntejas, gavejas,
                  tvarkarastis=False, utc=False, raktas_adrese=False):
    """Text of .github/workflows/meta-rytas.yml. Every value is checked before use."""
    if not re.fullmatch(r"[0-9a-f]{40}", sha or ""):
        raise ValueError("--sha turi būti 40 ženklų commit SHA")
    if not re.fullmatch(r"v[0-9]+(\.[0-9]+){0,2}", zyme or ""):
        raise ValueError("--zyme turi būti versija, pavyzdžiui, v4.2.2")
    if siuntimas not in ("resend", "gmail"):
        raise ValueError("--siuntimas turi būti resend arba gmail")
    paskyra = (paskyra or "").strip()
    paskyra = paskyra[4:] if paskyra.startswith("act_") else paskyra
    if not paskyra.isdigit():
        raise ValueError("--paskyra turi būti vien skaitmenys")
    gavejai = [a.strip() for a in (gavejas or "").split(",") if a.strip()]
    if not gavejai or not all(ADRESO_RX.fullmatch(a) for a in gavejai):
        raise ValueError("--gavejas turi būti el. pašto adresas")
    if not ADRESO_RX.fullmatch(siuntejas or ""):
        raise ValueError("--siuntejas turi būti el. pašto adresas")
    if utc and not tvarkarastis:
        raise ValueError("--utc galima tik kartu su --tvarkarastis")

    eil = ["name: Meta rytinis laiškas", "on:"]
    if tvarkarastis:
        eil.append("  schedule:")
        if utc:
            eil += [f"    - cron: '{TVARKARASTIS_VASARA}'",
                    f"    - cron: '{TVARKARASTIS_ZIEMA}'"]
        else:
            eil += [f"    - cron: '{TVARKARASTIS_VILNIUS}'",
                    "      timezone: \"Europe/Vilnius\""]
    eil += [
        "  workflow_dispatch:",
        "permissions:",
        "  contents: read",
        "jobs:",
        "  laiskas:",
        "    runs-on: ubuntu-latest",
        "    timeout-minutes: 5",
        "    steps:",
        f"      - uses: actions/checkout@{sha} # {zyme}",
        "        with:",
        "          persist-credentials: false",
        "      - name: Sudaryti ir išsiųsti laišką",
        "        run: python3 meta_rytas.py" + (" --raktas-adrese" if raktas_adrese else ""),
        "        env:",
        "          META_TOKEN: ${{ secrets.META_TOKEN }}",
    ]
    if siuntimas == "gmail":
        eil += [
            "          GMAIL_CLIENT_ID: ${{ secrets.GMAIL_CLIENT_ID }}",
            "          GMAIL_CLIENT_SECRET: ${{ secrets.GMAIL_CLIENT_SECRET }}",
            "          GMAIL_REFRESH_TOKEN: ${{ secrets.GMAIL_REFRESH_TOKEN }}",
        ]
    else:
        eil.append("          RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}")
    eil += [
        f"          META_PASKYROS_ID: \"{paskyra}\"",
        f"          LAISKO_SIUNTIMAS: \"{siuntimas}\"",
        f"          LAISKO_SIUNTEJAS: \"{siuntejas}\"",
        f"          LAISKO_GAVEJAS: \"{', '.join(gavejai)}\"",
        "          SCHEDULE: ${{ github.event.schedule }}",
    ]
    return "\n".join(eil) + "\n"


def rasyk_eiga(arg):
    cia = Path(__file__).resolve()
    if cia.name != "meta_rytas.py":
        raise ValueError("failas turi vadintis meta_rytas.py, nes eiga paleidžia būtent jį")
    tekstas = eigos_tekstas(arg.sha, arg.zyme, arg.siuntimas, arg.paskyra, arg.siuntejas, arg.gavejas,
                            arg.tvarkarastis, arg.utc, arg.raktas_adrese)
    tikslas = cia.parent / ".github" / "workflows" / "meta-rytas.yml"
    tikslas.parent.mkdir(parents=True, exist_ok=True)
    with open(tikslas, "w", encoding="utf-8", newline="\n") as f:
        f.write(tekstas)
    print("Įrašyta: .github/workflows/meta-rytas.yml, " + ("su tvarkaraščiu" if arg.tvarkarastis else "be tvarkaraščio"))
    return 0


# ------------------------------------------------------------------ Gmail key (owner's own terminal)

def gmail_nuoroda(kliento_id, grizimas, issukis, busena):
    return GOOGLE_AUTH + "?" + urllib.parse.urlencode({
        "client_id": kliento_id,
        "redirect_uri": grizimas,
        "response_type": "code",
        "scope": GMAIL_TEISE,
        "access_type": "offline",
        "prompt": "consent",
        "code_challenge": issukis,
        "code_challenge_method": "S256",
        "state": busena,
    })


def pkce():
    tikrintojas = secrets.token_urlsafe(64)
    issukis = base64.urlsafe_b64encode(hashlib.sha256(tikrintojas.encode("ascii")).digest()).decode("ascii").rstrip("=")
    return tikrintojas, issukis


def kopijuok(tekstas):
    if sys.platform == "darwin":
        komandos = (("pbcopy",),)
    elif sys.platform.startswith("win"):
        komandos = (("clip",),)
    else:
        komandos = (("wl-copy",), ("xclip", "-selection", "clipboard"))
    for k in komandos:
        try:
            subprocess.run(list(k), input=tekstas.encode("ascii"), check=True,
                           stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            return True
        except Exception:
            continue
    return False


def gmail_tvarkytojas(busena, rezultatas):
    from http.server import BaseHTTPRequestHandler

    class Tvarkytojas(BaseHTTPRequestHandler):
        def do_GET(self):
            q = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
            if q.get("state", [""])[0] != busena:
                rezultatas["klaida"] = "netinka state"
            elif "code" in q:
                rezultatas["kodas"] = q["code"][0]
            else:
                rezultatas["klaida"] = "Google negrąžino kodo"
            self.send_response(200)
            self.send_header("Content-Type", "text/plain; charset=utf-8")
            self.end_headers()
            self.wfile.write("Galima uždaryti šį langą ir grįžti į terminalą.".encode("utf-8"))

        def log_message(self, *args):
            # The default handler prints the request line, which holds the code.
            return

    return Tvarkytojas


def gmail_raktas():
    if not (sys.stdin.isatty() and sys.stdout.isatty()):
        print("Šią komandą paleisk pats savo terminale, ne per asistentą: raktas neturi patekti į pokalbį.")
        return 2
    import getpass
    import webbrowser
    from http.server import HTTPServer

    kliento_id = input("Google OAuth kliento ID (Client ID): ").strip()
    kliento_slaptas = getpass.getpass("Kliento slaptas raktas (Client secret, ekrane nerodomas): ").strip()
    if not kliento_id or not kliento_slaptas:
        print("Reikia abiejų reikšmių.")
        return 2
    busena = secrets.token_urlsafe(24)
    tikrintojas, issukis = pkce()
    rezultatas = {}
    serveris = HTTPServer(("127.0.0.1", 0), gmail_tvarkytojas(busena, rezultatas))
    serveris.timeout = 300
    grizimas = f"http://127.0.0.1:{serveris.server_address[1]}"
    nuoroda = gmail_nuoroda(kliento_id, grizimas, issukis, busena)
    print("Atidarau naršyklę. Prisijunk ta Google paskyra, iš kurios eis laiškai, ir leisk tik siųsti laiškus.")
    print("Jei naršyklė neatsidarė, atidaryk šį adresą pats:")
    print(nuoroda)
    webbrowser.open(nuoroda)
    serveris.handle_request()
    serveris.server_close()
    if "kodas" not in rezultatas:
        print("Nepavyko: " + rezultatas.get("klaida", "per 5 minutes atsakymo nebuvo"))
        return 1
    forma = urllib.parse.urlencode({
        "code": rezultatas["kodas"], "client_id": kliento_id, "client_secret": kliento_slaptas,
        "redirect_uri": grizimas, "grant_type": "authorization_code", "code_verifier": tikrintojas,
    }).encode("ascii")
    uzklausa = urllib.request.Request(GOOGLE_RAKTU_ADRESAS, data=forma, method="POST", headers={
        "Content-Type": "application/x-www-form-urlencoded", "User-Agent": NARSYKLE})
    try:
        ats = _kviesk(uzklausa, "Google", (kliento_slaptas, rezultatas["kodas"]))
    except Klaida as e:
        print(e.eilute())
        return 1
    raktas = ats.get("refresh_token") if isinstance(ats, dict) else None
    if not raktas:
        print("Google negrąžino ilgalaikio rakto. Google paskyros nustatymuose pašalink šios programos prieigą ir bandyk dar kartą.")
        return 1
    if kopijuok(raktas):
        print("Ilgalaikis Gmail raktas nukopijuotas. Įklijuok jį tik į GitHub Secrets kaip GMAIL_REFRESH_TOKEN.")
        print("Į pokalbį su asistentu jo neklijuok.")
    else:
        print("Nukopijuoti nepavyko. Rakto nerodau. Pasakyk asistentui, kad kopijuoti nepavyko.")
        return 1
    return 0


# ------------------------------------------------------------------ self-test

def savitikra():
    global ATIDARYK

    def draudziama(*a, **k):
        raise RuntimeError("savitikra tinklo nenaudoja")

    buves, ATIDARYK = ATIDARYK, draudziama
    try:
        return _savitikra()
    finally:
        ATIDARYK = buves


def _savitikra():
    klaidos = []

    def tikrink(aprasas, gauta, tiketasi):
        if gauta != tiketasi:
            klaidos.append(f"{aprasas}: gauta {gauta!r}, tikėtasi {tiketasi!r}")

    for n, t in ((0, "0 užklausų"), (1, "1 užklausa"), (2, "2 užklausos"), (10, "10 užklausų"),
                 (11, "11 užklausų"), (21, "21 užklausa"), (22, "22 užklausos"), (111, "111 užklausų"),
                 (1250, "1 250 užklausų")):
        tikrink(f"daugiskaita {n}", uzklausos(n), t)
    tikrink("pinigai", pinigai(Decimal("7")), "7,00 €")
    tikrink("pokytis +", pokytis(12, 10), "+20 %")
    tikrink("pokytis −", pokytis(Decimal(7), Decimal(9)), MINUSAS + "22 %")
    tikrink("pokytis be pagrindo", pokytis(5, 0), None)

    (d_nuo, d_iki), (a_nuo, a_iki) = langai(date(2026, 10, 27))
    tikrink("langai", (d_nuo.isoformat(), d_iki.isoformat(), a_nuo.isoformat(), a_iki.isoformat()),
            ("2026-10-20", "2026-10-26", "2026-10-13", "2026-10-19"))
    dabar = {"nuo": "2026-10-20", "iki": "2026-10-26", "islaidos": Decimal("84"), "uzklausos": 12}
    anksciau = {"nuo": "2026-10-13", "iki": "2026-10-19", "islaidos": Decimal("90"), "uzklausos": 10}
    tema, tekstas = sudaryk_laiska(dabar, anksciau)
    for dalis in ("12 užklausų", "7,00 € už užklausą", "+20 %", MINUSAS + "22 %"):
        tikrink(f"laiške yra „{dalis}“", dalis in tekstas, True)
    nulis = dict(anksciau, islaidos=Decimal("0"), uzklausos=0)
    _, t0 = sudaryk_laiska(dabar, nulis)
    tikrink("anksčiau 0: yra „anksčiau 0“", "anksčiau 0" in t0, True)
    tikrink("anksčiau 0: nėra procento", "%" in t0, False)
    _, t1 = sudaryk_laiska(dict(dabar, uzklausos=0), anksciau)
    tikrink("dabar 0: kainos nėra", "kainos už užklausą nėra" in t1, True)
    _, t2 = sudaryk_laiska(dict(dabar, uzklausos=0, islaidos=Decimal("0")), nulis)
    tikrink("abu 0: be procento", "%" in t2, False)
    vasara = datetime(2026, 7, 1, 4, 17, tzinfo=timezone.utc)
    ziema = datetime(2026, 12, 1, 5, 17, tzinfo=timezone.utc)
    tikrink("vasaros eilutė vasarą", ar_vykdyti(TVARKARASTIS_VASARA, vasara), True)
    tikrink("žiemos eilutė vasarą", ar_vykdyti(TVARKARASTIS_ZIEMA, vasara), False)
    tikrink("žiemos eilutė žiemą", ar_vykdyti(TVARKARASTIS_ZIEMA, ziema), True)
    tikrink("vasaros eilutė žiemą", ar_vykdyti(TVARKARASTIS_VASARA, ziema), False)

    print("Pavyzdinis laiškas (bandomieji skaičiai, ne tikri):")
    print("Tema: " + tema)
    print()
    print(tekstas)
    if klaidos:
        for k in klaidos:
            print("KLAIDA: " + k)
        print(f"Savitikra: rasta klaidų {len(klaidos)}")
        return 1
    print("Savitikra: tvarkoje")
    return 0


# ------------------------------------------------------------------ entry point

def _utf8_isvestis():
    for srautas in (sys.stdout, sys.stderr):
        try:
            "ų€−".encode(srautas.encoding or "ascii")
        except (UnicodeEncodeError, LookupError):
            try:
                srautas.reconfigure(encoding="utf-8", errors="replace")
            except Exception:
                pass


def main(argv=None):
    _utf8_isvestis()
    p = argparse.ArgumentParser(description="Rytinis laiškas apie Facebook ir Instagram reklamos užklausas.")
    rezimas = p.add_mutually_exclusive_group()
    rezimas.add_argument("--savitikra", action="store_true")
    rezimas.add_argument("--rasyk-eiga", action="store_true")
    rezimas.add_argument("--gmail-raktas", action="store_true")
    p.add_argument("--sha")
    p.add_argument("--zyme")
    p.add_argument("--siuntimas", choices=("resend", "gmail"))
    p.add_argument("--paskyra")
    p.add_argument("--siuntejas")
    p.add_argument("--gavejas")
    p.add_argument("--tvarkarastis", action="store_true")
    p.add_argument("--utc", action="store_true")
    p.add_argument("--raktas-adrese", action="store_true")
    arg = p.parse_args(argv)
    if arg.savitikra:
        return savitikra()
    if arg.gmail_raktas:
        try:
            return gmail_raktas()
        except Exception as e:
            print("Netikėta klaida: " + type(e).__name__)
            return 1
    if arg.rasyk_eiga:
        try:
            return rasyk_eiga(arg)
        except ValueError as e:
            print(str(e))
            return 2
    try:
        return paleisk(raktas_adrese=arg.raktas_adrese)
    except Exception as e:
        print("Netikėta klaida: " + type(e).__name__)
        return 1


if __name__ == "__main__":
    sys.exit(main())
