"""Publishing use cases shared by the web UI and the REST API (one validation, two adapters)."""
import logging
from datetime import datetime, timezone

from app.services.errors import ValidationError
from app.services.uploads import VIDEO_EXTENSIONS, remove_quietly, store_upload
from app.tiktok_api import (
    PRIVACY_LEVELS, POST_MODES, init_video_pull, publish_video_file, wait_for_publish,
)

log = logging.getLogger(__name__)

TITLE_MAX_CHARS = 2200
MODE_SCOPES = {"draft": "video.upload", "direct": "video.publish"}


def _text(data, key):
    """Return a stripped string field from form/JSON data; raises ValidationError on a non-string."""
    value = data.get(key)
    if value is None or value == "":
        return ""
    if not isinstance(value, str):
        raise ValidationError(f"{key} must be a string")
    return value.strip()


def validate_post_fields(data, granted_scopes=None):
    """Validate the shared publish fields; returns a dict with title, privacy_level, mode,
    video_url and the optional video file. With `granted_scopes` (the account's comma-separated
    scopes) the mode must be one the account authorized. Raises ValidationError."""
    title = _text(data, "title")
    privacy_level = _text(data, "privacy_level") or "SELF_ONLY"
    mode = _text(data, "mode") or "draft"
    video_url = _text(data, "video_url")
    video_file = data.get("video_file")

    if len(title) > TITLE_MAX_CHARS:
        raise ValidationError(f"title must be at most {TITLE_MAX_CHARS} characters")
    if privacy_level not in PRIVACY_LEVELS:
        raise ValidationError("privacy_level must be one of: " + ", ".join(PRIVACY_LEVELS))
    if mode not in POST_MODES:
        raise ValidationError("mode must be 'draft' or 'direct'")
    if granted_scopes is not None:
        needed = MODE_SCOPES[mode]
        if needed not in {s.strip() for s in (granted_scopes or "").split(",")}:
            raise ValidationError(f"{mode} posts need the {needed} permission; reconnect the account and grant it")
    if video_url and not video_url.startswith(("http://", "https://")):
        raise ValidationError("video_url must be an http(s) URL")
    if video_url and video_file:
        raise ValidationError("send either video_url or a video file, not both")
    if not (video_url or video_file):
        raise ValidationError("a post needs a video_url or a video file")
    if video_file and getattr(video_file, "filename", "") and not video_file.filename.lower().endswith(tuple(VIDEO_EXTENSIONS)):
        raise ValidationError("video must be one of: " + ", ".join(sorted(VIDEO_EXTENSIONS)))

    return {
        "title": title or None,
        "privacy_level": privacy_level,
        "mode": mode,
        "video_url": video_url or None,
        "video_file": video_file if (video_file and getattr(video_file, "filename", "")) else None,
    }


def publish_now(token, fields, upload_folder):
    """Publish a video immediately: FILE_UPLOAD for a file (temp copy, always cleaned up) or
    PULL_FROM_URL for a URL; waits for TikTok's processing up to ~90s. Returns
    {publish_id, publish_status, video_id}."""
    stored = None
    try:
        if fields["video_file"]:
            stored = store_upload(fields["video_file"], upload_folder, VIDEO_EXTENSIONS)
            result = publish_video_file(
                token, stored.path,
                title=fields["title"], privacy_level=fields["privacy_level"], mode=fields["mode"],
            )
        else:
            result = init_video_pull(
                token, fields["video_url"],
                title=fields["title"], privacy_level=fields["privacy_level"], mode=fields["mode"],
            )
        status = wait_for_publish(token, result["publish_id"])
        log.info("posts.publish ok publish_id=%s status=%s mode=%s",
                 result["publish_id"], status["publish_status"], fields["mode"])
        return {
            "publish_id": result["publish_id"],
            "publish_status": status["publish_status"],
            "video_id": status.get("video_id"),
        }
    finally:
        if stored:
            remove_quietly(stored.path)


def parse_scheduled_time(value):
    """Validate a scheduling value (ISO-8601 string); returns an aware UTC datetime or None;
    raises ValidationError. The time must be in the future (the scheduler publishes at that moment)."""
    if value in (None, ""):
        return None
    if not isinstance(value, str):
        raise ValidationError("scheduled_at must be an ISO-8601 datetime")
    try:
        parsed = datetime.fromisoformat(value.strip().replace("Z", "+00:00"))
    except ValueError:
        raise ValidationError("scheduled_at must be an ISO-8601 datetime") from None
    scheduled = parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)
    if scheduled <= datetime.now(timezone.utc):
        raise ValidationError("scheduled_at must be in the future")
    return scheduled
