from flask import (
    Flask, render_template, request, send_file,
    redirect, url_for, abort, g
)
from jinja2 import TemplateNotFound
from urllib.parse import urlparse, urlunparse
import yt_dlp, uuid, os, json, glob, subprocess, io

# Try requests if available (used only to proxy image downloads for nice filenames)
try:
    import requests  # type: ignore
    REQUESTS_AVAILABLE = True
except Exception:
    requests = None  # type: ignore
    REQUESTS_AVAILABLE = False

app = Flask(__name__)

# ================== LANGUAGE SETTINGS ==================
# List of supported language codes. The first entry is
# treated as the default language for the site.  If a
# user visits a URL without an explicit language prefix,
# the application will automatically redirect them to
# ``/<DEFAULT_LANG>/``.  Additional languages can be
# added here as needed.
SUPPORTED_LANGUAGES = [
    "en",  # English (default)
    "fr",  # French
    "ar",  # Arabic
    "ja",  # Japanese
    "th",  # Thai
    "id",  # Indonesian
    "de",  # German
    "es",  # Spanish
    "tr",  # Turkish
    "it",  # Italian
]

# Default language used when none is specified in the URL.
DEFAULT_LANG = SUPPORTED_LANGUAGES[0]

# Preprocess incoming URLs to extract the ``lang`` parameter
# from the path and store it on the Flask ``g`` object.  If
# the provided language code is not in ``SUPPORTED_LANGUAGES``
# or is missing entirely, the default language is used.
@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    # When there are no path variables (values can be None),
    # ``values`` will be None.  Normalise to an empty dict.
    if values is None:
        values = {}
    lang = values.pop("lang", None)
    if lang not in SUPPORTED_LANGUAGES:
        lang = DEFAULT_LANG
    g.lang = lang


# Ensure that any call to ``url_for`` will include the
# appropriate language prefix for routes that expect a
# ``lang`` parameter.  This keeps all internal links
# consistent with the currently selected language.  If
# ``g.lang`` hasn't been set yet, nothing is added.
@app.url_defaults
def add_language_code(endpoint, values):
    if "lang" in values or not hasattr(g, "lang"):
        return
    # Only inject ``lang`` for routes that expect it.
    if app.url_map.is_endpoint_expecting(endpoint, "lang"):
        values["lang"] = g.lang


# Inject useful variables into the template context.  The
# current language code and the list of all supported
# languages are available in every template.  Templates can
# reference ``lang`` to build links or highlight the active
# language.
@app.context_processor
def inject_language():
    current_lang = getattr(g, "lang", DEFAULT_LANG)
    return {
        "lang": current_lang,
        "supported_langs": SUPPORTED_LANGUAGES,
        # Provide the translation table for the current language.  If a
        # translation is missing, fall back to English.
        "translations": TRANSLATIONS.get(current_lang, TRANSLATIONS[DEFAULT_LANG]),
    }

# ================== BASIC SETTINGS ==================
DOWNLOAD_DIR = "downloads"
os.makedirs(DOWNLOAD_DIR, exist_ok=True)

# Set this if ffmpeg isn't on PATH, e.g. r"C:\\tools\\ffmpeg\\bin"
FFMPEG_DIR = None  # or r"C:\\ffmpeg\\bin"
# ====================================================

