"""The scheduled-post worker: publishes due posts and records the outcome (ADR-005).

Run by the dedicated scheduler container:  python -m app.scheduler
"""
import logging
from datetime import datetime, timezone

from app import db
from app.auth import ensure_fresh_token
from app.models import ScheduledPost
from app.services.errors import ValidationError
from app.services.posts import validate_post_fields
from app.services.uploads import VIDEO_EXTENSIONS, remove_quietly, store_upload
from app.tiktok_api import TikTokAPIError, init_video_pull, publish_video_file, wait_for_publish

log = logging.getLogger(__name__)


def create_scheduled_post(account, data, media_folder, upload_max_bytes=None):
    """Create a pending scheduled post from form/JSON fields plus an optional video file; the
    file is stored under MEDIA_FOLDER until the scheduler publishes it. Raises ValidationError."""
    from app.services.posts import parse_scheduled_time

    fields = validate_post_fields(data, account.scopes)
    scheduled_at = parse_scheduled_time(data.get("scheduled_at"))
    if not scheduled_at:
        raise ValidationError("scheduled_at is required for a scheduled post")

    video_path = None
    if fields["video_file"]:
        stored = store_upload(fields["video_file"], media_folder, VIDEO_EXTENSIONS, max_bytes=upload_max_bytes)
        video_path = stored.path

    post = ScheduledPost(
        account_id=account.id,
        title=fields["title"],
        privacy_level=fields["privacy_level"] if fields["mode"] == "direct" else None,
        mode=fields["mode"],
        video_path=video_path,
        video_url=fields["video_url"],
        scheduled_at=scheduled_at.astimezone(timezone.utc).replace(tzinfo=None),
        status=ScheduledPost.STATUS_PENDING,
    )
    db.session.add(post)
    db.session.commit()
    log.info("scheduled.create ok id=%s at=%s mode=%s", post.id, scheduled_at.isoformat(), post.mode)
    return post


def reschedule_post(post, scheduled_at_value):
    """Move a scheduled post to a new time and put it back in the pending queue."""
    from app.services.posts import parse_scheduled_time

    scheduled_at = parse_scheduled_time(scheduled_at_value)
    if not scheduled_at:
        raise ValidationError("scheduled_at is required")
    post.scheduled_at = scheduled_at.astimezone(timezone.utc).replace(tzinfo=None)
    post.status = ScheduledPost.STATUS_PENDING
    post.error = None
    db.session.commit()
    return post


def delete_scheduled_post(post):
    """Delete a scheduled post row and its stored media file (when present)."""
    if post.video_path:
        remove_quietly(post.video_path)
    db.session.delete(post)
    db.session.commit()


def retry_post(post):
    """Put a failed post back in the pending queue with its original time (already in the past,
    so the scheduler publishes it on the next tick)."""
    post.status = ScheduledPost.STATUS_PENDING
    post.error = None
    db.session.commit()
    return post


def run_due_posts(now=None):
    """Publish every pending post whose time has come; marks each row published or failed with a
    safe error message. Returns the number of due posts processed. `scheduled_at` is stored as
    naive UTC, so the comparison uses naive UTC too."""
    now = now or datetime.now(timezone.utc)
    now_naive = now.replace(tzinfo=None) if now.tzinfo else now
    due = ScheduledPost.query.filter_by(status=ScheduledPost.STATUS_PENDING).filter(
        ScheduledPost.scheduled_at <= now_naive
    ).order_by(ScheduledPost.scheduled_at).all()

    for post in due:
        post.status = ScheduledPost.STATUS_PUBLISHING
        db.session.commit()
        try:
            token = ensure_fresh_token(post.account)
            if post.video_path:
                result = publish_video_file(
                    token, post.video_path,
                    title=post.title, privacy_level=post.privacy_level, mode=post.mode,
                )
            else:
                result = init_video_pull(
                    token, post.video_url,
                    title=post.title, privacy_level=post.privacy_level, mode=post.mode,
                )
            status = wait_for_publish(token, result["publish_id"])
            if status["publish_status"] == "FAILED":
                post.status = ScheduledPost.STATUS_FAILED
                post.error = "TikTok rejected the video while processing it."
            else:
                post.status = ScheduledPost.STATUS_PUBLISHED
                post.error = None
            post.publish_id = result["publish_id"]
            post.video_id = status.get("video_id")
            if post.status == ScheduledPost.STATUS_PUBLISHED and post.video_path:
                remove_quietly(post.video_path)
            log.info("scheduled.publish id=%s status=%s", post.id, post.status)
        except TikTokAPIError as e:
            post.status = ScheduledPost.STATUS_FAILED
            post.error = e.message
            log.error("scheduled.publish failed id=%s code=%s", post.id, e.code)
        except Exception:
            post.status = ScheduledPost.STATUS_FAILED
            post.error = "Publishing failed; check the server log."
            log.error("scheduled.publish failed id=%s", post.id, exc_info=True)
        db.session.commit()
    return len(due)
