#!/usr/bin/env python3
"""
Token Manager - Handle token balance operations.

Operations:
- Check balance, has_tokens
- Deduct tokens (for generation)
- Credit tokens (for purchases, admin grants)
- Refund tokens (on generation failure)
- Block/unblock users
"""

import os
import sqlite3
from pathlib import Path
from typing import Optional, Dict, List, Any

from database import (
    get_db_path,
    get_connection,
    init_db,
    get_user,
    get_or_create_user,
    update_user,
    create_transaction,
    get_transaction,
    get_user_transactions,
    get_transaction_by_task,
    get_promo_remaining,
    get_stats,
    # Giveaway functions
    get_giveaway_config,
    is_giveaway_active,
    get_giveaway_remaining,
    # Gift pack functions
    create_gift_pack,
    get_user_active_packs,
    redeem_gift_pack,
    has_received_gift_pack,
    set_gift_pack_received,
    get_matching_campaign,
    get_active_campaigns,
    decrement_campaign_slots,
)


# =============================================================================
# Balance Operations
# =============================================================================

def get_balance(user_id: int, db_path: Optional[Path] = None) -> int:
    """
    Get user's token balance.

    Returns:
        Token balance (0 if user doesn't exist).
    """
    user = get_user(user_id, db_path)
    return user["balance"] if user else 0


def has_tokens(user_id: int, count: int = 1, db_path: Optional[Path] = None) -> bool:
    """
    Check if user has at least `count` tokens.

    Returns:
        True if user has enough tokens.
    """
    return get_balance(user_id, db_path) >= count


def is_blocked(user_id: int, db_path: Optional[Path] = None) -> bool:
    """
    Check if user is blocked.

    Returns:
        True if user is blocked.
    """
    user = get_user(user_id, db_path)
    return bool(user and user.get("is_blocked"))


def is_promo_eligible(user_id: int, db_path: Optional[Path] = None) -> bool:
    """
    Check if user is eligible for promo pricing.

    Returns:
        True if user can buy at promo prices.
    """
    user = get_user(user_id, db_path)
    return bool(user and user.get("is_promo_eligible"))


def check_and_grant_giveaway(
    user_id: int,
    is_new_user: bool,
    db_path: Optional[Path] = None
) -> Dict[str, Any]:
    """
    Check if user should receive giveaway tokens and grant them if eligible.

    This function uses EXCLUSIVE transaction lock to prevent race conditions
    when multiple users sign up simultaneously.

    Args:
        user_id: Telegram user ID
        is_new_user: Whether this is a newly created user
        db_path: Optional database path

    Returns:
        Dict with:
            - granted: bool (whether tokens were granted)
            - tokens: int (number of tokens granted, 0 if not granted)
            - reason: str (reason for grant or no-grant)
    """
    # Quick check without lock - not a new user
    if not is_new_user:
        return {"granted": False, "tokens": 0, "reason": "existing_user"}

    # Quick check - giveaway not active
    if not is_giveaway_active(db_path):
        return {"granted": False, "tokens": 0, "reason": "giveaway_inactive"}

    # Use EXCLUSIVE transaction to prevent race conditions
    if db_path is None:
        db_path = get_db_path()

    conn = sqlite3.connect(str(db_path))
    conn.row_factory = sqlite3.Row

    try:
        # Start EXCLUSIVE transaction
        conn.execute("BEGIN EXCLUSIVE")

        cursor = conn.cursor()

        # Check if user already received giveaway
        cursor.execute(
            "SELECT giveaway_received FROM users WHERE user_id = ?",
            (user_id,)
        )
        row = cursor.fetchone()
        if not row:
            conn.rollback()
            return {"granted": False, "tokens": 0, "reason": "user_not_found"}

        if row["giveaway_received"] == 1:
            conn.rollback()
            return {"granted": False, "tokens": 0, "reason": "already_received"}

        # Check remaining slots
        cursor.execute(
            "SELECT value FROM giveaway_config WHERE key = 'remaining_users'"
        )
        row = cursor.fetchone()
        remaining = int(row["value"]) if row else 0

        if remaining <= 0:
            conn.rollback()
            return {"granted": False, "tokens": 0, "reason": "slots_exhausted"}

        # Get tokens amount
        cursor.execute(
            "SELECT value FROM giveaway_config WHERE key = 'tokens_per_user'"
        )
        row = cursor.fetchone()
        tokens = int(row["value"]) if row else 3

        # Get current balance
        cursor.execute("SELECT balance FROM users WHERE user_id = ?", (user_id,))
        row = cursor.fetchone()
        balance_before = row["balance"] if row else 0
        balance_after = balance_before + tokens

        # Grant tokens
        cursor.execute("""
            UPDATE users
            SET balance = ?,
                giveaway_received = 1,
                updated_at = CURRENT_TIMESTAMP
            WHERE user_id = ?
        """, (balance_after, user_id))

        # Decrement remaining counter
        cursor.execute("""
            UPDATE giveaway_config
            SET value = CAST((CAST(value AS INTEGER) - 1) AS TEXT)
            WHERE key = 'remaining_users'
        """)

        # Create transaction record
        cursor.execute("""
            INSERT INTO transactions (
                user_id, amount, balance_before, balance_after, type, description
            ) VALUES (?, ?, ?, ?, 'giveaway', ?)
        """, (
            user_id,
            tokens,
            balance_before,
            balance_after,
            f"Welcome bonus: {tokens} free tokens"
        ))

        conn.commit()

        return {
            "granted": True,
            "tokens": tokens,
            "reason": "success"
        }

    except Exception as e:
        conn.rollback()
        return {"granted": False, "tokens": 0, "reason": f"error: {str(e)}"}
    finally:
        conn.close()


