#!/usr/bin/env python3
"""
Tier Reconciliation Script

Compares subscription tiers between Clerk (source of truth for billing)
and usage_quota_model (backend tracking) to detect and optionally fix drift.

Usage:
    # Check for drift without fixing
    python -m fmatch.saas.scripts.reconcile_tiers --check-only

    # Check and auto-fix drift
    python -m fmatch.saas.scripts.reconcile_tiers --auto-fix

    # Dry run (show what would be fixed)
    python -m fmatch.saas.scripts.reconcile_tiers --dry-run
"""

import argparse
import asyncio
import logging
import os
import sys
from datetime import datetime
from typing import Any, Dict, List, Optional

import httpx
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker

# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))

from fmatch.saas.model_defs.usage_quota_model import UsageQuotaModel
from fmatch.saas.settings import settings as saas_settings

logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
log = logging.getLogger("reconcile_tiers")


class TierDrift:
    """Represents a detected tier drift."""

    def __init__(
        self,
        account_id: str,
        clerk_tier: str,
        db_tier: str,
        clerk_status: Optional[str] = None,
    ):
        self.account_id = account_id
        self.clerk_tier = clerk_tier
        self.db_tier = db_tier
        self.clerk_status = clerk_status

    def __repr__(self):
        return (
            f"TierDrift(account={self.account_id}, "
            f"clerk={self.clerk_tier}, db={self.db_tier})"
        )


async def get_clerk_tier(user_id: str, clerk_secret_key: str) -> Dict[str, Any]:
    """
    Fetch user's subscription tier from Clerk API.

    Args:
        user_id: Clerk user ID
        clerk_secret_key: Clerk secret key for API auth

    Returns:
        {
            "tier": "free"|"pro"|"scale",
            "status": "active"|"canceled"|etc,
            "source": "clerk_metadata"
        }
    """
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"https://api.clerk.com/v1/users/{user_id}",
            headers={"Authorization": f"Bearer {clerk_secret_key}"},
            timeout=10.0,
        )
        response.raise_for_status()
        user_data = response.json()

        # Extract tier from publicMetadata
        public_metadata = user_data.get("public_metadata", {})
        subscription = public_metadata.get("subscription", {})

        tier = subscription.get("tier", "free")
        status = subscription.get("status", "unknown")

        return {
            "tier": tier,
            "status": status,
            "source": "clerk_metadata",
        }


async def get_db_tier(account_id: str, db: AsyncSession) -> Optional[str]:
    """
    Fetch user's tier from usage_quota_model.

    Args:
        account_id: User's account ID (Clerk user ID)
        db: Database session

    Returns:
        Tier string or None if user not found
    """
    stmt = select(UsageQuotaModel).where(UsageQuotaModel.account_id == account_id)
    result = await db.execute(stmt)
    quota = result.scalar_one_or_none()

    if not quota:
        return None

    return quota.tier


async def fix_tier_drift(
    drift: TierDrift,
    db: AsyncSession,
    dry_run: bool = False,
) -> bool:
    """
    Fix tier drift by updating usage_quota_model to match Clerk.

    Args:
        drift: TierDrift instance
        db: Database session
        dry_run: If True, log what would be done without making changes

    Returns:
        True if fixed (or would be fixed in dry run), False otherwise
    """
    if dry_run:
        log.info(
            f"[DRY RUN] Would update {drift.account_id}: "
            f"{drift.db_tier} → {drift.clerk_tier}"
        )
        return True

    stmt = select(UsageQuotaModel).where(UsageQuotaModel.account_id == drift.account_id)
    result = await db.execute(stmt)
    quota = result.scalar_one_or_none()

    if not quota:
        log.warning(f"User {drift.account_id} not found in DB, cannot fix")
        return False

    quota.tier = drift.clerk_tier
    await db.commit()
    await db.refresh(quota)

    log.info(
        f"Fixed tier for {drift.account_id}: " f"{drift.db_tier} → {drift.clerk_tier}"
    )
    return True


