#!/usr/bin/env python3
"""paste — stdlib-only client for paste.dot.com.in"""
import argparse
import configparser
import json
import mimetypes
import os
import socket
import sys
import time
import urllib.error
import urllib.request
import uuid
from pathlib import Path

CONFIG_PATH = Path(os.environ.get("PASTE_CONFIG", "~/.config/paste/config.ini")).expanduser()
DEFAULT_ENDPOINT = "https://paste.dot.com.in"


def load_config() -> configparser.ConfigParser:
    cfg = configparser.ConfigParser()
    cfg["paste"] = {"endpoint": DEFAULT_ENDPOINT, "token": ""}
    if CONFIG_PATH.exists():
        cfg.read(CONFIG_PATH)
    return cfg


def save_config(cfg: configparser.ConfigParser) -> None:
    CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
    with CONFIG_PATH.open("w") as f:
        cfg.write(f)
    CONFIG_PATH.chmod(0o600)


def request(method: str, url: str, *, headers=None, data=None, timeout=60):
    req = urllib.request.Request(url, method=method, data=data, headers=headers or {})
    return urllib.request.urlopen(req, timeout=timeout)


def auth_headers(cfg) -> dict:
    tok = cfg["paste"].get("token", "")
    if not tok:
        sys.exit("not logged in — run `paste login` first")
    return {"Authorization": f"Bearer {tok}"}


def cmd_login(args, cfg):
    endpoint = args.endpoint or cfg["paste"]["endpoint"]
    label = args.label or f"{socket.gethostname()}"
    body = json.dumps({"label": label}).encode()
    r = request("POST", f"{endpoint}/api/link/start",
                headers={"Content-Type": "application/json"}, data=body)
    start = json.loads(r.read())
    print(f"\nOpen this URL in a browser where you're already signed in:")
    print(f"   {start['url']}\n")
    print("Waiting for confirmation (Ctrl-C to cancel)...", end="", flush=True)
    deadline = time.time() + start["expires_in"]
    while time.time() < deadline:
        time.sleep(2)
        try:
            poll = request("GET", f"{endpoint}/api/link/{start['code']}").read()
            data = json.loads(poll)
            if data["status"] == "approved":
                cfg["paste"]["endpoint"] = endpoint
                cfg["paste"]["token"] = data["token"]
                save_config(cfg)
                print(f"\nLinked. Token saved to {CONFIG_PATH}.")
                return
        except urllib.error.HTTPError as e:
            if e.code != 404:
                raise
        print(".", end="", flush=True)
    sys.exit("\nlink expired")


def parse_url_or_id(s: str, cfg) -> str:
    if "/" in s:
        return s.rsplit("/", 1)[-1]
    return s


def cmd_push(args, cfg):
    endpoint = cfg["paste"]["endpoint"]
    headers = auth_headers(cfg)
    if args.file:
        path = Path(args.file)
        if not path.is_file():
            sys.exit(f"no such file: {path}")
        mime = mimetypes.guess_type(str(path))[0] or "application/octet-stream"
        boundary = uuid.uuid4().hex
        with path.open("rb") as f:
            data = f.read()
        body = build_multipart(boundary, path.name, mime, data, args.ttl)
        h = dict(headers)
        h["Content-Type"] = f"multipart/form-data; boundary={boundary}"
        r = request("POST", f"{endpoint}/api/paste/file", headers=h, data=body)
    else:
        if args.text == "-":
            content = sys.stdin.read()
        else:
            content = args.text or ""
        body = json.dumps({"content": content, "ttl": args.ttl or ""}).encode()
        h = dict(headers)
        h["Content-Type"] = "application/json"
        r = request("POST", f"{endpoint}/api/paste/text", headers=h, data=body)
    out = json.loads(r.read())
    print(out["url"])


def build_multipart(boundary, filename, mime, data, ttl):
    parts = []
    if ttl:
        parts.append(f"--{boundary}\r\nContent-Disposition: form-data; name=\"ttl\"\r\n\r\n{ttl}\r\n".encode())
    parts.append(
        f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{filename}\"\r\n"
        f"Content-Type: {mime}\r\n\r\n".encode()
    )
    parts.append(data)
    parts.append(f"\r\n--{boundary}--\r\n".encode())
    return b"".join(parts)


