"""torrent.py — .torrent + magnet link generation for product bundles.

Bundle contents depend on product type:

  Free content (is_sellable=False):
    content.{ext}       ← the actual file (freely distributed)
    thumbnail1.{ext}    ← cover image
    thumbnail2..4.{ext} ← additional images (if present)
    description.md      ← product description

  Paid product (is_sellable=True):
    preview.{ext}       ← preview file only (the paid file is NOT included)
    thumbnail1.{ext}
    thumbnail2..4.{ext}
    description.md

Thumbnails + description travel with both types.
The paid product file is never seeded — it's gated behind normal payment flow.

Pipeline per product:
  1. Download bundle files from S3 into a temp directory
  2. Build a multi-file .torrent with torf (BEP 3/BEP 19 web seeds)
  3. Upload the .torrent back to S3 at {s3_path}/bundle.torrent
  4. Derive magnet link from torrent info hash
  5. Save magnet + torrent URL to product row in DB

Fire-and-forget daemon threads — same pattern as s3_mirror.
"""

import logging
import os
import tempfile
import threading

import boto3

log = logging.getLogger(__name__)

DEFAULT_TRACKERS = [
    "udp://tracker.opentrackr.org:1337/announce",
    "udp://open.stealth.si:80/announce",
    "udp://tracker.torrent.eu.org:451/announce",
    "udp://tracker.openbittorrent.com:80/announce",
]


# ── S3 credential helpers ─────────────────────────────────────────────────────

def _capture_s3_creds(client):
    """Extract serialisable credentials from a boto3 client."""
    creds = client._request_signer._credentials
    meta = client.meta
    return {
        "endpoint": meta.endpoint_url,
        "region": meta.region_name,
        "access_key": creds.access_key,
        "secret_key": creds.secret_key,
    }


def _make_client(creds):
    return boto3.session.Session().client(
        "s3",
        region_name=creds["region"],
        endpoint_url=creds["endpoint"],
        aws_access_key_id=creds["access_key"],
        aws_secret_access_key=creds["secret_key"],
    )


# ── Bundle file list builder ──────────────────────────────────────────────────

def build_bundle_files(product, cdn_endpoint):
    """Return a list of dicts describing every file that belongs in the torrent.

    Each dict:
        s3_key      — key in the S3 bucket
        filename    — local filename to use inside the torrent directory
        webseed_url — BEP-19 HTTP seed URL (or None)

    The paid product file is intentionally excluded for sellable products.
    """
    s3_path = product.s3_path
    originals = product.originals
    extensions = product.extensions

    def cdn(s3_key):
        return f"{cdn_endpoint}/{s3_key}" if cdn_endpoint else None

    files = []

    if product.is_sellable:
        # Paid product — seed preview only, never the product file.
        if "preview" in originals:
            ext = extensions.get("preview", "")
            s3_key = f"{s3_path}/preview"
            files.append({
                "s3_key": s3_key,
                "filename": f"preview.{ext}" if ext else "preview",
                "webseed_url": cdn(s3_key),
            })
    else:
        # Free content — seed the content file.
        if "product" in originals:
            ext = extensions.get("product", "")
            s3_key = f"{s3_path}/product"
            files.append({
                "s3_key": s3_key,
                "filename": f"content.{ext}" if ext else "content",
                "webseed_url": cdn(s3_key),
            })

    # Thumbnails — same for both types.
    for key in ["thumbnail1", "thumbnail2", "thumbnail3", "thumbnail4"]:
        if key in originals:
            ext = extensions.get(key, "")
            s3_key = f"{s3_path}/{key}"
            files.append({
                "s3_key": s3_key,
                "filename": f"{key}.{ext}" if ext else key,
                "webseed_url": cdn(s3_key),
            })

    return files


# ── Core torrent generator (runs in background thread) ───────────────────────

