"""Rank recent public TikTok posts from several accounts."""

from __future__ import annotations

import argparse
import csv
import os
import sys
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path

from tapline import SyncTaplineClient, TaplineError
from tapline.tiktok import TikTokStatistics, TikTokVideo

POSTS_PER_ACCOUNT = 5
DEFAULT_ACCOUNTS = ("duolingo", "babbel")
OUTPUT_PREFIX = "tiktok-competitors"


@dataclass(frozen=True)
class RankedPost:
    account: str
    post_id: str
    posted_at: str
    caption: str
    url: str
    views: int
    likes: int
    comments: int
    shares: int
    engagement_score: int


def read_statistics(post: TikTokVideo) -> TikTokStatistics:
    """Counts TikTok leaves out read as zero, so one sparse post can't break the ranking."""
    return post.statistics or TikTokStatistics()


def engagement_score(stats: TikTokStatistics) -> int:
    """Weight comments and shares more heavily than passive views or likes."""
    return (stats.digg_count or 0) + (stats.comment_count or 0) * 5 + (stats.share_count or 0) * 10


def collect_account(
    tapline: SyncTaplineClient, account: str, limit: int = POSTS_PER_ACCOUNT
) -> list[RankedPost]:
    page = tapline.tiktok.get_profile_videos(handle=account, sort_by="latest", region="US")
    recent_posts = sorted(page.videos, key=lambda post: post.create_time or 0, reverse=True)
    ranked: list[RankedPost] = []
    for post in recent_posts[:limit]:
        stats = read_statistics(post)
        ranked.append(
            RankedPost(
                account=account,
                post_id=post.aweme_id or "",
                posted_at=post.create_time_utc or "",
                caption=post.description or "",
                url=post.url or "",
                views=stats.play_count or 0,
                likes=stats.digg_count or 0,
                comments=stats.comment_count or 0,
                shares=stats.share_count or 0,
                engagement_score=engagement_score(stats),
            )
        )
    return ranked


def write_csv(posts: list[RankedPost], output_dir: Path) -> Path:
    output_dir.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    output_path = output_dir / f"{OUTPUT_PREFIX}-{timestamp}.csv"
    fields = [
        "rank",
        "account",
        "post_id",
        "posted_at",
        "caption",
        "url",
        "views",
        "likes",
        "comments",
        "shares",
        "engagement_score",
    ]
    with output_path.open("w", newline="", encoding="utf-8") as output:
        writer = csv.DictWriter(output, fieldnames=fields)
        writer.writeheader()
        for rank, post in enumerate(
            sorted(posts, key=lambda item: item.engagement_score, reverse=True), start=1
        ):
            writer.writerow({"rank": rank, **post.__dict__})
    return output_path


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("accounts", nargs="*", help="TikTok handles without the @")
    parser.add_argument(
        "--output-dir",
        type=Path,
        default=Path("."),
        help="Directory for the timestamped CSV (default: current directory)",
    )
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    accounts = tuple(args.accounts) or DEFAULT_ACCOUNTS
    posts: list[RankedPost] = []

    with SyncTaplineClient(api_key=os.environ["TAPLINE_API_KEY"]) as tapline:
        for account in accounts:
            try:
                posts.extend(collect_account(tapline, account))
            except TaplineError as exc:
                print(f"Skipping @{account}: {type(exc).__name__}", file=sys.stderr)

    if not posts:
        raise SystemExit("No posts were returned. Check the handles and API key.")

    output_path = write_csv(posts, args.output_dir)
    print(f"Wrote {len(posts)} posts to {output_path}")
    return 0


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