# ---- metadata store for preview ----
# {file_id: {...}}
DOWNLOAD_META = {}
# A simple translation dictionary for key UI labels.  These
# translations are used in the navigation and mode tabs across
# templates.  Feel free to expand or modify these translations
# as needed.
TRANSLATIONS = {
    "en": {
        "video": "Video Downloader",
        "story_viewer": "Story Viewer",
        "mp3": "MP3",  # short label for the MP3 converter
        "story_downloader": "Story Downloader",
        "photo": "Photo Downloader",
        # Additional UI strings for localisation.  These keys are used for
        # button labels, placeholder text and hero headings.  When adding
        # further languages below, include entries for these keys as well.
        "paste": "Paste",
        "download": "Download",
        "paste_placeholder": "🔗 Paste TikTok link here",
        "hero_brand": "SSS TikTok Downloader",
        "hero_sub": "Download TikTok Videos Instantly 100% Free, No Watermark, MP4 & MP3",

        # Extended translations for new navigation and action labels.  These
        # strings are used for the reordered tool buttons, preview page
        # actions and additional desktop navigation links.  The English
        # values are descriptive and will be used as the default for
        # languages that don't override them.
        "video_nowm": "Video Downloader (No Watermark)",
        "without_watermark_hd": "Without Watermark HD",
        "without_watermark_mp4": "Without Watermark MP4",
        "download_mp3_full": "Download MP3",
        "download_another": "Download Another",
        "download_tiktok_stories": "Download TikTok Stories",
        "download_tiktok_mp3": "Download TikTok MP3",
    },
    "fr": {
        "video": "Téléchargeur de vidéo",
        "story_viewer": "Visionneuse de story",
        "mp3": "Convertisseur MP3",
        "story_downloader": "Téléchargeur de story",
        "photo": "Téléchargeur de photo",
        "paste": "Coller",
        "download": "Télécharger",
        "paste_placeholder": "🔗 Coller le lien TikTok ici",
        "hero_brand": "Téléchargeur TikTok SSS",
        "hero_sub": "Téléchargez des vidéos TikTok instantanément gratuitement, sans filigrane, MP4 et MP3",

        # New keys reuse existing French terms where possible.  When a
        # specific translation isn't available we fall back to the English
        # label or reuse an existing translation for a similar concept.
        "video_nowm": "Téléchargeur de vidéo",
        "without_watermark_hd": "Téléchargeur de vidéo",
        "without_watermark_mp4": "Téléchargeur de vidéo",
        "download_mp3_full": "Convertisseur MP3",
        "download_another": "Télécharger",
        "download_tiktok_stories": "Téléchargeur de story",
        "download_tiktok_mp3": "Convertisseur MP3",
    },
    "ar": {
        "video": "تحميل الفيديو",
        "story_viewer": "عارض القصص",
        "mp3": "محول MP3",
        "story_downloader": "تحميل القصص",
        "photo": "تحميل الصور",
        "paste": "لصق",
        "download": "تنزيل",
        "paste_placeholder": "🔗 الصق رابط TikTok هنا",
        "hero_brand": "SSS تنزيل تيك توك",
        "hero_sub": "قم بتنزيل مقاطع فيديو تيك توك على الفور مجانًا بنسبة 100٪ بدون علامة مائية MP4 و MP3",

        # Arabic: reuse existing labels to maintain basic localisation.
        "video_nowm": "تحميل الفيديو",
        "without_watermark_hd": "تحميل الفيديو",
        "without_watermark_mp4": "تحميل الفيديو",
        "download_mp3_full": "محول MP3",
        "download_another": "تنزيل",
        "download_tiktok_stories": "تحميل القصص",
        "download_tiktok_mp3": "محول MP3",
    },
    "ja": {
        "video": "動画ダウンローダー",
        "story_viewer": "ストーリービューア",
        "mp3": "MP3変換",
        "story_downloader": "ストーリーダウンローダー",
        "photo": "写真ダウンローダー",
        "paste": "貼り付け",
        "download": "ダウンロード",
        "paste_placeholder": "🔗 TikTokのリンクをここに貼り付け",
        "hero_brand": "SSS TikTokダウンローダー",
        "hero_sub": "TikTok動画を瞬時に無料でダウンロード、透かしなし、MP4＆MP3対応",

        # Japanese: reuse existing labels to maintain consistency.
        "video_nowm": "動画ダウンローダー",
        "without_watermark_hd": "動画ダウンローダー",
        "without_watermark_mp4": "動画ダウンローダー",
        "download_mp3_full": "MP3変換",
        "download_another": "ダウンロード",
        "download_tiktok_stories": "ストーリーダウンローダー",
        "download_tiktok_mp3": "MP3変換",
    },
    "th": {
        "video": "ตัวดาวน์โหลดวิดีโอ",
        "story_viewer": "ตัวดูสตอรี่",
        "mp3": "ตัวแปลง MP3",
        "story_downloader": "ตัวดาวน์โหลดสตอรี่",
        "photo": "ตัวดาวน์โหลดรูปภาพ",
        "paste": "วาง",
        "download": "ดาวน์โหลด",
        "paste_placeholder": "🔗 วางลิงก์ TikTok ที่นี่",
        "hero_brand": "ตัวดาวน์โหลด TikTok SSS",
        "hero_sub": "ดาวน์โหลดวิดีโอ TikTok ทันทีฟรี 100% ไม่มีลายน้ำ MP4 & MP3",

        # Thai: reuse existing labels for new keys.
        "video_nowm": "ตัวดาวน์โหลดวิดีโอ",
        "without_watermark_hd": "ตัวดาวน์โหลดวิดีโอ",
        "without_watermark_mp4": "ตัวดาวน์โหลดวิดีโอ",
        "download_mp3_full": "ตัวแปลง MP3",
        "download_another": "ดาวน์โหลด",
        "download_tiktok_stories": "ตัวดาวน์โหลดสตอรี่",
        "download_tiktok_mp3": "ตัวแปลง MP3",
    },
    "id": {
        "video": "Pengunduh Video",
        "story_viewer": "Penampil Cerita",
        "mp3": "Konverter MP3",
        "story_downloader": "Pengunduh Cerita",
        "photo": "Pengunduh Foto",
        "paste": "Tempel",
        "download": "Unduh",
        "paste_placeholder": "🔗 Tempel tautan TikTok di sini",
        "hero_brand": "Pengunduh TikTok SSS",
        "hero_sub": "Unduh video TikTok secara instan 100% gratis, tanpa watermark, MP4 & MP3",

        # Indonesian: reuse existing labels.
        "video_nowm": "Pengunduh Video",
        "without_watermark_hd": "Pengunduh Video",
        "without_watermark_mp4": "Pengunduh Video",
        "download_mp3_full": "Konverter MP3",
        "download_another": "Unduh",
        "download_tiktok_stories": "Pengunduh Cerita",
        "download_tiktok_mp3": "Konverter MP3",
    },
    "de": {
        "video": "Video-Downloader",
        "story_viewer": "Story-Viewer",
        "mp3": "MP3-Konverter",
        "story_downloader": "Story-Downloader",
        "photo": "Foto-Downloader",
        "paste": "Einfügen",
        "download": "Herunterladen",
        "paste_placeholder": "🔗 TikTok-Link hier einfügen",
        "hero_brand": "SSS TikTok Downloader",
        "hero_sub": "Lade TikTok-Videos sofort herunter, 100 % kostenlos, ohne Wasserzeichen, MP4 & MP3",

        # German: reuse existing labels for new strings.
        "video_nowm": "Video-Downloader",
        "without_watermark_hd": "Video-Downloader",
        "without_watermark_mp4": "Video-Downloader",
        "download_mp3_full": "MP3-Konverter",
        "download_another": "Herunterladen",
        "download_tiktok_stories": "Story-Downloader",
        "download_tiktok_mp3": "MP3-Konverter",
    },
    "es": {
        "video": "Descargador de video",
        "story_viewer": "Visor de historias",
        "mp3": "Convertidor MP3",
        "story_downloader": "Descargador de historias",
        "photo": "Descargador de fotos",
        "paste": "Pegar",
        "download": "Descargar",
        "paste_placeholder": "🔗 Pega el enlace de TikTok aquí",
        "hero_brand": "Descargador de TikTok SSS",
        "hero_sub": "Descarga videos de TikTok al instante 100 % gratis, sin marca de agua, MP4 y MP3",

        # Spanish: reuse existing labels.
        "video_nowm": "Descargador de video",
        "without_watermark_hd": "Descargador de video",
        "without_watermark_mp4": "Descargador de video",
        "download_mp3_full": "Convertidor MP3",
        "download_another": "Descargar",
        "download_tiktok_stories": "Descargador de historias",
        "download_tiktok_mp3": "Convertidor MP3",
    },
    "tr": {
        "video": "Video İndirici",
        "story_viewer": "Hikaye Görüntüleyici",
        "mp3": "MP3 Dönüştürücü",
        "story_downloader": "Hikaye İndirici",
        "photo": "Fotoğraf İndirici",
        "paste": "Yapıştır",
        "download": "İndir",
        "paste_placeholder": "🔗 TikTok bağlantısını buraya yapıştırın",
        "hero_brand": "SSS TikTok İndirici",
        "hero_sub": "TikTok videolarını anında indir  %100 ücretsiz, filigran yok, MP4 & MP3",

        # Turkish: reuse existing labels.
        "video_nowm": "Video İndirici",
        "without_watermark_hd": "Video İndirici",
        "without_watermark_mp4": "Video İndirici",
        "download_mp3_full": "MP3 Dönüştürücü",
        "download_another": "İndir",
        "download_tiktok_stories": "Hikaye İndirici",
        "download_tiktok_mp3": "MP3 Dönüştürücü",
    },
    "it": {
        "video": "Downloader video",
        "story_viewer": "Visualizzatore di storie",
        "mp3": "Convertitore MP3",
        "story_downloader": "Downloader di storie",
        "photo": "Downloader di foto",
        "paste": "Incolla",
        "download": "Scarica",
        "paste_placeholder": "🔗 Incolla qui il link di TikTok",
        "hero_brand": "Downloader TikTok SSS",
        "hero_sub": "Scarica video TikTok istantaneamente 100% gratis, senza filigrana, MP4 e MP3",

        # Italian: reuse existing labels.
        "video_nowm": "Downloader video",
        "without_watermark_hd": "Downloader video",
        "without_watermark_mp4": "Downloader video",
        "download_mp3_full": "Convertitore MP3",
        "download_another": "Scarica",
        "download_tiktok_stories": "Downloader di storie",
        "download_tiktok_mp3": "Convertitore MP3",
    },
}

