#!/usr/bin/env python3
import http.client
import hashlib
import json
import mimetypes
import secrets
import ssl
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import urlparse

PROJECT = Path(__file__).resolve().parents[1]
CONFIG = Path.home() / '.config/huyen-an-audio/postiz.env'
RECEIPT = PROJECT / 'log/postiz-media.json'
MEDIA = [('video', PROJECT / 'output/final-upload.mp4'), ('thumbnail', PROJECT / 'image/intro-poster-1920x1080-r2.png')]


def sha(path):
    digest = hashlib.sha256()
    with path.open('rb') as handle:
        for block in iter(lambda: handle.read(8 * 1024 * 1024), b''):
            digest.update(block)
    return digest.hexdigest()


def load_env():
    values = {}
    for raw in CONFIG.read_text(encoding='utf-8').splitlines():
        line = raw.strip()
        if not line or line.startswith('#') or '=' not in line:
            continue
        key, value = line.split('=', 1)
        values[key.strip()] = value.strip().strip('"').strip("'")
    return values


def upload(base, api_key, path):
    parsed = urlparse(base)
    if parsed.scheme not in {'http', 'https'}:
        raise RuntimeError('Unsupported Postiz scheme')
    boundary = '----HermesPostiz' + secrets.token_hex(16)
    mime = mimetypes.guess_type(path.name)[0] or 'application/octet-stream'
    prefix = (
        f'--{boundary}\r\n'
        f'Content-Disposition: form-data; name="file"; filename="{path.name}"\r\n'
        f'Content-Type: {mime}\r\n\r\n'
    ).encode('utf-8')
    suffix = f'\r\n--{boundary}--\r\n'.encode('ascii')
    length = len(prefix) + path.stat().st_size + len(suffix)
    host = parsed.hostname
    port = parsed.port
    connection = (
        http.client.HTTPSConnection(host, port, timeout=7200, context=ssl.create_default_context())
        if parsed.scheme == 'https'
        else http.client.HTTPConnection(host, port, timeout=7200)
    )
    base_path = parsed.path.rstrip('/')
    target = base_path + '/upload'
    connection.putrequest('POST', target)
    connection.putheader('Authorization', api_key)
    connection.putheader('Content-Type', f'multipart/form-data; boundary={boundary}')
    connection.putheader('Content-Length', str(length))
    connection.endheaders()
    connection.send(prefix)
    sent = 0
    with path.open('rb') as handle:
        while True:
            block = handle.read(8 * 1024 * 1024)
            if not block:
                break
            connection.send(block)
            sent += len(block)
            if sent and sent % (512 * 1024 * 1024) < 8 * 1024 * 1024:
                print(json.dumps({'asset': path.name, 'sent_bytes': sent, 'total_bytes': path.stat().st_size}), flush=True)
    connection.send(suffix)
    response = connection.getresponse()
    raw = response.read()
    status = response.status
    connection.close()
    if not 200 <= status < 300:
        body = raw.decode(errors='replace')[:1000]
        raise RuntimeError(f'Postiz upload HTTP {status}: {body}')
    data = json.loads(raw.decode('utf-8'))
    item = data.get('media') or data.get('data') or data
    if isinstance(item, list):
        item = item[0] if item else None
    if not isinstance(item, dict) or not item.get('id') or not item.get('path'):
        raise RuntimeError('Postiz upload response missing media id/path')
    return {'id': item['id'], 'path': item['path'], 'name': path.name, 'bytes': path.stat().st_size}


def save(canon, media, verified=False):
    data = {'version': 1, 'verified': verified, 'source_canon_sha256': canon, 'media': media, 'updated_at': datetime.now(timezone.utc).isoformat()}
    RECEIPT.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n')


def main():
    required = [PROJECT / 'story/promotion.json', PROJECT / 'log/upload-transcode.json']
    missing = [str(path) for path in required if not path.exists()]
    if missing:
        raise SystemExit('Postiz upload blocked: missing verified prerequisites: ' + ', '.join(missing))
    gate = subprocess.run([sys.executable, str(PROJECT / 'script/assert-publish-ready.py')], cwd=PROJECT)
    if gate.returncode:
        raise SystemExit('Postiz upload blocked: publish gate failed')
    env = load_env(); base = env.get('POSTIZ_BASE_URL'); key = env.get('POSTIZ_API_KEY')
    if not base or not key:
        raise SystemExit('Postiz config incomplete')
    canon = json.loads((PROJECT / 'story/promotion.json').read_text())['spoken_narration_sha256']
    transcode = json.loads((PROJECT / 'log/upload-transcode.json').read_text())
    upload_copy = PROJECT / 'output/final-upload.mp4'
    if (
        transcode.get('verified') is not True
        or transcode.get('source_canon_sha256') != canon
        or transcode.get('server_verification', {}).get('under_one_gb') is not True
        or not upload_copy.exists()
        or upload_copy.stat().st_size >= 1_000_000_000
        or transcode.get('output_sha256') != sha(upload_copy)
    ):
        raise RuntimeError('Upload copy hash/size receipt is invalid for current canon')
    media = {}
    if RECEIPT.exists():
        existing = json.loads(RECEIPT.read_text())
        if existing.get('source_canon_sha256') == canon:
            media = existing.get('media', {})
    for name, path in MEDIA:
        current_sha = sha(path) if path.exists() else None
        if name in media and media[name].get('id') and media[name].get('path') and media[name].get('sha256') == current_sha:
            print(json.dumps({'asset': name, 'status': 'reused_from_project_receipt', 'id': media[name]['id']}), flush=True)
            continue
        if not path.exists() or path.stat().st_size <= 0:
            raise RuntimeError(f'Missing media: {path}')
        media[name] = upload(base, key, path)
        media[name]['sha256'] = current_sha
        save(canon, media, False)
        print(json.dumps({'asset': name, 'status': 'uploaded', 'id': media[name]['id'], 'bytes': media[name]['bytes']}), flush=True)
    verified = set(media) == {name for name, _ in MEDIA}
    save(canon, media, verified)
    print(json.dumps({'verified': verified, 'assets': list(media), 'receipt': str(RECEIPT)}, ensure_ascii=False))
    return 0 if verified else 1


if __name__ == '__main__':
    raise SystemExit(main())