async def reconcile_tiers(
    check_only: bool = False,
    auto_fix: bool = False,
    dry_run: bool = False,
) -> Dict[str, Any]:
    """
    Main reconciliation function.

    Args:
        check_only: Only detect drift, don't fix
        auto_fix: Automatically fix detected drift
        dry_run: Log fixes without making changes

    Returns:
        Summary dictionary with stats
    """
    clerk_secret_key = os.getenv("CLERK_SECRET_KEY")
    if not clerk_secret_key:
        log.error("CLERK_SECRET_KEY not set in environment")
        return {"error": "CLERK_SECRET_KEY not configured"}

    # Create async database session
    engine = create_async_engine(saas_settings.SQLALCHEMY_DATABASE_URI)
    async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

    drifts: List[TierDrift] = []
    checked_count = 0
    fixed_count = 0
    error_count = 0

    async with async_session() as db:
        # Get all users from usage_quota_model
        stmt = select(UsageQuotaModel)
        result = await db.execute(stmt)
        quotas = result.scalars().all()

        log.info(f"Checking {len(quotas)} users for tier drift...")

        for quota in quotas:
            checked_count += 1
            account_id = quota.account_id
            db_tier = quota.tier

            try:
                # Fetch tier from Clerk
                clerk_data = await get_clerk_tier(account_id, clerk_secret_key)
                clerk_tier = clerk_data["tier"]
                clerk_status = clerk_data["status"]

                # Compare tiers
                if clerk_tier != db_tier:
                    drift = TierDrift(
                        account_id=account_id,
                        clerk_tier=clerk_tier,
                        db_tier=db_tier,
                        clerk_status=clerk_status,
                    )
                    drifts.append(drift)

                    log.warning(
                        f"Tier drift detected: {account_id} | "
                        f"Clerk: {clerk_tier} ({clerk_status}) | DB: {db_tier}"
                    )

                    # Fix if requested
                    if auto_fix or dry_run:
                        if await fix_tier_drift(drift, db, dry_run=dry_run):
                            fixed_count += 1

            except httpx.HTTPStatusError as e:
                log.error(f"Failed to fetch Clerk data for {account_id}: {e}")
                error_count += 1
            except Exception as e:
                log.error(f"Error processing {account_id}: {e}")
                error_count += 1

    # Generate summary
    summary = {
        "timestamp": datetime.utcnow().isoformat(),
        "checked_count": checked_count,
        "drift_count": len(drifts),
        "fixed_count": fixed_count,
        "error_count": error_count,
        "mode": "check_only" if check_only else ("dry_run" if dry_run else "auto_fix"),
        "drifts": [
            {
                "account_id": d.account_id,
                "clerk_tier": d.clerk_tier,
                "db_tier": d.db_tier,
                "clerk_status": d.clerk_status,
            }
            for d in drifts
        ],
    }

    log.info("=" * 80)
    log.info("Reconciliation Summary")
    log.info("=" * 80)
    log.info(f"Users checked: {checked_count}")
    log.info(f"Drifts detected: {len(drifts)}")
    log.info(f"Drifts fixed: {fixed_count}")
    log.info(f"Errors: {error_count}")
    log.info("=" * 80)

    if drifts and check_only:
        log.warning(
            "Run with --auto-fix to fix these drifts, "
            "or --dry-run to see what would happen"
        )

    return summary


async def main():
    parser = argparse.ArgumentParser(
        description="Reconcile subscription tiers between Clerk and backend"
    )
    mode_group = parser.add_mutually_exclusive_group(required=True)
    mode_group.add_argument(
        "--check-only", action="store_true", help="Only check for drift, don't fix"
    )
    mode_group.add_argument(
        "--auto-fix", action="store_true", help="Automatically fix detected drift"
    )
    mode_group.add_argument(
        "--dry-run",
        action="store_true",
        help="Show what would be fixed without making changes",
    )

    args = parser.parse_args()

    summary = await reconcile_tiers(
        check_only=args.check_only,
        auto_fix=args.auto_fix,
        dry_run=args.dry_run,
    )

    # Exit with error code if drifts detected or errors occurred
    if summary.get("drift_count", 0) > 0 or summary.get("error_count", 0) > 0:
        sys.exit(1)
    sys.exit(0)


if __name__ == "__main__":
    asyncio.run(main())