# Path for storing newsletter sign‑ups.  Emails are persisted in a
# simple JSON array.  This file lives alongside the application code
# so that it can be committed or backed up with the project.  If the
# file does not exist it will be created on first use.
EMAILS_FILE = os.path.join(os.path.dirname(__file__), "emails.json")
# -----------------------------------


# ---------------------- helpers ----------------------
def _file_stem(file_id: str) -> str:
    return os.path.join(DOWNLOAD_DIR, f"{file_id}")

def _file_path_mp4(file_id: str) -> str:
    return os.path.join(DOWNLOAD_DIR, f"{file_id}.mp4")

def _info_json_path(file_id: str) -> str:
    return os.path.join(DOWNLOAD_DIR, f"{file_id}.json")

def _ydl_opts(file_id: str) -> dict:
    """
    Minimal yt-dlp options, with a desktop UA (TikTok is picky).
    """
    out_pattern = os.path.join(DOWNLOAD_DIR, f"{file_id}.%(ext)s")
    return {
        "format": (
            "best[ext=mp4][acodec!=none]/"
            "bestvideo[ext=mp4]+bestaudio/best"
        ),
        "outtmpl": out_pattern,
        "merge_output_format": "mp4",
        "ffmpeg_location": FFMPEG_DIR,
        "quiet": True,
        "noplaylist": True,
        "http_headers": {
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/123.0.0.0 Safari/537.36"
            )
        },
    }

