import os
from io import BytesIO

import pytest

from app.tiktok_api import TikTokAPIError


@pytest.fixture
def fresh_token(monkeypatch):
    monkeypatch.setattr("app.routes.api_routes.ensure_fresh_token", lambda a: "tok")


def test_posts_endpoints_require_api_key(client):
    for path in ("/api/v1/videos", "/api/v1/posts/pub/status", "/api/v1/scheduled"):
        r = client.get(path)
        assert r.status_code == 401


def test_list_videos_without_any_account(client, api_headers):
    r = client.get("/api/v1/videos", headers=api_headers)
    assert r.status_code == 404
    assert r.get_json()["error"]["code"] == "NO_ACCOUNT"


def test_list_videos(client, api_headers, account, fresh_token, monkeypatch):
    monkeypatch.setattr(
        "app.routes.api_routes.list_videos",
        lambda token, max_count=20, cursor=0: {"videos": [{"id": "v1", "view_count": 5}], "cursor": 20, "has_more": False},
    )
    r = client.get("/api/v1/videos", headers=api_headers)
    assert r.status_code == 200
    data = r.get_json()["data"]
    assert data["videos"][0]["id"] == "v1"
    assert data["has_more"] is False


def test_create_post_requires_explicit_account(client, api_headers, account, fresh_token):
    r = client.post("/api/v1/posts", headers=api_headers, json={"video_url": "https://x/v.mp4"})
    assert r.status_code == 400
    assert r.get_json()["error"]["code"] == "EXPLICIT_ACCOUNT_REQUIRED"


def test_create_post_happy_path(client, api_headers, account, fresh_token, monkeypatch):
    monkeypatch.setattr("app.services.posts.init_video_pull", lambda token, url, title=None, privacy_level=None, mode=None: {"publish_id": "pub_1"})
    monkeypatch.setattr("app.services.posts.wait_for_publish", lambda token, pid: {"publish_status": "PUBLISH_COMPLETE", "video_id": "v_1"})
    r = client.post("/api/v1/posts?account_id=%s" % account, headers=api_headers,
                    json={"video_url": "https://x/v.mp4", "title": "cap"})
    assert r.status_code == 201, r.get_json()
    data = r.get_json()["data"]
    assert data["publish_id"] == "pub_1"
    assert data["publish_status"] == "PUBLISH_COMPLETE"


def test_create_post_validates_input(client, api_headers, account, fresh_token):
    r = client.post("/api/v1/posts?account_id=%s" % account, headers=api_headers, json={})
    assert r.status_code == 400
    assert r.get_json()["error"]["code"] == "INVALID_INPUT"


def test_create_post_maps_tiktok_errors_safely(client, api_headers, account, fresh_token, monkeypatch):
    def boom(token, url, title=None, privacy_level=None, mode=None):
        raise TikTokAPIError(403, "TikTok refused the request: the app is not allowed to do this (unaudited apps can only post SELF_ONLY videos).")
    monkeypatch.setattr("app.services.posts.init_video_pull", boom)
    r = client.post("/api/v1/posts?account_id=%s" % account, headers=api_headers, json={"video_url": "https://x/v.mp4"})
    assert r.status_code == 403
    body = r.get_json()
    assert body["error"]["code"] == "FORBIDDEN"
    assert "secret" not in body["error"]["message"]


def test_media_post_publishes_and_cleans_up(client, api_headers, account, app, fresh_token, monkeypatch):
    seen = {}
    monkeypatch.setattr(
        "app.services.posts.publish_video_file",
        lambda token, path, title=None, privacy_level=None, mode=None: seen.update(existed=os.path.isfile(path), path=path) or {"publish_id": "pub_2"},
    )
    monkeypatch.setattr("app.services.posts.wait_for_publish", lambda token, pid: {"publish_status": "PUBLISH_COMPLETE", "video_id": "v_2"})
    r = client.post(
        "/api/v1/posts/media?account_id=%s" % account,
        headers=api_headers,
        data={"video_file": (BytesIO(b"fakevideo"), "clip.mp4"), "title": "from file"},
        content_type="multipart/form-data",
    )
    assert r.status_code == 201, r.get_json()
    assert seen["existed"] is True
    assert not os.path.exists(seen["path"])
    assert os.listdir(app.config["UPLOAD_FOLDER"]) == []