def cmd_pull(args, cfg):
    endpoint = cfg["paste"]["endpoint"]
    pid = parse_url_or_id(args.id, cfg)
    meta = json.loads(request("GET", f"{endpoint}/api/paste/{pid}").read())
    if meta["kind"] == "text":
        text = meta["content"]
        if args.output:
            Path(args.output).write_text(text)
            print(f"saved → {args.output}")
        else:
            sys.stdout.write(text)
            if not text.endswith("\n"):
                sys.stdout.write("\n")
    else:
        out = args.output or meta["filename"]
        with request("GET", f"{endpoint}/api/paste/{pid}/download") as r, open(out, "wb") as f:
            while chunk := r.read(64 * 1024):
                f.write(chunk)
        print(f"saved → {out} ({meta['size_bytes']} B)")


def cmd_list(args, cfg):
    endpoint = cfg["paste"]["endpoint"]
    headers = auth_headers(cfg)
    data = json.loads(request("GET", f"{endpoint}/api/pastes", headers=headers).read())
    now = int(time.time())
    print(f"{'id':<12}{'kind':<6}{'size':>10}  {'age':<8}{'expires':<10}{'name'}")
    for p in data["pastes"]:
        age = f"{int((now - p['created_at']) / 60)}m"
        exp = "never" if p["expires_at"] is None else f"{int((p['expires_at'] - now) / 3600)}h"
        name = p.get("filename") or ""
        print(f"{p['id']:<12}{p['kind']:<6}{p['size_bytes']:>10}  {age:<8}{exp:<10}{name}")


def cmd_rm(args, cfg):
    endpoint = cfg["paste"]["endpoint"]
    headers = auth_headers(cfg)
    pid = parse_url_or_id(args.id, cfg)
    request("DELETE", f"{endpoint}/api/paste/{pid}", headers=headers)
    print(f"deleted {pid}")


def cmd_whoami(args, cfg):
    endpoint = cfg["paste"]["endpoint"]
    headers = auth_headers(cfg)
    out = json.loads(request("GET", f"{endpoint}/api/whoami", headers=headers).read())
    print(f"endpoint: {endpoint}")
    print(f"session:  {out['session_id']}")


def main():
    p = argparse.ArgumentParser(prog="paste")
    sub = p.add_subparsers(dest="cmd", required=True)

    sp = sub.add_parser("login", help="link this machine to a browser session")
    sp.add_argument("--endpoint", help=f"override endpoint (default {DEFAULT_ENDPOINT})")
    sp.add_argument("--label", help="label for this token (default: hostname)")
    sp.set_defaults(fn=cmd_login)

    sp = sub.add_parser("push", help="create a paste")
    sp.add_argument("text", nargs="?", help='text content (use "-" for stdin)')
    sp.add_argument("--file", help="upload a file instead of text")
    sp.add_argument("--ttl", help="e.g. 7d, 24h, never")
    sp.set_defaults(fn=cmd_push)

    sp = sub.add_parser("pull", help="fetch a paste by id or URL")
    sp.add_argument("id", help="paste id or full URL")
    sp.add_argument("-o", "--output", help="write to this path instead of stdout")
    sp.set_defaults(fn=cmd_pull)

    sp = sub.add_parser("list", help="list your pastes")
    sp.set_defaults(fn=cmd_list)

    sp = sub.add_parser("rm", help="delete a paste")
    sp.add_argument("id", help="paste id or full URL")
    sp.set_defaults(fn=cmd_rm)

    sp = sub.add_parser("whoami", help="show current session")
    sp.set_defaults(fn=cmd_whoami)

    args = p.parse_args()
    cfg = load_config()
    try:
        args.fn(args, cfg)
    except urllib.error.HTTPError as e:
        sys.exit(f"HTTP {e.code}: {e.read().decode(errors='replace')}")


if __name__ == "__main__":
    main()