def normalize_tiktok_url(raw: str) -> str:
    """
    Make TikTok links yt-dlp-friendly.
    - Turn /photo/<id> into /video/<id>
    - Unify host to www.tiktok.com (handles m., vm., vt.)
    - Drop fragments, keep query
    Leaves non-TikTok links untouched.
    """
    if not raw:
        return raw
    try:
        u = urlparse(raw.strip())
    except Exception:
        return raw

    host = (u.netloc or "").lower()
    if "tiktok.com" not in host:
        return raw

    if host.startswith(("m.", "vm.", "vt.")):
        host = "www.tiktok.com"

    path = u.path or ""
    segs = [s for s in path.split("/") if s]
    if "photo" in segs:
        try:
            i = segs.index("photo")
            segs[i] = "video"
            path = "/" + "/".join(segs)
        except ValueError:
            pass

    return urlunparse((
        u.scheme or "https",
        host,
        path,
        "",
        u.query,
        ""
    ))

def _collect_image_urls(info: dict):
    """
    Collect image URLs for TikTok photo/slideshow posts from several possible fields.
    """
    urls = []

    if isinstance(info.get("images"), list):
        for im in info["images"]:
            if isinstance(im, dict) and im.get("url"):
                urls.append(im["url"])
            elif isinstance(im, str):
                urls.append(im)

    if isinstance(info.get("slides"), list):
        for s in info["slides"]:
            u = s.get("image_url") if isinstance(s, dict) else None
            if u:
                urls.append(u)

    if not urls and isinstance(info.get("thumbnails"), list):
        for t in info["thumbnails"]:
            u = t.get("url") if isinstance(t, dict) else None
            if u:
                urls.append(u)

    if not urls and isinstance(info.get("entries"), list):
        for entry in info["entries"]:
            if isinstance(entry, dict):
                if isinstance(entry.get("images"), list):
                    for im in entry["images"]:
                        if isinstance(im, dict) and im.get("url"):
                            urls.append(im["url"])
                        elif isinstance(im, str):
                            urls.append(im)
                elif isinstance(entry.get("thumbnails"), list):
                    for t in entry["thumbnails"]:
                        u = t.get("url") if isinstance(t, dict) else None
                        if u:
                            urls.append(u)

    # Deduplicate in order
    seen = set()
    result = []
    for u in urls:
        if u and u not in seen:
            seen.add(u)
            result.append(u)
    return result

def _analyze_media(info: dict):
    fmts = info.get("formats") or []
    has_video = any((f.get("vcodec") not in (None, "none")) for f in fmts)
    has_audio = any((f.get("acodec") not in (None, "none")) for f in fmts)

    images = _collect_image_urls(info)
    is_slideshow = bool(images or info.get("_type") == "playlist")

    thumb = info.get("thumbnail")
    if not thumb:
        thumbs = info.get("thumbnails") or []
        if thumbs:
            thumb = thumbs[-1].get("url")

    media_type = "video" if has_video else ("slideshow" if is_slideshow else "audio")
    return media_type, bool(has_audio), thumb, images

def _save_info_json(file_id: str, info: dict):
    try:
        with open(_info_json_path(file_id), "w", encoding="utf-8") as f:
            json.dump(info, f, ensure_ascii=False, indent=2)
    except Exception:
        pass

def _download_with_ytdlp(url: str, file_id: str):
    """
    Run yt-dlp and return (info, final_path) where final_path is a media file
    if one exists for video/audio posts.
    """
    ydl_opts = _ydl_opts(file_id)
    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
        info = ydl.extract_info(url, download=True)
    _save_info_json(file_id, info)

    # If it was a video/audio, yt-dlp should have created something with our stem.
    final_mp4 = _file_path_mp4(file_id)
    if os.path.isfile(final_mp4):
        return info, final_mp4

    matches = glob.glob(_file_stem(file_id) + ".*")
    if matches:
        # prefer mp4 if present
        mp4s = [m for m in matches if m.lower().endswith(".mp4")]
        return info, (mp4s[0] if mp4s else matches[0])

    # For pure photo slideshows, it's fine that nothing was created.
    return info, None