# =============================================================================
# Gift Pack Operations
# =============================================================================

def check_and_grant_gift_pack(
    user_id: int,
    is_new_user: bool,
    ref_source: Optional[str] = None,
    db_path: Optional[Path] = None
) -> Dict[str, Any]:
    """
    Check if user should receive a gift pack and create it if eligible.

    Creates a pack (NOT balance tokens). Pack auto-unpacks when user sends a URL.

    Args:
        user_id: Telegram user ID
        is_new_user: Whether this is a newly created user
        ref_source: Referral source from deep link

    Returns:
        Dict with granted, tokens, expiration_days, reason.
    """
    # Quick check: already received
    if has_received_gift_pack(user_id, db_path):
        return {"granted": False, "tokens": 0, "reason": "already_received"}

    # Quick check: any active campaigns?
    campaigns = get_active_campaigns(db_path)
    if not campaigns:
        return {"granted": False, "tokens": 0, "reason": "no_campaigns"}

    # Find matching campaign
    campaign = get_matching_campaign(is_new_user, ref_source, db_path)
    if not campaign:
        return {"granted": False, "tokens": 0, "reason": "no_matching_campaign"}

    # Use EXCLUSIVE transaction to prevent race conditions
    if db_path is None:
        db_path = get_db_path()

    conn = sqlite3.connect(str(db_path))
    conn.row_factory = sqlite3.Row

    try:
        conn.execute("BEGIN EXCLUSIVE")
        cursor = conn.cursor()

        # Re-check gift_pack_received inside lock
        cursor.execute(
            "SELECT gift_pack_received FROM users WHERE user_id = ?",
            (user_id,)
        )
        row = cursor.fetchone()
        if not row:
            conn.rollback()
            return {"granted": False, "tokens": 0, "reason": "user_not_found"}

        if row["gift_pack_received"] == 1:
            conn.rollback()
            return {"granted": False, "tokens": 0, "reason": "already_received"}

        # Re-check campaign slots inside lock
        cursor.execute(
            "SELECT remaining_slots, is_active FROM gift_pack_campaigns WHERE id = ?",
            (campaign["id"],)
        )
        camp_row = cursor.fetchone()
        if not camp_row or not camp_row["is_active"]:
            conn.rollback()
            return {"granted": False, "tokens": 0, "reason": "campaign_inactive"}

        # remaining_slots=-1 means unlimited
        if camp_row["remaining_slots"] != -1 and camp_row["remaining_slots"] <= 0:
            conn.rollback()
            return {"granted": False, "tokens": 0, "reason": "slots_exhausted"}

        tokens = campaign["tokens"]
        exp_days = campaign["expiration_days"]
        camp_id = f"camp_{campaign['id']}"

        # Create the gift pack
        cursor.execute("""
            INSERT INTO gift_packs (user_id, tokens, expires_at, campaign_id, description, created_by)
            VALUES (?, ?, datetime('now', '+' || ? || ' days'), ?, ?, NULL)
        """, (user_id, tokens, exp_days, camp_id,
              f"Auto: {campaign['campaign_type']} campaign #{campaign['id']}"))

        # Mark user as having received a gift pack
        cursor.execute(
            "UPDATE users SET gift_pack_received = 1, updated_at = CURRENT_TIMESTAMP WHERE user_id = ?",
            (user_id,)
        )

        # Decrement campaign slots if limited (remaining_slots > 0)
        if camp_row["remaining_slots"] > 0:
            cursor.execute("""
                UPDATE gift_pack_campaigns
                SET remaining_slots = remaining_slots - 1
                WHERE id = ? AND remaining_slots > 0
            """, (campaign["id"],))

        conn.commit()

        return {
            "granted": True,
            "tokens": tokens,
            "expiration_days": exp_days,
            "reason": "success"
        }

    except Exception as e:
        conn.rollback()
        return {"granted": False, "tokens": 0, "reason": f"error: {str(e)}"}
    finally:
        conn.close()