def generate_torrent(s3_client, bucket, bundle_name, files, description,
                     s3_path, product_id, session_factory,
                     trackers=None, cdn_endpoint=None):
    """Download files from S3, build a directory .torrent, upload, save to DB.

    bundle_name: torrent name / top-level directory inside the .torrent
    files:       list of {s3_key, filename, webseed_url} dicts (from build_bundle_files)
    description: product description text (written as description.md)
    s3_path:     {shop_id}/{product_id} — where to upload the .torrent file
    """
    if trackers is None:
        trackers = DEFAULT_TRACKERS

    if not files:
        log.warning("torrent: no files to bundle for product=%s — skipping", product_id)
        return

    try:
        import torf
    except ImportError:
        raise RuntimeError("torf is required — pip install torf")

    log.info("torrent: generating bundle '%s' for product=%s (%d files)",
             bundle_name, product_id, len(files))

    with tempfile.TemporaryDirectory() as tmpdir:
        bundle_dir = os.path.join(tmpdir, bundle_name)
        os.makedirs(bundle_dir)

        webseeds = []
        downloaded = set()  # filenames that actually landed on disk

        # 1. Download each file from S3 into the bundle directory.
        for f in files:
            local_path = os.path.join(bundle_dir, f["filename"])
            log.info("torrent: downloading s3://%s/%s → %s", bucket, f["s3_key"], f["filename"])
            try:
                s3_client.download_file(bucket, f["s3_key"], local_path)
                downloaded.add(f["filename"])
            except Exception:
                log.warning("torrent: skipping missing file s3://%s/%s", bucket, f["s3_key"])
                continue
            if f.get("webseed_url"):
                webseeds.append(f["webseed_url"])

        # 2. Write index.md + index.html into the bundle directory.
        # Separate the main file (content/preview) from thumbnails using the
        # filename prefix — build_bundle_files names them content.*, preview.*,
        # and thumbnail1..4.*.
        main_file = next(
            (f for f in files
             if f["filename"].startswith(("content.", "preview."))
             and f["filename"] in downloaded),
            None,
        )
        thumb_files = [
            f for f in files
            if f["filename"].startswith("thumbnail")
            and f["filename"] in downloaded
        ]

        if description:
            with open(os.path.join(bundle_dir, "index.md"), "w", encoding="utf-8") as fh:
                fh.write(description)

        try:
            import markdown as _md
            html_body = _md.markdown(description or "", extensions=["fenced_code", "tables"])
        except ImportError:
            html_body = f"<pre>{description}</pre>" if description else ""

        # Thumbnail gallery — relative links, rendered inline.
        thumb_html = ""
        if thumb_files:
            imgs = "".join(
                f'<a href="{f["filename"]}">'
                f'<img src="{f["filename"]}" style="max-width:320px;margin:4px" /></a>'
                for f in thumb_files
            )
            thumb_html = f'<div class="thumbnails" style="display:flex;flex-wrap:wrap;gap:4px">{imgs}</div>\n'

        # Main file link — download anchor.
        main_html = ""
        if main_file:
            label = "Download" if main_file["filename"].startswith("content.") else "Preview"
            main_html = (
                f'<p><a href="{main_file["filename"]}" download>'
                f'{label}: {main_file["filename"]}</a></p>\n'
            )

        html = (
            "<!DOCTYPE html>\n<html>\n<head>"
            "<meta charset='utf-8'>"
            f"<title>{bundle_name}</title>"
            "<style>body{font-family:sans-serif;max-width:800px;margin:2em auto;padding:0 1em}"
            "img{border-radius:4px}a{color:#16a34a}</style>"
            "</head>\n<body>\n"
            f"<h1>{bundle_name}</h1>\n"
            + thumb_html
            + main_html
            + html_body
            + "\n</body>\n</html>\n"
        )
        with open(os.path.join(bundle_dir, "index.html"), "w", encoding="utf-8") as fh:
            fh.write(html)

        # 3. Build multi-file .torrent from the bundle directory.
        t = torf.Torrent(
            path=bundle_dir,
            name=bundle_name,
            trackers=[[tr] for tr in trackers],
            webseeds=webseeds,
            private=False,
            comment="permacomputer.com — open distribution bundle",
            source="permacomputer",
        )
        t.generate()
        magnet_uri = str(t.magnet())
        log.info("torrent: magnet=%s", magnet_uri)

        # 4. Write and upload the .torrent file.
        torrent_path = os.path.join(tmpdir, "bundle.torrent")
        t.write(torrent_path)
        torrent_s3_key = f"{s3_path}/bundle.torrent"
        s3_client.upload_file(
            torrent_path,
            bucket,
            torrent_s3_key,
            ExtraArgs={
                "ACL": "public-read",
                "ContentType": "application/x-bittorrent",
                "ContentDisposition": f'attachment; filename="{bundle_name}.torrent"',
                "CacheControl": "public, max-age=86400",
            },
        )
        log.info("torrent: uploaded .torrent to s3://%s/%s", bucket, torrent_s3_key)

    torrent_file_url = (
        f"{cdn_endpoint}/{torrent_s3_key}" if cdn_endpoint else None
    )

    # 5. Save magnet + torrent URL to the product row.
    try:
        with session_factory() as session:
            from ..models.product import Product
            product = session.get(Product, product_id)
            if product is not None:
                product.torrent_magnet_link = magnet_uri
                product.torrent_file_url = torrent_file_url
                session.add(product)
                session.commit()
                log.info("torrent: saved magnet + torrent_file_url for product=%s", product_id)
    except Exception:
        log.exception("torrent: failed to save torrent data for product=%s", product_id)


def generate_torrent_async(s3_client, bucket, bundle_name, files, description,
                           s3_path, product_id, session_factory,
                           trackers=None, cdn_endpoint=None):
    """Fire-and-forget: generate torrent bundle in a daemon thread."""
    creds = _capture_s3_creds(s3_client)

    def _run():
        try:
            fresh_client = _make_client(creds)
            generate_torrent(
                fresh_client, bucket, bundle_name, files, description,
                s3_path, product_id, session_factory,
                trackers=trackers, cdn_endpoint=cdn_endpoint,
            )
        except Exception:
            log.exception("torrent: background generation failed for product=%s", product_id)

    t = threading.Thread(target=_run, daemon=True, name=f"torrent-{product_id}")
    t.start()
    log.info("torrent: background thread started for product=%s", product_id)