def _store_meta(file_id: str, info: dict):
    """Save metadata so the preview page shows real title/desc/uploader, plus media analysis."""
    title = info.get("title", "TikTok Post")
    description = info.get("description")
    uploader_name = info.get("uploader") or info.get("creator") or info.get("channel")
    uploader_handle = (
        info.get("uploader_id")
        or (info.get("uploader_url") or "").split("@")[-1].split("/")[0]
        or None
    )

    media_type, has_audio, preview_image, image_urls = _analyze_media(info)

    DOWNLOAD_META[file_id] = {
        "title": title,
        "description": description,
        "uploader_name": uploader_name,
        "uploader_handle": uploader_handle,
        "media_type": media_type,        # "video" | "slideshow" | "audio"
        "has_audio": has_audio,
        "preview_image": preview_image,
        "image_urls": image_urls,        # for photos/slideshows
        "image_count": len(image_urls),
    }
    return title

def _get_ffmpeg_bin():
    if FFMPEG_DIR:
        exe = os.path.join(FFMPEG_DIR, "ffmpeg.exe") if os.name == "nt" else os.path.join(FFMPEG_DIR, "ffmpeg")
        return exe if os.path.isfile(exe) else "ffmpeg"
    return "ffmpeg"

def _ensure_mp3(file_id: str) -> str:
    """
    Extract MP3 from whatever file we downloaded for this id.
    Returns the mp3 path.
    """
    stem = _file_stem(file_id)
    mp3_path = f"{stem}.mp3"
    if os.path.isfile(mp3_path):
        return mp3_path

    candidates = []
    for ext in (".mp4", ".m4a", ".webm", ".mov", ".mkv"):
        p = stem + ext
        if os.path.isfile(p):
            candidates.append(p)

    if not candidates:
        matches = glob.glob(stem + ".*")
        if not matches:
            raise FileNotFoundError("No media file found for audio extraction.")
        candidates = matches

    input_path = candidates[0]
    ffmpeg = _get_ffmpeg_bin()
    cmd = [
        ffmpeg, "-y",
        "-i", input_path,
        "-vn",
        "-acodec", "libmp3lame",
        "-b:a", "192k",
        mp3_path
    ]
    proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if proc.returncode != 0 or not os.path.isfile(mp3_path):
        raise RuntimeError(f"FFmpeg failed to create MP3.\n{proc.stderr.decode(errors='ignore')[:800]}")
    return mp3_path

def _image_urls_for(file_id: str):
    """
    Return the image URL list for this file_id (from memory or the saved info JSON).
    """
    meta = DOWNLOAD_META.get(file_id) or {}
    urls = meta.get("image_urls") or []
    if urls:
        return urls

    try:
        with open(_info_json_path(file_id), "r", encoding="utf-8") as f:
            info = json.load(f)
        return _collect_image_urls(info)
    except Exception:
        return []


# ----------------------- routes -----------------------
# The root route ('/') simply dispatches to the default language
# version of the index page.  If a POST is received without a
# language prefix, it is treated as an English (default) request.
@app.route("/", methods=["GET", "POST"])
def index_root():
    """Delegate to the language-specific index.

    When no language prefix is present in the URL, this handler sets
    the language to the default via ``g.lang`` (through the
    ``pull_lang_code`` preprocessor) and then dispatches to the
    main index view.  We intentionally do not pass any arguments to
    ``index()`` because ``pull_lang_code`` has already removed the
    ``lang`` value from the request and stored it on ``g``.  Passing
    a parameter would cause a ``TypeError``.
    """
    return index()


# Main index page with language prefix.  This route handles both
# GET and POST.  On GET, it renders the home page with the search
# form.  On POST, it attempts to download the provided TikTok URL
# and then redirects to the preview page.
@app.route("/<lang>/", methods=["GET", "POST"])
def index():  # noqa: D401
    """Render the home page and handle form submissions.

    The ``lang`` segment is stripped by the ``pull_lang_code``
    preprocessor and stored on ``g.lang``.  Use ``g.lang`` when
    building URLs or redirecting.
    """
    current_lang = g.lang
    # For POST requests, attempt to process the download.
    if request.method == "POST":
        raw_url = (request.form.get("url") or "").strip()
        if not raw_url:
            return "<h3>Error:</h3><p>Please paste a TikTok link.</p>"

        url = normalize_tiktok_url(raw_url)
        file_id = uuid.uuid4().hex

        try:
            info, _ = _download_with_ytdlp(url, file_id)
            title = _store_meta(file_id, info)
            # Include the language code when redirecting to the preview
            return redirect(url_for("preview", file_id=file_id, title=title, lang=current_lang))
        except Exception as err:
            return "<h3>Error:</h3><pre>{}</pre>".format(err)

    # GET: render the home page for the given language.  The ``lang``
    # variable is injected automatically via the context processor,
    # so templates can build links accordingly.
    return render_template("index.html")


@app.route("/preview")
def preview_query_root():
    """Accept ``/preview?url=...`` without a language prefix.

    The request is processed using the default language and the
    user is redirected to the language-specific preview page.  If
    no URL is provided the user is sent back to the home page.
    """
    raw_url = (request.args.get("url") or "").strip()
    if not raw_url:
        return redirect(url_for("index", lang=DEFAULT_LANG))

    url = normalize_tiktok_url(raw_url)
    file_id = uuid.uuid4().hex

    try:
        info, _ = _download_with_ytdlp(url, file_id)
        title = _store_meta(file_id, info)
        return redirect(url_for("preview", file_id=file_id, title=title, lang=DEFAULT_LANG))
    except Exception as err:
        return "<h3>Error:</h3><pre>{}</pre>".format(err), 500


