"""Idempotent seed for the E2E characterization (golden-master) account.

Run INSIDE the api container — it needs the app's models + the argon2 hasher:

    docker exec -i siftlode-api-1 python - < scripts/e2e_seed.py

Resets a dedicated, NON-demo test account to a known baseline every run, so the Playwright
golden-master net is deterministic and never touches a real account. Self-healing: a crashed
spec that left the account dirty is cleaned on the next run. Prints one JSON line describing
what it seeded (the wrapper / global-setup reads it).

Credentials come from env (E2E_EMAIL / E2E_PASSWORD) with local-only defaults; the account
lives only in the localdev DB.
"""

import json
import os
import sys
from datetime import datetime, timezone

sys.path.insert(0, "/app")

from sqlalchemy import delete, func, select  # noqa: E402

from app.db import SessionLocal  # noqa: E402
from app.models import (  # noqa: E402
    Channel,
    Playlist,
    PlaylistItem,
    Subscription,
    User,
    Video,
    VideoState,
)
from app.security import hash_password  # noqa: E402

EMAIL = os.environ.get("E2E_EMAIL", "e2e@siftlode.local")
PASSWORD = os.environ.get("E2E_PASSWORD", "e2e-golden-master")
N_SUBS = 3


def _now() -> datetime:
    return datetime.now(timezone.utc)


def main() -> None:
    db = SessionLocal()
    try:
        # 1. Upsert the account and always reset it to a clean, loginable baseline
        #    (email_verified + is_active + not suspended + not demo + password set).
        user = db.scalar(select(User).where(User.email == EMAIL))
        if user is None:
            user = User(email=EMAIL)
            db.add(user)
        user.password_hash = hash_password(PASSWORD)
        user.email_verified = True
        user.is_active = True
        user.is_suspended = False
        user.is_demo = False
        user.google_sub = None
        # Admin so the E2E can reach the admin-only pages (scheduler / config / users / audit) and
        # thus the render ternary's admin branches — the primary real user (npeter83) is admin too.
        user.role = "admin"
        user.display_name = "E2E Tester"
        user.preferences = {"language": "en"}
        db.commit()

        # 2. Subscriptions: the top-N organic channels by catalog size — deterministic, and
        #    >1 so a channel filter visibly narrows the feed. Reset each run.
        top = db.execute(
            select(Video.channel_id, func.count().label("n"))
            .join(Channel, Channel.id == Video.channel_id)
            .where(Channel.from_search.is_(False), Channel.from_explore.is_(False))
            .group_by(Video.channel_id)
            .order_by(func.count().desc())
            .limit(N_SUBS)
        ).all()
        channel_ids = [row[0] for row in top]

        db.execute(delete(Subscription).where(Subscription.user_id == user.id))
        for cid in channel_ids:
            db.add(Subscription(user_id=user.id, channel_id=cid, subscribed_at=_now()))
        db.commit()

        # 3. Deterministic watch state (reset each run):
        #    - one in-progress video (drives the resume bar + "in progress" filter) from the
        #      first channel — status stays "new" so it still counts as unwatched;
        #    - one WATCHED video from the second channel, so the "watched" show-filter is a
        #      real, non-empty narrowing of the feed (and the "all" vs "unwatched" split exists).
        db.execute(delete(VideoState).where(VideoState.user_id == user.id))

        def _newest(channel_id: str, exclude: set[str]) -> Video | None:
            conds = [
                Video.channel_id == channel_id,
                Video.duration_seconds.is_not(None),
                Video.live_status == "none",
            ]
            if exclude:
                conds.append(Video.id.not_in(exclude))
            return db.scalar(
                select(Video)
                .where(*conds)
                .order_by(Video.published_at.desc())
                .limit(1)
            )

        used: set[str] = set()
        in_progress = None
        watched = None
        if channel_ids:
            vid = _newest(channel_ids[0], used)
            if vid is not None:
                db.add(
                    VideoState(
                        user_id=user.id,
                        video_id=vid.id,
                        status="new",
                        position_seconds=42,
                        progress_updated_at=_now(),
                    )
                )
                in_progress = vid.id
                used.add(vid.id)
        if len(channel_ids) > 1:
            wv = _newest(channel_ids[1], used)
            if wv is not None:
                db.add(
                    VideoState(
                        user_id=user.id,
                        video_id=wv.id,
                        status="watched",
                        watched_at=_now(),
                    )
                )
                watched = wv.id
                used.add(wv.id)

        # 4. One "saved" video (Watch-later membership drives the saved marker) from the last
        #    subscribed channel. Reset the built-in playlist's items each run.
        wl = db.scalar(
            select(Playlist).where(
                Playlist.user_id == user.id, Playlist.kind == "watch_later"
            )
        )
        if wl is None:
            wl = Playlist(user_id=user.id, name="Watch later", kind="watch_later")
            db.add(wl)
            db.commit()
        db.execute(delete(PlaylistItem).where(PlaylistItem.playlist_id == wl.id))
        saved = None
        if channel_ids:
            svid = _newest(channel_ids[-1], used)
            if svid is not None:
                db.add(PlaylistItem(playlist_id=wl.id, video_id=svid.id, position=0))
                saved = svid.id
        db.commit()

        print(
            json.dumps(
                {
                    "ok": True,
                    "user_id": user.id,
                    "email": EMAIL,
                    "channels": channel_ids,
                    "in_progress": in_progress,
                    "watched": watched,
                    "saved": saved,
                }
            )
        )
    finally:
        db.close()


if __name__ == "__main__":
    main()
