import json
import os
from datetime import datetime, timedelta, timezone

import pytest
from cryptography.fernet import Fernet
from flask_migrate import downgrade, upgrade

from app import create_app, db
from app.config import database_uri
from app.models import Account, ScheduledPost

TEST_API_KEY = "test-api-key"
TEST_ENCRYPTION_KEY = Fernet.generate_key().decode()


def make_config(tmp_path, **overrides):
    """Build a config class for one test app; every folder and the DB live under tmp_path."""
    uploads = tmp_path / "uploads"
    media = tmp_path / "media"
    uploads.mkdir(exist_ok=True)
    media.mkdir(exist_ok=True)
    db_path = tmp_path / "test.db"
    if os.environ.get("DB_ENGINE", "sqlite").lower() == "postgres":
        uri = database_uri(os.environ)
    else:
        uri = f"sqlite:///{db_path.as_posix()}"
    values = dict(
        TESTING=True,
        FLASK_ENV="testing",
        SECRET_KEY="test-secret-key",
        API_KEY=TEST_API_KEY,
        TIKTOK_CLIENT_KEY="test-client-key",
        TIKTOK_CLIENT_SECRET="test-client-secret",
        TIKTOK_REDIRECT_URI="https://localhost:9506/auth/callback",
        TIKTOK_SCOPES=["user.info.basic", "user.info.stats", "video.list", "video.publish"],
        DATABASE_PATH=str(db_path),
        SQLALCHEMY_DATABASE_URI=uri,
        SQLALCHEMY_TRACK_MODIFICATIONS=False,
        MAX_UPLOAD_SIZE_MB=10,
        MAX_CONTENT_LENGTH=10 * 1024 * 1024,
        MEDIA_FOLDER=str(media),
        SCHEDULER_INTERVAL_SECONDS=30,
        APP_NAME="TikTok Manager (test)",
        UPLOAD_FOLDER=str(uploads),
        WTF_CSRF_ENABLED=False,
        TOKEN_ENCRYPTION_KEY=TEST_ENCRYPTION_KEY,
    )
    values.update(overrides)
    return type("TestConfig", (), values)


@pytest.fixture
def app(tmp_path):
    """One app per test; the schema comes from the migrations (both engines), never create_all."""
    application = create_app(make_config(tmp_path))
    with application.app_context():
        upgrade()
    yield application
    with application.app_context():
        db.session.remove()
        downgrade(revision="base")
        db.engine.dispose()


@pytest.fixture
def client(app):
    return app.test_client()


@pytest.fixture
def api_headers():
    return {"X-API-Key": TEST_API_KEY}


def add_account(app, open_id="open_id_0001", name="Test Account"):
    """Insert one connected account into the given app's DB and return its database id."""
    with app.app_context():
        account = Account(
            open_id=open_id,
            display_name=name,
            access_token="access-token-plain",
            refresh_token="refresh-token-plain",
            scopes="user.info.basic,video.list,video.upload,video.publish",
            expires_at=datetime.now(timezone.utc) + timedelta(hours=20),
            follower_count=1000,
            following_count=100,
            likes_count=5000,
            video_count=12,
        )
        db.session.add(account)
        db.session.commit()
        return account.id


@pytest.fixture
def account(app):
    """One connected account; returns its database id."""
    return add_account(app)


def add_scheduled_post(app, account_id, scheduled_at=None, status="pending", video_path=None, video_url=None):
    with app.app_context():
        at = scheduled_at or datetime.now(timezone.utc) + timedelta(hours=1)
        if at.tzinfo:
            at = at.astimezone(timezone.utc).replace(tzinfo=None)
        post = ScheduledPost(
            account_id=account_id,
            title="scheduled title",
            privacy_level="SELF_ONLY",
            mode="direct",
            video_path=video_path,
            video_url=video_url or "https://example.com/video.mp4",
            scheduled_at=at,
            status=status,
        )
        db.session.add(post)
        db.session.commit()
        return post.id


def login(client, account_db_id):
    with client.session_transaction() as s:
        s["active_account_id"] = account_db_id
