from datetime import datetime, timezone, timedelta
from pathlib import Path

from fastapi.testclient import TestClient
from sqlmodel import Session, select

from app.core.config import get_settings
from app.db.session import init_db, reset_db_for_tests
from app.main import app
from app.models.core import (
    AppSetting,
    ActivityEvent,
    ExternalPost,
    PlatformAccount,
    PostDraft,
    Provider,
    VideoAsset,
    VideoStatus,
    WebsiteCompanion,
    YouTubeUploadAudit,
)
from app.security.tokens import decrypt_token, encrypt_token


def setup_test_db(tmp_path):
    settings = get_settings()
    settings.database_url = "sqlite://"
    settings.storage_root = tmp_path / "storage"
    settings.google_client_id = None
    settings.google_client_secret = None
    settings.google_redirect_uri = "http://testserver/api/accounts/youtube/connect/callback"
    settings.youtube_upload_dry_run = False
    engine = init_db(settings)
    reset_db_for_tests(engine)
    return engine, TestClient(app)


def make_video_draft_account(engine, tmp_path, *, privacy="private", suffix=""):
    video_file = tmp_path / "video.mp4"
    video_file.parent.mkdir(parents=True, exist_ok=True)
    video_file.write_bytes(b"fake video bytes")
    with Session(engine) as session:
        video = VideoAsset(candidate_id=f"sprint8-video{suffix}", package_id=f"package_s8{suffix}", source="prepared_package", file_path=str(video_file), file_size=video_file.stat().st_size, checksum=f"sprint8checksum{suffix}", working_title="Sprint 8 Upload", status=VideoStatus.ready_for_review, is_aigc=True)
        draft = PostDraft(video_asset_id=video.id, provider=Provider.youtube, title="Sprint 8 Upload Title", description="Reviewed description", tags_json=["cybersecurity", "onlinesafety"], privacy_status=privacy, category_id="22", metadata_json={"disclosure": {"synthetic_media": True}})
        account = PlatformAccount(provider=Provider.youtube, display_name=f"YouTube Test{suffix}", external_account_id=f"yt-test{suffix}", scopes=["https://www.googleapis.com/auth/youtube.upload"], encrypted_access_token=encrypt_token("access-token"), encrypted_refresh_token=encrypt_token("refresh-token"), token_expires_at=datetime.now(timezone.utc) + timedelta(hours=1), status="connected", is_default=True)
        session.add(video); session.add(draft); session.add(account); session.commit(); session.refresh(video); session.refresh(draft); session.refresh(account)
        comp = WebsiteCompanion(video_asset_id=video.id, post_draft_id=draft.id, title="Sprint 8 Guide", slug="sprint-8-guide", status="generated")
        session.add(comp); session.commit(); session.refresh(comp)
        return video.id, draft.id, account.id, comp.id


def test_oauth_start_requires_config(tmp_path):
    _, client = setup_test_db(tmp_path)
    response = client.get("/api/accounts/youtube/connect/start")
    assert response.status_code == 400
    assert response.json()["detail"]["message"] == "Google OAuth config missing"


def test_oauth_start_stores_state_when_configured(tmp_path):
    engine, client = setup_test_db(tmp_path)
    settings = get_settings()
    settings.google_client_id = "client-id"
    settings.google_client_secret = "client-secret"
    response = client.get("/api/accounts/youtube/connect/start")
    assert response.status_code == 200
    body = response.json()
    assert "authorization_url" in body
    with Session(engine) as session:
        row = session.get(AppSetting, "youtube_oauth_state")
        assert row.value_json["state"] == body["state"]


def test_encrypted_tokens_roundtrip_and_are_not_plaintext():
    encrypted = encrypt_token("refresh-token-secret")
    assert encrypted != "refresh-token-secret"
    assert decrypt_token(encrypted) == "refresh-token-secret"


def test_upload_rejects_missing_confirmations_and_non_private(tmp_path):
    engine, client = setup_test_db(tmp_path)
    _, draft_id, account_id, _ = make_video_draft_account(engine, tmp_path)
    response = client.post(f"/api/publish/youtube/private-upload/{draft_id}", json={"account_id": account_id})
    assert response.status_code == 400
    assert "confirmation" in response.text.lower()

    _, draft2_id, account2_id, _ = make_video_draft_account(engine, tmp_path / "two", privacy="unlisted", suffix="2")
    response = client.post(f"/api/publish/youtube/private-upload/{draft2_id}", json={"account_id": account2_id, "confirm_private_upload": True, "confirm_metadata_reviewed": True, "confirm_disclosure_reviewed": True, "dry_run": True})
    assert response.status_code == 400
    assert "private uploads only" in response.text


def test_dry_run_private_upload_creates_audit_and_links_context(tmp_path):
    engine, client = setup_test_db(tmp_path)
    video_id, draft_id, account_id, comp_id = make_video_draft_account(engine, tmp_path)
    response = client.post(f"/api/publish/youtube/private-upload/{draft_id}", json={"account_id": account_id, "confirm_private_upload": True, "confirm_metadata_reviewed": True, "confirm_disclosure_reviewed": True, "dry_run": True})
    assert response.status_code == 200
    body = response.json()
    assert body["dry_run"] is True
    assert body["youtube_video_id"].startswith("dryrun_")
    assert body["website_link_status"] == "waiting_for_public_video"
    with Session(engine) as session:
        draft = session.get(PostDraft, draft_id)
        video = session.get(VideoAsset, video_id)
        comp = session.get(WebsiteCompanion, comp_id)
        external = session.exec(select(ExternalPost).where(ExternalPost.external_post_id == body["youtube_video_id"])).first()
        audit = session.exec(select(YouTubeUploadAudit).where(YouTubeUploadAudit.youtube_video_id == body["youtube_video_id"])).first()
        events = session.exec(select(ActivityEvent).where(ActivityEvent.event_type == "youtube_upload_private_succeeded")).all()
        assert draft.external_post_id == body["youtube_video_id"]
        assert video.external_youtube_id == body["youtube_video_id"]
        assert external is not None
        assert audit is not None
        assert audit.privacy_status == "private"
        assert audit.notify_subscribers is False
        assert "access-token" not in str(audit.request_redacted_json)
        assert comp.status == "waiting_for_public_video"
        assert comp.website_link_allowed is False
        assert events


def test_logs_include_youtube_upload_audit_without_tokens(tmp_path):
    engine, client = setup_test_db(tmp_path)
    _, draft_id, account_id, _ = make_video_draft_account(engine, tmp_path)
    result = client.post(f"/api/publish/youtube/private-upload/{draft_id}", json={"account_id": account_id, "confirm_private_upload": True, "confirm_metadata_reviewed": True, "confirm_disclosure_reviewed": True, "dry_run": True})
    assert result.status_code == 200
    logs = client.get("/api/logs?kind=youtube_uploads").json()["items"]
    assert logs
    serialized = str(logs)
    assert "refresh-token" not in serialized
    assert "access-token" not in serialized
    assert result.json()["youtube_video_id"] in serialized