@app.route("/<lang>/preview")
def preview_from_query():
    """Accept ``/<lang>/preview?url=...``.

    Downloads the provided TikTok URL and redirects to the
    language-specific preview page.  If no URL is given the
    request is redirected back to the index page of the same
    language.
    The ``lang`` segment is removed by ``pull_lang_code`` and
    available on ``g.lang``.
    """
    current_lang = g.lang
    raw_url = (request.args.get("url") or "").strip()
    if not raw_url:
        return redirect(url_for("index", lang=current_lang))

    url = normalize_tiktok_url(raw_url)
    file_id = uuid.uuid4().hex

    try:
        info, _ = _download_with_ytdlp(url, file_id)
        title = _store_meta(file_id, info)
        return redirect(url_for("preview", file_id=file_id, title=title, lang=current_lang))
    except Exception as err:
        return "<h3>Error:</h3><pre>{}</pre>".format(err), 500


@app.route("/preview/<file_id>")
def preview_no_lang(file_id):
    """Show the preview page without a language prefix.

    The user is redirected to the language-specific version of
    the page, preserving any query parameters (like ``title``).
    """
    # Preserve the ``title`` query parameter if present.
    args = {k: v for k, v in request.args.items() if k == "title"}
    return redirect(url_for("preview", file_id=file_id, lang=DEFAULT_LANG, **args))


@app.route("/<lang>/preview/<file_id>")
def preview(file_id):
    """Render the preview page for the current language and file.

    Metadata about the downloaded TikTok post is looked up from
    memory or from the stored JSON file.  Any provided ``title``
    query parameter is used to override the default title.  The
    language code is available via ``g.lang`` and need not be passed
    explicitly.
    """
    # read stored meta so template can show real title/description/user
    meta = DOWNLOAD_META.get(file_id, {})
    title = request.args.get("title", meta.get("title") or "TikTok Post")
    return render_template(
        "preview.html",
        file_id=file_id,
        title=title,
        description=meta.get("description"),
        uploader_name=meta.get("uploader_name"),
        uploader_handle=meta.get("uploader_handle"),
        media_type=meta.get("media_type", "video"),
        has_audio=meta.get("has_audio", False),
        preview_image=meta.get("preview_image"),
        image_count=meta.get("image_count", 0),
    )


# ---------- Newsletter subscription ----------
# Accept email sign‑ups via POST.  This route accepts either JSON or
# form‑encoded bodies.  Valid emails are appended to ``EMAILS_FILE``.
# Duplicate entries are ignored.  Any errors result in a JSON error
# response with an appropriate status code.
@app.post("/subscribe")
def subscribe():
    """Persist the provided email into ``emails.json``.

    The request body may be JSON or form data.  The response is a
    JSON object indicating success or failure.  Email addresses are
    minimally validated for the presence of '@' and a dot.  This is
    not meant to be exhaustive validation but is sufficient to catch
    most malformed inputs.
    """
    try:
        data = request.get_json(silent=True) or request.form
    except Exception:
        data = request.form
    email = (data.get("email") or "").strip()
    # rudimentary validation
    if not email:
        return {"status": "error", "message": "No email provided"}, 400
    if "@" not in email or "." not in email:
        return {"status": "error", "message": "Invalid email address"}, 400
    try:
        # load existing emails
        emails = []
        if os.path.exists(EMAILS_FILE):
            with open(EMAILS_FILE, "r", encoding="utf-8") as f:
                try:
                    emails = json.load(f) or []
                except Exception:
                    emails = []
        # append if unique
        if email not in emails:
            emails.append(email)
            with open(EMAILS_FILE, "w", encoding="utf-8") as f:
                json.dump(emails, f, ensure_ascii=False, indent=2)
        return {"status": "ok"}
    except Exception as e:
        return {"status": "error", "message": str(e)}, 500


# ---------- serve media for <video> if it exists ----------
@app.get("/video/<file_id>")
def serve_video(file_id):
    # choose any file with our stem if .mp4 is absent
    path = _file_path_mp4(file_id)
    if not os.path.isfile(path):
        matches = glob.glob(_file_stem(file_id) + ".*")
        if not matches:
            abort(404)
        mp4s = [m for m in matches if m.lower().endswith(".mp4")]
        path = mp4s[0] if mp4s else matches[0]
    return send_file(path, mimetype="video/mp4")


@app.get("/download/<file_id>")
def download_video(file_id):
    path = _file_path_mp4(file_id)
    if not os.path.isfile(path):
        matches = glob.glob(_file_stem(file_id) + ".*")
        if not matches:
            abort(404)
        mp4s = [m for m in matches if m.lower().endswith(".mp4")]
        path = mp4s[0] if mp4s else matches[0]
    name = os.path.basename(path)
    return send_file(path, as_attachment=True, download_name=name)


