import os import shutil from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from sqlalchemy import text from ..auth_utils import get_current_user from ..config import settings from ..database import get_db from ..models import User, UserTagAffinity router = APIRouter() @router.get("") def get_stats( db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): uid = current_user.id totals = db.execute( text(""" SELECT COUNT(*) AS total_watched, SUM(uv.watch_progress_seconds) AS total_watch_seconds FROM user_videos uv WHERE uv.user_id = :uid AND uv.watched = 1 """), {"uid": uid}, ).mappings().first() top_channels = db.execute( text(""" SELECT c.id, c.name, COUNT(*) AS watch_count, SUM(uv.watch_progress_seconds) AS watch_seconds FROM user_videos uv JOIN videos v ON uv.video_id = v.id JOIN channels c ON v.channel_id = c.id WHERE uv.user_id = :uid AND uv.watched = 1 GROUP BY c.id, c.name ORDER BY watch_seconds DESC LIMIT 10 """), {"uid": uid}, ).mappings().all() daily = db.execute( text(""" SELECT date(uv.last_watched_at) AS date, COUNT(*) AS count, SUM(uv.watch_progress_seconds) AS seconds FROM user_videos uv WHERE uv.user_id = :uid AND uv.watched = 1 AND uv.last_watched_at >= datetime('now', '-30 days') GROUP BY date(uv.last_watched_at) ORDER BY date ASC """), {"uid": uid}, ).mappings().all() this_week = db.execute( text(""" SELECT COUNT(*) AS count, SUM(uv.watch_progress_seconds) AS seconds FROM user_videos uv WHERE uv.user_id = :uid AND uv.watched = 1 AND uv.last_watched_at >= datetime('now', '-7 days') """), {"uid": uid}, ).mappings().first() this_month = db.execute( text(""" SELECT COUNT(*) AS count, SUM(uv.watch_progress_seconds) AS seconds FROM user_videos uv WHERE uv.user_id = :uid AND uv.watched = 1 AND uv.last_watched_at >= datetime('now', '-30 days') """), {"uid": uid}, ).mappings().first() avg_completion = db.execute( text(""" SELECT AVG(uv.completion_percent) AS avg_pct, COUNT(CASE WHEN uv.completion_percent >= 90 THEN 1 END) AS finished_count, COUNT(CASE WHEN uv.completion_percent < 20 AND uv.completion_percent IS NOT NULL THEN 1 END) AS bailed_count, SUM(uv.rewatch_count) AS total_rewatches, COUNT(CASE WHEN uv.rewatch_count > 0 THEN 1 END) AS rewatched_videos FROM user_videos uv WHERE uv.user_id = :uid AND uv.watched = 1 """), {"uid": uid}, ).mappings().first() top_categories = db.execute( text(""" SELECT v.category, COUNT(*) AS watch_count, AVG(uv.completion_percent) AS avg_completion FROM user_videos uv JOIN videos v ON uv.video_id = v.id WHERE uv.user_id = :uid AND uv.watched = 1 AND v.category IS NOT NULL GROUP BY v.category ORDER BY watch_count DESC LIMIT 8 """), {"uid": uid}, ).mappings().all() taste_profile = db.execute( text(""" SELECT tag, score FROM user_tag_affinity WHERE user_id = :uid AND score > 0 ORDER BY score DESC LIMIT 60 """), {"uid": uid}, ).mappings().all() peak_hours = db.execute( text(""" SELECT CAST(strftime('%H', uv.last_watched_at) AS INTEGER) AS hour, COUNT(*) AS count FROM user_videos uv WHERE uv.user_id = :uid AND uv.watched = 1 AND uv.last_watched_at IS NOT NULL GROUP BY hour ORDER BY hour ASC """), {"uid": uid}, ).mappings().all() liked_count = db.execute( text("SELECT COUNT(*) AS n FROM user_videos WHERE user_id = :uid AND liked = 1"), {"uid": uid}, ).mappings().first() try: disk = shutil.disk_usage(settings.download_path) download_bytes = sum( e.stat().st_size for e in os.scandir(settings.download_path) if e.is_file() ) except Exception: disk = None download_bytes = 0 return { "total_watched": totals["total_watched"] or 0, "total_watch_seconds": totals["total_watch_seconds"] or 0, "top_channels": [dict(r) for r in top_channels], "daily": [dict(r) for r in daily], "this_week": { "count": this_week["count"] or 0, "seconds": this_week["seconds"] or 0, }, "this_month": { "count": this_month["count"] or 0, "seconds": this_month["seconds"] or 0, }, "avg_completion_percent": round(avg_completion["avg_pct"] or 0, 1), "finished_count": avg_completion["finished_count"] or 0, "bailed_count": avg_completion["bailed_count"] or 0, "total_rewatches": avg_completion["total_rewatches"] or 0, "rewatched_videos": avg_completion["rewatched_videos"] or 0, "total_liked": liked_count["n"] or 0, "top_categories": [dict(r) for r in top_categories], "peak_hours": [dict(r) for r in peak_hours], "taste_profile": [dict(r) for r in taste_profile], "disk": { "total_bytes": disk.total if disk else None, "free_bytes": disk.free if disk else None, "used_bytes": disk.used if disk else None, "download_bytes": download_bytes, }, } @router.delete("/taste/{tag}", status_code=204) def delete_taste_tag( tag: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): row = db.query(UserTagAffinity).filter_by(user_id=current_user.id, tag=tag).first() if not row: raise HTTPException(status_code=404, detail="Tag not found") db.delete(row) db.commit()