def check_and_redeem_gift_pack(
    user_id: int,
    db_path: Optional[Path] = None
) -> Dict[str, Any]:
    """
    Auto-unpack oldest active gift pack, adding tokens to user's balance.

    Returns:
        Dict with redeemed, tokens, pack_id.
    """
    if db_path is None:
        db_path = get_db_path()

    conn = sqlite3.connect(str(db_path))
    conn.row_factory = sqlite3.Row

    try:
        conn.execute("BEGIN EXCLUSIVE")
        cursor = conn.cursor()

        # Find oldest active non-expired pack
        cursor.execute("""
            SELECT * FROM gift_packs
            WHERE user_id = ? AND status = 'active' AND expires_at > datetime('now')
            ORDER BY expires_at ASC LIMIT 1
        """, (user_id,))
        pack = cursor.fetchone()

        if not pack:
            conn.rollback()
            return {"redeemed": False, "tokens": 0}

        pack = dict(pack)
        tokens = pack["tokens"]
        pack_id = pack["id"]

        # Get current balance
        cursor.execute("SELECT balance FROM users WHERE user_id = ?", (user_id,))
        row = cursor.fetchone()
        if not row:
            conn.rollback()
            return {"redeemed": False, "tokens": 0}

        balance_before = row["balance"]
        balance_after = balance_before + tokens

        # Update user balance
        cursor.execute("""
            UPDATE users
            SET balance = ?,
                updated_at = CURRENT_TIMESTAMP
            WHERE user_id = ?
        """, (balance_after, user_id))

        # Mark pack as redeemed
        cursor.execute("""
            UPDATE gift_packs
            SET status = 'redeemed', redeemed_at = CURRENT_TIMESTAMP
            WHERE id = ?
        """, (pack_id,))

        # Create transaction record
        cursor.execute("""
            INSERT INTO transactions (
                user_id, amount, balance_before, balance_after, type, description
            ) VALUES (?, ?, ?, ?, 'gift_pack_redeem', ?)
        """, (
            user_id,
            tokens,
            balance_before,
            balance_after,
            f"Gift pack #{pack_id} redeemed: +{tokens} tokens"
        ))

        conn.commit()

        return {
            "redeemed": True,
            "tokens": tokens,
            "pack_id": pack_id
        }

    except Exception as e:
        conn.rollback()
        return {"redeemed": False, "tokens": 0, "reason": f"error: {str(e)}"}
    finally:
        conn.close()


# =============================================================================
# Token Credit/Debit
# =============================================================================

def deduct_token(
    user_id: int,
    task_id: str,
    product_id: str,
    description: Optional[str] = None,
    db_path: Optional[Path] = None
) -> Optional[int]:
    """
    Deduct 1 token from user for video generation.

    Args:
        user_id: Telegram user ID
        task_id: Video generation task ID
        product_id: WB product ID
        description: Optional description

    Returns:
        Transaction ID if successful, None if insufficient balance.
    """
    user = get_user(user_id, db_path)
    if not user:
        return None

    balance_before = user["balance"]
    if balance_before < 1:
        return None

    balance_after = balance_before - 1

    # Update user balance
    with get_connection(db_path) as conn:
        cursor = conn.cursor()
        cursor.execute("""
            UPDATE users
            SET balance = ?,
                total_spent = total_spent + 1,
                updated_at = CURRENT_TIMESTAMP
            WHERE user_id = ?
        """, (balance_after, user_id))

    # Create transaction
    txn_id = create_transaction(
        user_id=user_id,
        amount=-1,
        balance_before=balance_before,
        balance_after=balance_after,
        txn_type="generation",
        description=description or f"Video generation for product {product_id}",
        task_id=task_id,
        product_id=product_id,
        db_path=db_path
    )

    return txn_id


