import json
from datetime import datetime, timedelta, timezone

from cryptography.fernet import Fernet, MultiFernet
from flask import current_app

from app import db
from app.config import token_encryption_keys


def _fernet() -> MultiFernet:
    """MultiFernet over TOKEN_ENCRYPTION_KEY: the first key encrypts, every listed key decrypts (rotation)."""
    keys = token_encryption_keys(current_app.config.get("TOKEN_ENCRYPTION_KEY"))
    return MultiFernet([Fernet(k) for k in keys])


def encrypt_secret(plain):
    """Fernet-encrypt a string for storage; None stays None."""
    if plain is None:
        return None
    return _fernet().encrypt(plain.encode("utf-8")).decode("ascii")


def decrypt_secret(cipher):
    """Decrypt a stored value; None stays None. Raises InvalidToken when no configured key fits."""
    if cipher is None:
        return None
    return _fernet().decrypt(cipher.encode("ascii")).decode("utf-8")


class Account(db.Model):
    """A TikTok account connected through TikTok Login; holds the OAuth access + refresh tokens."""

    __tablename__ = "accounts"

    id = db.Column(db.Integer, primary_key=True)
    open_id = db.Column(db.String(64), unique=True, nullable=False, index=True)
    union_id = db.Column(db.String(64), nullable=True)
    display_name = db.Column(db.String(256), nullable=False)
    avatar_url = db.Column(db.String(512), nullable=True)
    _access_token = db.Column("access_token", db.Text, nullable=False)
    _refresh_token = db.Column("refresh_token", db.Text, nullable=False)
    scopes = db.Column(db.Text, nullable=True)
    expires_at = db.Column(db.DateTime(), nullable=True)
    follower_count = db.Column(db.Integer, nullable=True)
    following_count = db.Column(db.Integer, nullable=True)
    likes_count = db.Column(db.Integer, nullable=True)
    video_count = db.Column(db.Integer, nullable=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(
        db.DateTime,
        default=lambda: datetime.now(timezone.utc),
        onupdate=lambda: datetime.now(timezone.utc),
    )

    scheduled_posts = db.relationship("ScheduledPost", back_populates="account", cascade="all, delete-orphan")

    def _set_access_token(self, value):
        if not value:
            raise ValueError("access token is required")
        self._access_token = encrypt_secret(value)

    def _set_refresh_token(self, value):
        if not value:
            raise ValueError("refresh token is required")
        self._refresh_token = encrypt_secret(value)

    access_token = property(None, _set_access_token, doc="write-only: encrypted on assignment")
    refresh_token = property(None, _set_refresh_token, doc="write-only: encrypted on assignment")

    def access_token_plain(self):
        """The only decryption point for the access token."""
        return decrypt_secret(self._access_token)

    def refresh_token_plain(self):
        """The only decryption point for the refresh token."""
        return decrypt_secret(self._refresh_token)

    def scope_list(self):
        return [s for s in (self.scopes or "").split(",") if s]

    def token_is_expired(self):
        """True when the access token is gone or past its expiry. `expires_at` is stored as
        naive UTC (engine-neutral), so the comparison uses naive UTC too."""
        if not self.expires_at:
            return True
        return self.expires_at <= datetime.now(timezone.utc).replace(tzinfo=None)

    def to_dict(self):
        return {
            "id": self.id,
            "open_id": self.open_id,
            "display_name": self.display_name,
            "avatar_url": self.avatar_url,
            "scopes": self.scope_list(),
            "follower_count": self.follower_count,
            "following_count": self.following_count,
            "likes_count": self.likes_count,
            "video_count": self.video_count,
            "created_at": self.created_at.isoformat() if self.created_at else None,
        }


class ScheduledPost(db.Model):
    """A video waiting for its scheduled time; the scheduler container publishes it when due (ADR-005)."""

    __tablename__ = "scheduled_posts"

    STATUS_PENDING = "pending"
    STATUS_PUBLISHING = "publishing"
    STATUS_PUBLISHED = "published"
    STATUS_FAILED = "failed"

    id = db.Column(db.Integer, primary_key=True)
    account_id = db.Column(db.Integer, db.ForeignKey("accounts.id"), nullable=False)
    title = db.Column(db.Text, nullable=True)
    privacy_level = db.Column(db.String(32), nullable=True)
    mode = db.Column(db.String(16), nullable=False, default="direct")
    video_path = db.Column(db.Text, nullable=True)
    video_url = db.Column(db.String(512), nullable=True)
    scheduled_at = db.Column(db.DateTime(), nullable=False)
    status = db.Column(db.String(16), nullable=False, default=STATUS_PENDING, index=True)
    publish_id = db.Column(db.String(128), nullable=True)
    video_id = db.Column(db.String(128), nullable=True)
    error = db.Column(db.Text, nullable=True)
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    updated_at = db.Column(
        db.DateTime,
        default=lambda: datetime.now(timezone.utc),
        onupdate=lambda: datetime.now(timezone.utc),
    )

    account = db.relationship("Account", back_populates="scheduled_posts")

    def to_dict(self):
        return {
            "id": self.id,
            "account_id": self.account_id,
            "title": self.title,
            "privacy_level": self.privacy_level,
            "mode": self.mode,
            "video_url": self.video_url,
            "scheduled_at": self.scheduled_at.isoformat() if self.scheduled_at else None,
            "status": self.status,
            "publish_id": self.publish_id,
            "video_id": self.video_id,
            "error": self.error,
            "created_at": self.created_at.isoformat() if self.created_at else None,
        }
