from __future__ import annotations

from datetime import datetime, timezone
from typing import Dict, List

from firebase_admin import firestore as admin_firestore

from app.services.firestore import get_firestore_client

USER_CREDITS_COLLECTION = "userCredits"
CREDIT_LOGS_COLLECTION = "creditLogs"

FREE_CREDITS_PER_MONTH = 1000


def _current_month_first_day(now: datetime) -> datetime:
    return datetime(now.year, now.month, 1, tzinfo=timezone.utc)


def _next_month_first_day(now: datetime) -> datetime:
    if now.month == 12:
        return datetime(now.year + 1, 1, 1, tzinfo=timezone.utc)
    return datetime(now.year, now.month + 1, 1, tzinfo=timezone.utc)


def _initialize_user_credits(user_id: str) -> Dict:
    db = get_firestore_client()
    doc_ref = db.collection(USER_CREDITS_COLLECTION).document(user_id)
    now = datetime.now(timezone.utc)
    payload = {
        "userId": user_id,
        "credits": FREE_CREDITS_PER_MONTH,
        "totalCreditsUsed": 0,
        "plan": "free",
        "lastResetDate": _current_month_first_day(now),
        "nextResetDate": _next_month_first_day(now),
        "createdAt": now,
        "updatedAt": now,
    }
    doc_ref.set(payload)
    return payload


def get_credit_summary(user_id: str) -> Dict:
    db = get_firestore_client()
    doc_ref = db.collection(USER_CREDITS_COLLECTION).document(user_id)
    snapshot = doc_ref.get()

    if not snapshot.exists:
        data = _initialize_user_credits(user_id)
    else:
        data = snapshot.to_dict()
        now = datetime.now(timezone.utc)
        last_reset = data.get("lastResetDate")
        if hasattr(last_reset, "to_datetime"):
            last_reset = last_reset.to_datetime()

        if last_reset:
            if now.month != last_reset.month or now.year != last_reset.year:
                update_payload = {
                    "lastResetDate": _current_month_first_day(now),
                    "nextResetDate": _next_month_first_day(now),
                    "updatedAt": now,
                }
                if data.get("credits", 0) < FREE_CREDITS_PER_MONTH:
                    update_payload.update({
                        "credits": FREE_CREDITS_PER_MONTH,
                        "totalCreditsUsed": 0,
                    })
                doc_ref.update(update_payload)
                data.update(update_payload)

    # Normalize timestamps to datetime
    for key in ("createdAt", "updatedAt", "lastResetDate", "nextResetDate"):
        value = data.get(key)
        if hasattr(value, "to_datetime"):
            data[key] = value.to_datetime()

    return {
        "userId": user_id,
        "credits": data.get("credits", 0),
        "totalCreditsUsed": data.get("totalCreditsUsed", 0),
        "plan": data.get("plan", "free"),
        "lastResetDate": data.get("lastResetDate"),
        "nextResetDate": data.get("nextResetDate"),
    }


def get_credit_logs(user_id: str, limit: int = 50) -> List[Dict]:
    db = get_firestore_client()
    logs_ref = db.collection(CREDIT_LOGS_COLLECTION)
    query = (
        logs_ref.where("userId", "==", user_id)
        .order_by("timestamp", direction=admin_firestore.Query.DESCENDING)
        .limit(limit)
    )

    logs: List[Dict] = []
    for doc in query.stream():
        data = doc.to_dict()
        timestamp = data.get("timestamp")
        if hasattr(timestamp, "to_datetime"):
            timestamp = timestamp.to_datetime()
        logs.append(
            {
                "id": doc.id,
                "amount": data.get("amount", 0),
                "type": data.get("type"),
                "description": data.get("description", ""),
                "timestamp": timestamp,
            }
        )
    return logs

CREDIT_COSTS = {
    "firewall": 1,
    "scanner_category": 50,
    "pii_redaction": 1,
}


class CreditError(Exception):
    """Base credit exception."""


class InsufficientCreditsError(CreditError):
    """Raised when a user lacks sufficient credits."""


def log_credit_transaction(user_id: str, amount: int, type_name: str, description: str) -> None:
    db = get_firestore_client()
    logs_ref = db.collection(CREDIT_LOGS_COLLECTION)
    logs_ref.add(
        {
            "userId": user_id,
            "amount": amount,
            "type": type_name,
            "description": description,
            "timestamp": datetime.now(timezone.utc),
        }
    )


@admin_firestore.transactional
def _update_credit_transaction(transaction, doc_ref, amount: int) -> Dict:
    snapshot = doc_ref.get(transaction=transaction)
    if not snapshot.exists:
        raise CreditError("User credits are not initialized")

    data = snapshot.to_dict()
    credits = data.get("credits", 0)
    if credits < amount:
        raise InsufficientCreditsError("Insufficient credits")

    new_credits = credits - amount
    new_total_used = data.get("totalCreditsUsed", 0) + amount

    transaction.update(
        doc_ref,
        {
            "credits": new_credits,
            "totalCreditsUsed": new_total_used,
            "updatedAt": datetime.now(timezone.utc),
        },
    )

    data["credits"] = new_credits
    data["totalCreditsUsed"] = new_total_used
    return data


def deduct_credits(user_id: str, amount: int, type_name: str, description: str) -> Dict:
    if amount <= 0:
        return get_credit_summary(user_id)

    db = get_firestore_client()
    doc_ref = db.collection(USER_CREDITS_COLLECTION).document(user_id)
    transaction = db.transaction()

    try:
        updated_data = _update_credit_transaction(transaction, doc_ref, amount)
    except InsufficientCreditsError:
        raise
    except CreditError:
        # Initialize and retry once
        _initialize_user_credits(user_id)
        updated_data = _update_credit_transaction(transaction, doc_ref, amount)

    log_credit_transaction(user_id, -amount, type_name, description)

    return {
        "userId": user_id,
        "credits": updated_data.get("credits", 0),
        "totalCreditsUsed": updated_data.get("totalCreditsUsed", 0),
        "plan": updated_data.get("plan", "free"),
        "lastResetDate": _to_datetime(updated_data.get("lastResetDate")),
        "nextResetDate": _to_datetime(updated_data.get("nextResetDate")),
    }


def deduct_firewall_credits(user_id: str, checks_count: int) -> Dict:
    amount = checks_count * CREDIT_COSTS["firewall"]
    return deduct_credits(user_id, amount, "firewall", f"Firewall check x{checks_count}")


def deduct_scanner_credits(user_id: str, categories_count: int) -> Dict:
    amount = categories_count * CREDIT_COSTS["scanner_category"]
    return deduct_credits(user_id, amount, "scanner", f"Scanner categories x{categories_count}")


def deduct_pii_redaction_credits(user_id: str) -> Dict:
    amount = CREDIT_COSTS["pii_redaction"]
    return deduct_credits(user_id, amount, "pii_redaction", "PII redaction operation")


def _to_datetime(value):
    if value is None:
        return None
    if hasattr(value, "to_datetime"):
        return value.to_datetime()
    return value