# ---------- audio (MP3) ----------
@app.get("/download/mp3/<file_id>")
def download_audio(file_id):
    try:
        mp3_path = _ensure_mp3(file_id)
        return send_file(mp3_path, as_attachment=True, download_name=f"{file_id}.mp3")
    except Exception as e:
        return "<h3>Error creating MP3:</h3><pre>{}</pre>".format(e), 500


# ---------- single-photo preview (redirect to original image URL) ----------
@app.get("/photo/inline/<file_id>/<int:idx>")
def inline_photo(file_id, idx: int):
    """
    For <img src="..."> in preview. We 302-redirect to the real image URL.
    Works even if 'requests' isn't installed.
    """
    urls = _image_urls_for(file_id)
    if not urls or idx < 1 or idx > len(urls):
        abort(404)
    return redirect(urls[idx - 1], code=302)


# ---------- single-photo download ----------
@app.get("/download/photo/<file_id>/<int:idx>")
def download_single_photo(file_id, idx: int):
    """
    Let the user download one image with a nice filename.
    If 'requests' isn't available, we fall back to a 302 redirect to the image URL.
    """
    urls = _image_urls_for(file_id)
    if not urls or idx < 1 or idx > len(urls):
        abort(404)

    img_url = urls[idx - 1]
    # fallback: just send a redirect (no custom filename)
    if not REQUESTS_AVAILABLE:
        return redirect(img_url, code=302)

    try:
        r = requests.get(img_url, timeout=30)
        r.raise_for_status()
        data = r.content
        # guess content type
        ctype = r.headers.get("Content-Type", "image/jpeg")
        # choose extension
        ext = ".jpg"
        if "png" in ctype:
            ext = ".png"
        elif "webp" in ctype:
            ext = ".webp"
        fname = f"{file_id}_photo_{idx:02d}{ext}"
        return send_file(
            io.BytesIO(data),
            mimetype=ctype,
            as_attachment=True,
            download_name=fname
        )
    except Exception:
        # If proxying fails for any reason, just redirect to the image
        return redirect(img_url, code=302)


# ---------- MODE PAGES (render dedicated templates so the URL changes) ----------
@app.get("/tiktok-video-downloader", endpoint="legacy_video_downloader")
def legacy_video_downloader():
    """Legacy entry point for the video downloader.

    Redirects to the language-prefixed version using the default language.
    """
    return redirect(url_for("video_page", lang=DEFAULT_LANG))


# Provide BOTH the new endpoint name and legacy endpoint name used in old templates
@app.get("/tiktok-story-viewer", endpoint="legacy_story_viewer")
def legacy_story_viewer():
    """Legacy entry point for the story viewer.

    Redirects to the language-prefixed version using the default language.
    """
    return redirect(url_for("story_viewer_mode", lang=DEFAULT_LANG))












@app.get("/tiktok-story-downloader", endpoint="legacy_story_downloader")
def legacy_story_downloader():
    """Legacy entry point for the story downloader.

    Redirects to the language-prefixed version using the default language.
    """
    return redirect(url_for("story_downloader", lang=DEFAULT_LANG))

@app.get("/stories", endpoint="stories_download_mode")  # legacy alias
def legacy_stories_redirect():
    """Legacy alias for the story downloader page.

    Redirects to the language-prefixed version using the default language.
    """
    return redirect(url_for("story_downloader", lang=DEFAULT_LANG))












# Provide a single legacy route for the MP3 downloader.  The original code
# defined this route twice (once with no explicit endpoint and once with a
# custom endpoint).  Defining the same URL multiple times can lead to
# endpoint conflicts.  Instead we expose a single handler under a new
# endpoint name.  Any requests to ``/tiktok-mp3-downloader`` will be
# redirected to the language-prefixed version using the default language.
@app.get("/tiktok-mp3-downloader", endpoint="legacy_mp3_downloader")
def legacy_mp3_downloader():
    """Legacy entry point for the MP3 downloader.

    Redirects to the language-prefixed version using the default language.
    """
    return redirect(url_for("mp3_page", lang=DEFAULT_LANG))

# Provide a single legacy route for the photo downloader.  This route
# previously pointed directly at the template, which conflicted with the
# language-prefixed version.  We now expose it under a distinct endpoint
# name and redirect to the new URL structure.
@app.get("/tiktok-photo-downloader", endpoint="legacy_photo_downloader")
def legacy_photo_downloader():
    """Legacy entry point for the photo downloader.

    Redirects to the language-prefixed version using the default language.
    """
    return redirect(url_for("photos_page", lang=DEFAULT_LANG))


# ---------- Language-prefixed MODE PAGES ----------
# These routes serve the actual templates for each mode under a language
# prefix.  The ``lang`` parameter is captured from the URL and
# propagated automatically to other links via ``url_for`` thanks to
# ``add_language_code``.  Each handler simply renders its
# corresponding template with the desired page title and mode.