def test_media_post_requires_a_file(client, api_headers, account, fresh_token):
    r = client.post("/api/v1/posts/media?account_id=%s" % account, headers=api_headers, data={})
    assert r.status_code == 400
    assert r.get_json()["error"]["code"] == "NO_FILE"


def test_post_status(client, api_headers, account, fresh_token, monkeypatch):
    monkeypatch.setattr(
        "app.routes.api_routes.fetch_publish_status",
        lambda token, pid: {"publish_status": "PUBLISH_COMPLETE", "video_id": "v_3", "fail_reason": None},
    )
    r = client.get("/api/v1/posts/pub_9/status", headers=api_headers)
    assert r.status_code == 200
    data = r.get_json()["data"]
    assert data["publish_status"] == "PUBLISH_COMPLETE"
    assert data["video_id"] == "v_3"


def test_scheduled_crud(client, api_headers, account, fresh_token):
    r = client.get("/api/v1/scheduled", headers=api_headers)
    assert r.status_code == 200
    assert r.get_json()["data"] == []

    from datetime import datetime, timedelta, timezone
    scheduled_at = (datetime.now(timezone.utc) + timedelta(hours=2)).isoformat()
    r = client.post("/api/v1/scheduled?account_id=%s" % account, headers=api_headers,
                    json={"video_url": "https://x/v.mp4", "title": "later", "scheduled_at": scheduled_at})
    assert r.status_code == 201, r.get_json()
    post = r.get_json()["data"]
    assert post["status"] == "pending"
    assert post["video_url"] == "https://x/v.mp4"

    new_time = (datetime.now(timezone.utc) + timedelta(hours=3)).isoformat()
    r = client.patch("/api/v1/scheduled/%s?account_id=%s" % (post["id"], account), headers=api_headers,
                     json={"scheduled_at": new_time})
    assert r.status_code == 200
    assert r.get_json()["data"]["status"] == "pending"

    r = client.delete("/api/v1/scheduled/%s?account_id=%s" % (post["id"], account), headers=api_headers)
    assert r.status_code == 200
    assert r.get_json()["data"]["deleted"] is True


def test_scheduled_requires_explicit_account(client, api_headers, account, fresh_token):
    from datetime import datetime, timedelta, timezone
    r = client.post("/api/v1/scheduled", headers=api_headers,
                    json={"video_url": "https://x/v.mp4", "scheduled_at": (datetime.now(timezone.utc) + timedelta(hours=1)).isoformat()})
    assert r.status_code == 400
    assert r.get_json()["error"]["code"] == "EXPLICIT_ACCOUNT_REQUIRED"


def test_scheduled_retry(client, app, api_headers, account, fresh_token):
    from tests.conftest import add_scheduled_post
    post_id = add_scheduled_post(app, account, status="failed")
    r = client.post("/api/v1/scheduled/%s/retry?account_id=%s" % (post_id, account), headers=api_headers)
    assert r.status_code == 200, r.get_json()
    assert r.get_json()["data"]["status"] == "pending"

    r = client.post("/api/v1/scheduled/%s/retry?account_id=%s" % (post_id, account), headers=api_headers)
    assert r.status_code == 400
    assert r.get_json()["error"]["code"] == "INVALID_INPUT"


def test_insights_endpoints(client, api_headers, account, fresh_token, monkeypatch):
    monkeypatch.setattr("app.routes.api_routes.get_user_info", lambda token: {"follower_count": 7, "likes_count": 70})
    monkeypatch.setattr(
        "app.routes.api_routes.list_videos",
        lambda token, max_count=50, cursor=0: {"videos": [{"id": "v1", "view_count": 9}, {"id": "v2", "view_count": 3}], "cursor": 2, "has_more": False},
    )
    r = client.get("/api/v1/insights/account", headers=api_headers)
    assert r.get_json()["data"]["user"]["follower_count"] == 7
    r = client.get("/api/v1/insights/videos", headers=api_headers)
    videos = r.get_json()["data"]["videos"]
    assert videos[0]["id"] == "v1"