def refund_token(
    user_id: int,
    task_id: str,
    reason: str,
    db_path: Optional[Path] = None
) -> bool:
    """
    Refund 1 token to user (on generation failure).

    Args:
        user_id: Telegram user ID
        task_id: Video generation task ID
        reason: Reason for refund

    Returns:
        True if refund was successful.
    """
    user = get_user(user_id, db_path)
    if not user:
        return False

    # Check if there's a deduction transaction for this task
    original_txn = get_transaction_by_task(task_id, db_path)
    if not original_txn:
        return False

    # Already refunded?
    with get_connection(db_path) as conn:
        cursor = conn.cursor()
        cursor.execute("""
            SELECT id FROM transactions
            WHERE task_id = ? AND type = 'refund'
        """, (task_id,))
        if cursor.fetchone():
            return False  # Already refunded

    balance_before = user["balance"]
    balance_after = balance_before + 1

    # Update user balance
    with get_connection(db_path) as conn:
        cursor = conn.cursor()
        cursor.execute("""
            UPDATE users
            SET balance = ?,
                total_spent = total_spent - 1,
                updated_at = CURRENT_TIMESTAMP
            WHERE user_id = ?
        """, (balance_after, user_id))

    # Create refund transaction
    create_transaction(
        user_id=user_id,
        amount=1,
        balance_before=balance_before,
        balance_after=balance_after,
        txn_type="refund",
        description=f"Refund for task {task_id}: {reason}",
        task_id=task_id,
        product_id=original_txn.get("product_id"),
        db_path=db_path
    )

    return True


def credit_tokens(
    user_id: int,
    amount: int,
    txn_type: str,
    description: str,
    telegram_payment_charge_id: Optional[str] = None,
    provider_payment_charge_id: Optional[str] = None,
    amount_rub: Optional[int] = None,
    initiated_by: Optional[int] = None,
    db_path: Optional[Path] = None
) -> bool:
    """
    Credit tokens to user.

    Args:
        user_id: Telegram user ID
        amount: Number of tokens to credit
        txn_type: Transaction type ('purchase', 'admin_grant', 'migration')
        description: Transaction description
        telegram_payment_charge_id: Telegram payment ID (for purchases)
        provider_payment_charge_id: Provider payment ID (for purchases)
        amount_rub: Amount in kopecks (for purchases)
        initiated_by: Admin user ID (for admin grants)

    Returns:
        True if successful.
    """
    user = get_user(user_id, db_path)
    if not user:
        return False

    balance_before = user["balance"]
    balance_after = balance_before + amount

    # Update user balance and totals
    with get_connection(db_path) as conn:
        cursor = conn.cursor()

        if txn_type == "purchase":
            cursor.execute("""
                UPDATE users
                SET balance = ?,
                    total_purchased = total_purchased + ?,
                    promo_purchases = promo_purchases + 1,
                    updated_at = CURRENT_TIMESTAMP
                WHERE user_id = ?
            """, (balance_after, amount, user_id))
        else:
            cursor.execute("""
                UPDATE users
                SET balance = ?,
                    updated_at = CURRENT_TIMESTAMP
                WHERE user_id = ?
            """, (balance_after, user_id))

    # Create transaction
    create_transaction(
        user_id=user_id,
        amount=amount,
        balance_before=balance_before,
        balance_after=balance_after,
        txn_type=txn_type,
        description=description,
        telegram_payment_charge_id=telegram_payment_charge_id,
        provider_payment_charge_id=provider_payment_charge_id,
        amount_rub=amount_rub,
        currency="RUB" if amount_rub else None,
        initiated_by=initiated_by,
        db_path=db_path
    )

    return True


def revoke_tokens(
    user_id: int,
    amount: int,
    reason: str,
    initiated_by: int,
    db_path: Optional[Path] = None
) -> bool:
    """
    Revoke tokens from user (admin action).

    Args:
        user_id: Telegram user ID
        amount: Number of tokens to revoke
        reason: Reason for revocation
        initiated_by: Admin user ID

    Returns:
        True if successful, False if user doesn't have enough tokens.
    """
    user = get_user(user_id, db_path)
    if not user:
        return False

    balance_before = user["balance"]
    if balance_before < amount:
        return False

    balance_after = balance_before - amount

    # Update user balance
    with get_connection(db_path) as conn:
        cursor = conn.cursor()
        cursor.execute("""
            UPDATE users
            SET balance = ?,
                updated_at = CURRENT_TIMESTAMP
            WHERE user_id = ?
        """, (balance_after, user_id))

    # Create transaction
    create_transaction(
        user_id=user_id,
        amount=-amount,
        balance_before=balance_before,
        balance_after=balance_after,
        txn_type="admin_revoke",
        description=reason,
        initiated_by=initiated_by,
        db_path=db_path
    )

    return True


# =============================================================================
# User Management
# =============================================================================

def block_user(
    user_id: int,
    reason: str,
    initiated_by: int,
    db_path: Optional[Path] = None
) -> bool:
    """Block a user."""
    return update_user(user_id, {"is_blocked": 1}, db_path)


