from datetime import datetime, date, timedelta, timezone
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select

from app.models.payment import Subscription
from app.models.academic import Package
from app.models.user import User
from app.core.config import settings
from app.core.security import generate_secure_token


async def get_active_subscription(db: AsyncSession, user_id: int) -> Optional[Subscription]:
    """Get the user's current active subscription."""
    result = await db.execute(
        select(Subscription)
        .where(
            Subscription.user_id == user_id,
            Subscription.status.in_(["active", "grace_period"]),
        )
        .order_by(Subscription.created_at.desc())
    )
    return result.scalar_one_or_none()


async def check_premium_access(
    db: AsyncSession, user_id: int, require_online_check: bool = False
) -> dict:
    """
    Check if a user has valid premium access.
    Returns dict with: has_access, is_premium, subscription_status, days_remaining
    """
    sub = await get_active_subscription(db, user_id)

    if not sub:
        # Check for freemium
        freemium_result = await db.execute(
            select(Subscription)
            .join(Package)
            .where(
                Subscription.user_id == user_id,
                Package.slug == "freemium",
            )
        )
        freemium_sub = freemium_result.scalar_one_or_none()
        return {
            "has_access": bool(freemium_sub),
            "is_premium": False,
            "subscription_status": "freemium" if freemium_sub else "none",
            "days_remaining": None,
        }

    # Load package
    pkg_result = await db.execute(select(Package).where(Package.id == sub.package_id))
    pkg = pkg_result.scalar_one_or_none()

    today = date.today()
    is_premium = pkg and pkg.name == "premium"

    # For premium, enforce online subscription check
    if is_premium and pkg.requires_subscription_check:
        # Check if last verification is recent enough
        if sub.last_verified_at:
            hours_since_check = (
                datetime.now(timezone.utc) - sub.last_verified_at
            ).total_seconds() / 3600
            if hours_since_check > settings.SUBSCRIPTION_CHECK_INTERVAL_HOURS:
                # Force re-verification
                sub.status = "grace_period"
                await db.commit()

    days_remaining = None
    if sub.end_date:
        days_remaining = (sub.end_date - today).days

        if today > sub.end_date:
            # Check grace period
            if sub.grace_period_end and today <= sub.grace_period_end:
                sub.status = "grace_period"
                await db.commit()
                return {
                    "has_access": True,
                    "is_premium": False,  # Locked out of premium features
                    "subscription_status": "grace_period",
                    "days_remaining": -(today - sub.end_date).days,
                    "grace_days_remaining": (sub.grace_period_end - today).days,
                }
            else:
                sub.status = "expired"
                await db.commit()
                return {
                    "has_access": False,
                    "is_premium": False,
                    "subscription_status": "expired",
                    "days_remaining": 0,
                }

    # Update verification timestamp
    sub.last_verified_at = datetime.now(timezone.utc)
    sub.verification_token = generate_secure_token(16)
    await db.commit()

    return {
        "has_access": True,
        "is_premium": is_premium,
        "subscription_status": sub.status,
        "days_remaining": days_remaining,
        "verification_token": sub.verification_token,
    }


async def verify_offline_access(
    db: AsyncSession, user_id: int, topic_id: int
) -> bool:
    """
    Verify if a user can access a topic offline.
    Freemium topics: always if downloaded.
    Premium topics: require active verified subscription.
    """
    from app.models.academic import Topic
    from app.models.progress import OfflineDownload

    # Get topic package
    topic_result = await db.execute(select(Topic).where(Topic.id == topic_id))
    topic = topic_result.scalar_one_or_none()
    if not topic:
        return False

    pkg_result = await db.execute(select(Package).where(Package.id == topic.package_id))
    pkg = pkg_result.scalar_one_or_none()

    if pkg and pkg.name == "freemium":
        # Check if downloaded
        dl_result = await db.execute(
            select(OfflineDownload).where(
                OfflineDownload.user_id == user_id,
                OfflineDownload.topic_id == topic_id,
                OfflineDownload.download_status == "completed",
            )
        )
        return dl_result.scalar_one_or_none() is not None

    # Premium: check subscription status with offline token
    access = await check_premium_access(db, user_id)
    return access["is_premium"] and access["has_access"]


async def create_subscription(
    db: AsyncSession,
    user_id: int,
    package_id: int,
    payment_reference: Optional[str] = None,
    duration_override_days: Optional[int] = None,
) -> Subscription:
    """Create or renew a subscription after successful payment."""
    pkg_result = await db.execute(select(Package).where(Package.id == package_id))
    pkg = pkg_result.scalar_one_or_none()
    if not pkg:
        raise ValueError("Package not found")

    start = date.today()
    end = None
    grace_end = None

    if pkg.duration_days:
        end = start + timedelta(days=duration_override_days or pkg.duration_days)
        grace_end = end + timedelta(days=pkg.grace_period_days)

    sub = Subscription(
        user_id=user_id,
        package_id=package_id,
        start_date=start,
        end_date=end,
        status="active",
        payment_reference=payment_reference,
        grace_period_end=grace_end,
        last_verified_at=datetime.now(timezone.utc),
        verification_token=generate_secure_token(16),
    )
    db.add(sub)
    await db.commit()
    await db.refresh(sub)
    return sub
