#!/usr/bin/env python3
"""Long-poll a bot; publish attachments, then a letter, then its cursor."""
import json
import signal
import sys
import threading
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
from common import call, download, message_id, state
from brr.channels.telegram import parse_update
from brr.loom.runtime.home import atomic_write
from brr.loom.runtime.port import _front


def receive(drop, update):
    parsed = parse_update(update)
    if parsed is None or parsed.is_edit:
        return
    msg = update['message']
    ident = message_id(parsed.chat_id, parsed.message_id)
    if (drop / f'{ident}.md').exists():
        return
    pointers = []
    if msg.get('photo'):
        pointers.append((msg['photo'][-1]['file_id'], '.jpg'))
    for kind in ('document', 'audio', 'voice', 'video', 'video_note', 'animation', 'sticker'):
        media = msg.get(kind)
        if isinstance(media, dict) and media.get('file_id'):
            suffix = Path(media.get('file_name', '')).suffix
            pointers.append((media['file_id'], suffix if suffix.lstrip('.').isalnum() else ''))
    missing = []
    for index, (file_id, suffix) in enumerate(pointers):
        dest = drop / f'{ident}.d' / f'{index}{suffix}'
        if dest.exists():
            continue
        for attempt in range(3):
            try:
                atomic_write(dest, download(file_id))
                break
            except Exception:
                if attempt == 2:
                    missing.append(str(index))
    fields = dict(kind='letter', id=ident, to=parsed.chat_id)
    if parsed.user_id is not None:
        fields['from'] = str(parsed.user_id)
    if parsed.reply_to_message_id is not None:
        fields['re'] = message_id(parsed.chat_id, parsed.reply_to_message_id)
    if missing:
        fields['missing'] = ' '.join(missing)
    atomic_write(drop / f'{ident}.md', _front(fields, parsed.text))


def poll(drop, stop):
    cursor = state() / 'cursor'
    offset = int(cursor.read_text()) if cursor.exists() else 0
    delay = 1
    while not stop.is_set():
        try:
            for update in call('getUpdates', dict(offset=offset, timeout=25,
                                                   allowed_updates=['message'])):
                receive(drop, update)
                offset = update['update_id'] + 1
                atomic_write(cursor, str(offset))
            delay = 1
        except Exception as exc:
            print(f'telegram: {type(exc).__name__}; retry in {delay}s', file=sys.stderr)
            stop.wait(delay)
            delay = min(60, delay * 2)


if __name__ == '__main__':
    stop = threading.Event()
    signal.signal(signal.SIGTERM, lambda *_: stop.set())
    signal.signal(signal.SIGINT, lambda *_: stop.set())
    drop = Path(sys.argv[1])
    drop.mkdir(parents=True, exist_ok=True)
    poll(drop, stop)
