"""Firestore storage utilities for console data."""

import logging
import secrets
import hashlib
from typing import Any, Dict, List, Optional
from datetime import datetime
from google.cloud import firestore
from agenticlypay.config import config

logger = logging.getLogger(__name__)

# Initialize Firestore client
db = firestore.Client(project=config.project_id or "agenticlypay-com")


class FirestoreStorage:
    """Firestore-based storage for console data."""

    @staticmethod
    def get_collection(name: str):
        """Get a Firestore collection reference."""
        return db.collection(name)

    @staticmethod
    def create_project(user_id: str, name: str) -> Dict[str, Any]:
        """Create a new project."""
        project_ref = db.collection("projects").document()
        project_data = {
            "id": project_ref.id,
            "user_id": user_id,
            "name": name,
            "created_at": firestore.SERVER_TIMESTAMP,
            "updated_at": firestore.SERVER_TIMESTAMP,
        }
        project_ref.set(project_data)
        return {**project_data, "created_at": datetime.now().isoformat(), "updated_at": datetime.now().isoformat()}

    @staticmethod
    def get_projects(user_id: str) -> List[Dict[str, Any]]:
        """Get all projects for a user."""
        projects = db.collection("projects").where("user_id", "==", user_id).stream()
        result = []
        for doc in projects:
            data = doc.to_dict()
            if data:
                # Convert Firestore timestamps to ISO strings
                if "created_at" in data and hasattr(data["created_at"], "isoformat"):
                    data["created_at"] = data["created_at"].isoformat()
                if "updated_at" in data and hasattr(data["updated_at"], "isoformat"):
                    data["updated_at"] = data["updated_at"].isoformat()
                result.append(data)
        return result

    @staticmethod
    def get_project(project_id: str) -> Optional[Dict[str, Any]]:
        """Get a project by ID."""
        doc = db.collection("projects").document(project_id).get()
        if doc.exists:
            data = doc.to_dict()
            if data:
                if "created_at" in data and hasattr(data["created_at"], "isoformat"):
                    data["created_at"] = data["created_at"].isoformat()
                if "updated_at" in data and hasattr(data["updated_at"], "isoformat"):
                    data["updated_at"] = data["updated_at"].isoformat()
            return data
        return None

    @staticmethod
    def log_usage(email: str, endpoint: str, status_code: int) -> None:
        """Log API usage by email."""
        usage_ref = db.collection("usage").document()
        usage_data = {
            "email": email,
            "endpoint": endpoint,
            "status_code": status_code,
            "timestamp": firestore.SERVER_TIMESTAMP,
        }
        usage_ref.set(usage_data)

    @staticmethod
    def get_usage_by_email(email: str, last_days: int = 30) -> List[Dict[str, Any]]:
        """Get usage data for an email address."""
        from datetime import timedelta

        cutoff = datetime.now() - timedelta(days=last_days)
        # Simple query to avoid needing composite index
        usage = db.collection("usage").where("email", "==", email).stream()
        result = []
        for doc in usage:
            data = doc.to_dict()
            if data:
                # Filter by timestamp in Python
                ts = data.get("timestamp")
                if ts and hasattr(ts, "timestamp") and ts.timestamp() < cutoff.timestamp():
                    continue
                if "timestamp" in data and hasattr(data["timestamp"], "isoformat"):
                    data["timestamp"] = data["timestamp"].isoformat()
                result.append(data)
        # Sort by timestamp descending in Python
        result.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
        return result[:1000]

    @staticmethod
    def create_user(user_id: str, email: str, provider: str) -> Dict[str, Any]:
        """Create or update a user."""
        user_ref = db.collection("users").document(user_id)
        user_data = {
            "id": user_id,
            "email": email,
            "provider": provider,
            "created_at": firestore.SERVER_TIMESTAMP,
            "updated_at": firestore.SERVER_TIMESTAMP,
        }
        user_ref.set(user_data, merge=True)
        return user_data

    @staticmethod
    def get_user(user_id: str) -> Optional[Dict[str, Any]]:
        """Get a user by ID."""
        doc = db.collection("users").document(user_id).get()
        if doc.exists:
            return doc.to_dict()
        return None

    @staticmethod
    def get_1099_consent(email: str) -> Optional[Dict[str, Any]]:
        """Get 1099 electronic delivery consent status for an email."""
        doc = db.collection("1099_consents").document(email).get()
        if doc.exists:
            data = doc.to_dict()
            if data:
                if "consent_date" in data and hasattr(data["consent_date"], "isoformat"):
                    data["consent_date"] = data["consent_date"].isoformat()
                if "withdrawal_date" in data and hasattr(data["withdrawal_date"], "isoformat"):
                    data["withdrawal_date"] = data["withdrawal_date"].isoformat()
            return data
        return None

    @staticmethod
    def set_1099_consent(email: str, consented: bool, consent_date: datetime, method: str) -> None:
        """Store 1099 electronic delivery consent."""
        consent_ref = db.collection("1099_consents").document(email)
        consent_data = {
            "email": email,
            "consented": consented,
            "consent_date": consent_date,
            "method": method,  # e.g., "console", "email_link"
            "updated_at": firestore.SERVER_TIMESTAMP,
        }
        consent_ref.set(consent_data, merge=True)

    @staticmethod
    def withdraw_1099_consent(email: str) -> None:
        """Withdraw 1099 electronic delivery consent."""
        consent_ref = db.collection("1099_consents").document(email)
        consent_ref.update({
            "consented": False,
            "withdrawal_date": firestore.SERVER_TIMESTAMP,
            "updated_at": firestore.SERVER_TIMESTAMP,
        })

    @staticmethod
    def store_tax_info(email: str, tax_info: Dict[str, Any]) -> None:
        """Store tax information (SSN/EIN, address, name) for a developer."""
        tax_ref = db.collection("tax_information").document(email)
        tax_data = {
            "email": email,
            **tax_info,
            "updated_at": firestore.SERVER_TIMESTAMP,
        }
        tax_ref.set(tax_data, merge=True)

    @staticmethod
    def get_tax_info(email: str) -> Optional[Dict[str, Any]]:
        """Get tax information for a developer."""
        doc = db.collection("tax_information").document(email).get()
        if doc.exists:
            return doc.to_dict()
        return None

    @staticmethod
    def _compute_token_lookup_key(token: str) -> str:
        """Compute a lookup key for fast token verification.
        
        Uses the first 16 chars of SHA-256 hash of the token for O(1) lookup.
        This is collision-resistant enough for indexing while keeping keys short.
        """
        return hashlib.sha256(token.encode('utf-8')).hexdigest()[:16]

    @staticmethod
    def create_api_token(email: str, name: str) -> Dict[str, Any]:
        """Create a new API token for a developer.
        
        Args:
            email: Developer email address
            name: Human-readable name for the token
            
        Returns:
            Dictionary with token (plaintext, shown only once), id, name, created_at
        """
        logger.info("Creating API token for email: %s", email)
        
        # Generate secure random token (32 bytes = 256 bits, URL-safe base64)
        token = "agt_" + secrets.token_urlsafe(32)  # Prefix for AgenticlyPay tokens
        
        # Generate a salt and hash token with SHA-256 (salted) for secure storage
        salt = secrets.token_hex(16)  # 16 bytes = 32 hex chars
        token_hash = hashlib.sha256((salt + token).encode('utf-8')).hexdigest()
        stored_hash = f"{salt}:{token_hash}"
        
        # Generate lookup key for O(1) token verification
        lookup_key = FirestoreStorage._compute_token_lookup_key(token)
        
        # Store in Firestore with lookup key as document ID for fast retrieval
        token_ref = db.collection("api_tokens").document(lookup_key)
        
        token_data = {
            "id": lookup_key,
            "email": email,
            "name": name,
            "token_hash": stored_hash,
            "lookup_key": lookup_key,
            "created_at": firestore.SERVER_TIMESTAMP,
            "last_used_at": None,
            "is_active": True,
        }
        
        token_ref.set(token_data)
        logger.info("API token created successfully for email: %s", email)
        
        # Return token info (including plaintext token - shown only once)
        return {
            "token": token,
            "id": lookup_key,
            "name": name,
            "created_at": datetime.now().isoformat(),
        }

    @staticmethod
    def verify_api_token(token: str) -> Optional[Dict[str, Any]]:
        """Verify an API token and return its info.
        
        Uses O(1) lookup via token prefix hash, then verifies the full salted hash.
        
        Args:
            token: The plaintext token to verify
            
        Returns:
            Dictionary with token info including email, or None if invalid
        """
        if not token or not token.startswith("agt_"):
            return None
        
        # Compute lookup key for O(1) retrieval
        lookup_key = FirestoreStorage._compute_token_lookup_key(token)
        
        # Direct document lookup - O(1) instead of O(n)
        doc = db.collection("api_tokens").document(lookup_key).get()
        
        if not doc.exists:
            # Fallback: check for legacy tokens without lookup_key (migration path)
            return FirestoreStorage._verify_legacy_token(token)
        
        data = doc.to_dict()
        if not data or not data.get("is_active"):
            return None
        
        # Verify the full salted hash for security
        stored = data.get("token_hash", "")
        if ":" not in stored:
            return None
        
        try:
            salt, stored_hash = stored.split(":", 1)
            computed_hash = hashlib.sha256((salt + token).encode('utf-8')).hexdigest()
            if computed_hash != stored_hash:
                logger.warning("Token hash mismatch for lookup_key: %s", lookup_key)
                return None
            
            # Update last_used_at asynchronously (fire and forget)
            try:
                doc.reference.update({"last_used_at": firestore.SERVER_TIMESTAMP})
            except Exception:
                pass  # Don't fail verification if update fails
            
            return data
        except Exception as e:
            logger.error("Error verifying token: %s", str(e))
            return None

    @staticmethod
    def _verify_legacy_token(token: str) -> Optional[Dict[str, Any]]:
        """Fallback verification for tokens created before O(1) lookup was added.
        
        This iterates through all active tokens - only used during migration period.
        """
        logger.debug("Falling back to legacy token verification")
        tokens = db.collection("api_tokens").where("is_active", "==", True).stream()
        for doc in tokens:
            data = doc.to_dict()
            if data and data.get("token_hash") and not data.get("lookup_key"):
                try:
                    stored = data["token_hash"]
                    if ":" in stored:
                        salt, stored_hash = stored.split(":", 1)
                        computed_hash = hashlib.sha256((salt + token).encode('utf-8')).hexdigest()
                        if computed_hash == stored_hash:
                            # Migrate to new format with lookup_key
                            lookup_key = FirestoreStorage._compute_token_lookup_key(token)
                            doc.reference.update({
                                "lookup_key": lookup_key,
                                "last_used_at": firestore.SERVER_TIMESTAMP
                            })
                            logger.info("Migrated legacy token to O(1) lookup format")
                            return data
                except Exception:
                    continue
        return None

    @staticmethod
    def list_api_tokens(email: str) -> List[Dict[str, Any]]:
        """List all API tokens for a developer.
        
        Args:
            email: Developer email address
            
        Returns:
            List of token metadata (excludes token hashes and plaintext tokens)
        """
        logger.debug("Listing API tokens for email: %s", email)
        tokens = db.collection("api_tokens").where("email", "==", email).stream()
        result = []
        for doc in tokens:
            data = doc.to_dict()
            # Filter for active tokens only
            if data and data.get("is_active", True):
                # Exclude sensitive data (token_hash, lookup_key)
                token_info = {
                    "id": data.get("id"),
                    "name": data.get("name"),
                    "created_at": data.get("created_at"),
                    "last_used_at": data.get("last_used_at"),
                    "is_active": data.get("is_active", True),
                }
                # Convert timestamps
                if "created_at" in token_info and hasattr(token_info["created_at"], "isoformat"):
                    token_info["created_at"] = token_info["created_at"].isoformat()
                if "last_used_at" in token_info and token_info["last_used_at"] and hasattr(token_info["last_used_at"], "isoformat"):
                    token_info["last_used_at"] = token_info["last_used_at"].isoformat()
                result.append(token_info)
        logger.debug("Found %d tokens for email: %s", len(result), email)
        return result

    @staticmethod
    def revoke_api_token(email: str, token_id: str) -> None:
        """Revoke an API token.
        
        Args:
            email: Developer email address (for verification)
            token_id: Token ID to revoke
        """
        token_ref = db.collection("api_tokens").document(token_id)
        token_doc = token_ref.get()
        if token_doc.exists:
            token_data = token_doc.to_dict()
            # Verify the token belongs to this email
            if token_data and token_data.get("email") == email:
                token_ref.update({
                    "is_active": False,
                    "revoked_at": firestore.SERVER_TIMESTAMP,
                })