def unblock_user(
    user_id: int,
    db_path: Optional[Path] = None
) -> bool:
    """Unblock a user."""
    return update_user(user_id, {"is_blocked": 0}, db_path)


def get_user_info(user_id: int, db_path: Optional[Path] = None) -> Optional[Dict[str, Any]]:
    """
    Get detailed user info including recent transactions.

    Returns:
        Dict with user data and transactions.
    """
    user = get_user(user_id, db_path)
    if not user:
        return None

    transactions = get_user_transactions(user_id, limit=10, db_path=db_path)

    return {
        "user": user,
        "transactions": transactions,
    }


# =============================================================================
# Migration
# =============================================================================

def migrate_user(
    user_id: int,
    initial_tokens: int,
    username: Optional[str] = None,
    first_name: Optional[str] = None,
    db_path: Optional[Path] = None
) -> bool:
    """
    Migrate an existing user from whitelist.

    Creates user if not exists and grants initial tokens.
    """
    # Create or get user
    user = get_or_create_user(user_id, username, first_name, db_path=db_path)

    # Only grant tokens if balance is 0 (not already migrated)
    if user["balance"] == 0 and initial_tokens > 0:
        return credit_tokens(
            user_id=user_id,
            amount=initial_tokens,
            txn_type="migration",
            description=f"Migration from whitelist - {initial_tokens} tokens",
            db_path=db_path
        )

    return True


# =============================================================================
# CLI
# =============================================================================

if __name__ == "__main__":
    import sys

    # Initialize database
    init_db()

    if len(sys.argv) < 2:
        print("Usage:")
        print("  python token_manager.py balance <user_id>")
        print("  python token_manager.py grant <user_id> <amount> [reason]")
        print("  python token_manager.py revoke <user_id> <amount> [reason]")
        print("  python token_manager.py info <user_id>")
        print("  python token_manager.py migrate <user_id> <tokens>")
        sys.exit(1)

    cmd = sys.argv[1]

    if cmd == "balance":
        user_id = int(sys.argv[2])
        balance = get_balance(user_id)
        print(f"User {user_id} balance: {balance} tokens")

    elif cmd == "grant":
        user_id = int(sys.argv[2])
        amount = int(sys.argv[3])
        reason = sys.argv[4] if len(sys.argv) > 4 else "CLI grant"

        # Ensure user exists
        get_or_create_user(user_id)

        if credit_tokens(user_id, amount, "admin_grant", reason, initiated_by=0):
            print(f"Granted {amount} tokens to user {user_id}")
            print(f"New balance: {get_balance(user_id)}")
        else:
            print("Failed to grant tokens")

    elif cmd == "revoke":
        user_id = int(sys.argv[2])
        amount = int(sys.argv[3])
        reason = sys.argv[4] if len(sys.argv) > 4 else "CLI revoke"

        if revoke_tokens(user_id, amount, reason, initiated_by=0):
            print(f"Revoked {amount} tokens from user {user_id}")
            print(f"New balance: {get_balance(user_id)}")
        else:
            print("Failed to revoke tokens (insufficient balance?)")

    elif cmd == "info":
        user_id = int(sys.argv[2])
        info = get_user_info(user_id)
        if info:
            user = info["user"]
            print(f"\n👤 User {user_id}:")
            print(f"  Username: @{user.get('username') or 'N/A'}")
            print(f"  Name: {user.get('first_name', '')} {user.get('last_name', '')}")
            print(f"  Balance: {user['balance']} tokens")
            print(f"  Total purchased: {user['total_purchased']}")
            print(f"  Total spent: {user['total_spent']}")
            print(f"  Promo eligible: {'Yes' if user['is_promo_eligible'] else 'No'}")
            print(f"  Blocked: {'Yes' if user['is_blocked'] else 'No'}")
            print(f"  Created: {user['created_at']}")

            print(f"\n📜 Recent transactions:")
            for txn in info["transactions"]:
                sign = "+" if txn["amount"] > 0 else ""
                print(f"  [{txn['created_at']}] {sign}{txn['amount']} ({txn['type']})")
                if txn.get("description"):
                    print(f"    {txn['description'][:60]}")
        else:
            print(f"User {user_id} not found")

    elif cmd == "migrate":
        user_id = int(sys.argv[2])
        tokens = int(sys.argv[3])

        if migrate_user(user_id, tokens):
            print(f"Migrated user {user_id} with {tokens} tokens")
            print(f"Balance: {get_balance(user_id)}")
        else:
            print("Migration failed")

    else:
        print(f"Unknown command: {cmd}")
        sys.exit(1)
