import os
from datetime import datetime, timedelta, timezone
from io import BytesIO

import pytest

from app.services import posts as posts_service
from app.services.errors import ValidationError
from app.services.posts import parse_scheduled_time, publish_now, validate_post_fields


def _file(name="clip.mp4", content=b"fakevideo"):
    from werkzeug.datastructures import FileStorage
    return FileStorage(stream=BytesIO(content), filename=name)


def test_validate_requires_a_video():
    with pytest.raises(ValidationError):
        validate_post_fields({"title": "hello"})


def test_validate_rejects_bad_fields():
    with pytest.raises(ValidationError):
        validate_post_fields({"title": "x" * (posts_service.TITLE_MAX_CHARS + 1), "video_url": "https://x/v.mp4"})
    with pytest.raises(ValidationError):
        validate_post_fields({"video_url": "https://x/v.mp4", "privacy_level": "EVERYONE"})
    with pytest.raises(ValidationError):
        validate_post_fields({"video_url": "https://x/v.mp4", "mode": "story"})
    with pytest.raises(ValidationError):
        validate_post_fields({"video_url": "ftp://x/v.mp4"})
    with pytest.raises(ValidationError):
        validate_post_fields({"video_url": "https://x/v.mp4", "video_file": _file()})
    with pytest.raises(ValidationError):
        validate_post_fields({"video_file": _file("clip.exe")})


def test_validate_defaults():
    fields = validate_post_fields({"video_url": "https://x/v.mp4"})
    assert fields["privacy_level"] == "SELF_ONLY"
    assert fields["mode"] == "draft"
    assert fields["title"] is None


def test_publish_now_pulls_from_url(monkeypatch):
    calls = {}
    monkeypatch.setattr(posts_service, "init_video_pull", lambda token, url, title=None, privacy_level=None, mode=None: calls.update(url=url, title=title) or {"publish_id": "pub_1"})
    monkeypatch.setattr(posts_service, "wait_for_publish", lambda token, pid: {"publish_status": "PUBLISH_COMPLETE", "video_id": "v_1"})
    result = publish_now("tok", validate_post_fields({"video_url": "https://x/v.mp4", "title": "cap"}), "/tmp")
    assert result == {"publish_id": "pub_1", "publish_status": "PUBLISH_COMPLETE", "video_id": "v_1"}
    assert calls["url"] == "https://x/v.mp4"
    assert calls["title"] == "cap"


def test_publish_now_uploads_file_and_cleans_up(monkeypatch, tmp_path):
    seen = {}
    monkeypatch.setattr(
        posts_service, "publish_video_file",
        lambda token, path, title=None, privacy_level=None, mode=None: seen.update(path=path, existed=os.path.isfile(path)) or {"publish_id": "pub_2"},
    )
    monkeypatch.setattr(posts_service, "wait_for_publish", lambda token, pid: {"publish_status": "PUBLISH_COMPLETE", "video_id": "v_2"})
    result = publish_now("tok", validate_post_fields({"video_file": _file()}), str(tmp_path))
    assert result["publish_id"] == "pub_2"
    assert seen["existed"] is True
    assert not os.path.exists(seen["path"])
    assert os.listdir(str(tmp_path)) == []


def test_publish_now_cleans_up_when_tiktok_fails(monkeypatch, tmp_path):
    from app.tiktok_api import TikTokAPIError
    seen = {}
    monkeypatch.setattr(
        posts_service, "publish_video_file",
        lambda token, path, title=None, privacy_level=None, mode=None: seen.update(path=path) or (_ for _ in ()).throw(TikTokAPIError(403, "TikTok refused the request.")),
    )
    with pytest.raises(TikTokAPIError):
        publish_now("tok", validate_post_fields({"video_file": _file()}), str(tmp_path))
    assert not os.path.exists(seen["path"])
    assert os.listdir(str(tmp_path)) == []


def test_publish_now_reports_processing_failure(monkeypatch, tmp_path):
    monkeypatch.setattr(posts_service, "publish_video_file", lambda token, path, title=None, privacy_level=None, mode=None: {"publish_id": "pub_3"})
    monkeypatch.setattr(posts_service, "wait_for_publish", lambda token, pid: {"publish_status": "FAILED", "video_id": None})
    result = publish_now("tok", validate_post_fields({"video_file": _file()}), str(tmp_path))
    assert result["publish_status"] == "FAILED"


def test_parse_scheduled_time():
    future = datetime.now(timezone.utc) + timedelta(hours=2)
    assert parse_scheduled_time(future.isoformat()) == future
    assert parse_scheduled_time("") is None
    assert parse_scheduled_time(None) is None
    with pytest.raises(ValidationError):
        parse_scheduled_time("not-a-date")
    with pytest.raises(ValidationError):
        parse_scheduled_time((datetime.now(timezone.utc) - timedelta(hours=1)).isoformat())
    with pytest.raises(ValidationError):
        parse_scheduled_time(12345)


def test_validate_rejects_a_mode_the_account_did_not_grant():
    with pytest.raises(ValidationError):
        validate_post_fields({"video_url": "https://x/v.mp4"}, "user.info.basic,video.list,video.publish")
    with pytest.raises(ValidationError):
        validate_post_fields({"video_url": "https://x/v.mp4", "mode": "direct"}, "user.info.basic,video.upload")
    assert validate_post_fields({"video_url": "https://x/v.mp4"}, "user.info.basic,video.upload")["mode"] == "draft"