@app.route("/<lang>/video-downloader")
def video_page():
    """Render the video downloader page for the current language.

    The language code is available on ``g.lang``.
    """
    return render_template(
        "video.html",
        page_title="SSS TikTok — Video Downloader",
        mode="video",
    )


@app.route("/<lang>/story-viewer")
def story_viewer_mode():
    """Render the story viewer page for the current language."""
    return render_template(
        "storyviewer.html",
        page_title="SSS TikTok — Story Viewer",
        mode="story",
    )


@app.route("/<lang>/story-downloader")
def story_downloader():
    """Render the story downloader page for the current language."""
    return render_template(
        "StoriesDownload.html",
        page_title="SSS TikTok — Story Downloader",
        mode="stories",
    )


@app.route("/<lang>/mp3-downloader")
def mp3_page():
    """Render the MP3 converter page for the current language."""
    return render_template(
        "MP3.html",
        page_title="SSS TikTok — MP3 Downloader",
        mode="mp3",
    )


@app.route("/<lang>/photo-downloader")
def photos_page():
    """Render the photo downloader page for the current language."""
    return render_template(
        "Photo.html",
        page_title="SSS TikTok — Photo Downloader",
        mode="photo",
    )


# --------- blueprint to satisfy url_for('photo.home') -----------
from flask import Blueprint
photo = Blueprint("photo", __name__)

@photo.route("/photo")
def home():
    # Legacy entry point: redirect to the language-prefixed version using the default language.
    return redirect(url_for("photos_page", lang=DEFAULT_LANG))

app.register_blueprint(photo)


# --------- language endpoints referenced by index.html ----------
# The old query-parameter-based language endpoints have been removed.
# Language selection is now performed via a URL prefix (e.g. ``/en/`` or ``/fr/``).


# ---------------- static pages used in footer ----------------
def _render_or_fallback(template_name: str, title: str, body: str):
    try:
        return render_template(template_name)
    except TemplateNotFound:
        # Build a simple fallback page.  When linking back to the
        # home page we must include the language code; otherwise
        # ``url_for('index')`` will generate a link that requires
        # a ``lang`` argument and cause a TypeError.  Use g.lang
        # where available, falling back to the default language.
        home_url = url_for("index", lang=getattr(g, "lang", DEFAULT_LANG))
        return (
            "<!doctype html>"
            "<html><head><meta charset='utf-8'><title>{}</title>"
            "<meta name='viewport' content='width=device-width,initial-scale=1'></head>"
            "<body style=\"font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;"
            "max-width:800px;margin:40px auto;padding:0 16px;line-height:1.6\">"
            "<h1>{}</h1>"
            "<p>{}</p>"
            "<p style='margin-top:24px'><a href='{}'>← Back to Home</a></p>"
            "</body></html>".format(title, title, body, home_url)
        )

# --------- STATIC PAGES USED IN FOOTER ---------
# Provide legacy routes without a language prefix.  These simply
# redirect to the default language versions to maintain
# compatibility with existing URLs.

@app.get("/contact-us")
def contact_no_lang():
    return redirect(url_for("contact", lang=DEFAULT_LANG))

@app.get("/about-us")
def about_no_lang():
    return redirect(url_for("about", lang=DEFAULT_LANG))

@app.get("/terms-and-conditions")
def terms_no_lang():
    return redirect(url_for("terms", lang=DEFAULT_LANG))

@app.get("/privacy-policy")
def privacy_no_lang():
    return redirect(url_for("privacy", lang=DEFAULT_LANG))


# Language-prefixed static pages.  These routes render the
# corresponding templates (if they exist) or fall back to a simple
# placeholder page.  The ``lang`` parameter is captured but not
# otherwise used by these pages; it simply keeps the URL in sync
# with the selected language.
@app.route("/<lang>/contact-us")
def contact():
    """Render the contact page for the current language or fallback."""
    return _render_or_fallback(
        "contact.html",
        "Contact Us",
        "This is a placeholder Contact page. Create templates/contact.html to customize.",
    )

@app.route("/<lang>/about-us")
def about():
    """Render the about page for the current language or fallback."""
    return _render_or_fallback(
        "about.html",
        "About",
        "This is a placeholder About page. Create templates/about.html to customize.",
    )

@app.route("/<lang>/terms-and-conditions")
def terms():
    """Render the terms page for the current language or fallback."""
    return _render_or_fallback(
        "terms.html",
        "Terms & Conditions",
        "This is a placeholder Terms page. Create templates/terms.html to customize.",
    )

@app.route("/<lang>/privacy-policy")
def privacy():
    """Render the privacy page for the current language or fallback."""
    return _render_or_fallback(
        "privacy.html",
        "Privacy Policy",
        "This is a placeholder Privacy page. Create templates/privacy.html to customize.",
    )


# ---------------------- run ----------------------
if __name__ == "__main__":
    app.run(debug=True)
