"""
Klippekort (Prepaid Time Card) Service
=======================================

Business logic for prepaid time cards: purchase, balance, deduction.

NOTE: As of migration 065, customers can have multiple active cards simultaneously.
      When multiple active cards exist, operations default to the card with earliest expiry.
"""

import logging
from datetime import datetime
from decimal import Decimal
from typing import Optional, Dict, Any, List

from app.core.database import execute_query, execute_insert, execute_update, execute_query_single
from app.ticket.backend.models import (
    TPrepaidCard,
    TPrepaidCardCreate,
    TPrepaidCardUpdate,
    TPrepaidCardWithStats,
    TPrepaidTransaction,
    TPrepaidTransactionCreate,
    PrepaidCardStatus,
    TransactionType
)

logger = logging.getLogger(__name__)


class KlippekortService:
    """Service for prepaid card operations"""
    
    @staticmethod
    def purchase_card(
        card_data: TPrepaidCardCreate,
        user_id: Optional[int] = None
    ) -> Dict[str, Any]:
        """
        Purchase a new prepaid card
        
        Note: As of migration 065, customers can have multiple active cards simultaneously.
        
        Args:
            card_data: Card purchase data
            user_id: User making purchase
            
        Returns:
            Created card dict
        """
        from psycopg2.extras import Json
        
        logger.info(f"💳 Purchasing prepaid card for customer {card_data.customer_id}: {card_data.purchased_hours}h")
        
        # Insert card (trigger will auto-generate card_number if NULL)
        card_id = execute_insert(
            """
            INSERT INTO tticket_prepaid_cards (
                card_number, customer_id, purchased_hours, price_per_hour, total_amount,
                status, expires_at, notes, economic_invoice_number, economic_product_number,
                created_by_user_id
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            """,
            (
                card_data.card_number,
                card_data.customer_id,
                card_data.purchased_hours,
                card_data.price_per_hour,
                card_data.total_amount,
                'active',
                card_data.expires_at,
                card_data.notes,
                card_data.economic_invoice_number,
                card_data.economic_product_number,
                user_id or card_data.created_by_user_id
            )
        )
        
        # Create initial transaction
        execute_insert(
            """
            INSERT INTO tticket_prepaid_transactions 
            (card_id, transaction_type, hours, balance_after, description, created_by_user_id)
            VALUES (%s, %s, %s, %s, %s, %s)
            """,
            (
                card_id,
                'purchase',
                card_data.purchased_hours,
                card_data.purchased_hours,
                f"Initial purchase: {card_data.purchased_hours}h @ {card_data.price_per_hour} DKK/h",
                user_id
            )
        )
        
        # Fetch created card
        card = execute_query_single(
            "SELECT * FROM tticket_prepaid_cards WHERE id = %s",
            (card_id,))
        
        logger.info(f"✅ Created prepaid card {card['card_number']} (ID: {card_id})")
        return card
    
    @staticmethod
    def get_card(card_id: int) -> Optional[Dict[str, Any]]:
        """Get prepaid card by ID"""
        return execute_query_single(
            "SELECT * FROM tticket_prepaid_cards WHERE id = %s",
            (card_id,))
    
    @staticmethod
    def get_card_with_stats(card_id: int) -> Optional[Dict[str, Any]]:
        """Get prepaid card with usage statistics"""
        return execute_query_single(
            "SELECT * FROM tticket_prepaid_balances WHERE id = %s",
            (card_id,))
    
    @staticmethod
    def get_active_cards_for_customer(customer_id: int) -> List[Dict[str, Any]]:
        """
        Get all active prepaid cards for customer (sorted by expiry)
        
        Returns empty list if no active cards exist.
        """
        cards = execute_query(
            """
            SELECT * FROM tticket_prepaid_cards 
            WHERE customer_id = %s AND status = 'active'
            ORDER BY expires_at ASC NULLS LAST, created_at ASC
            """,
            (customer_id,))
        return cards or []
    
    @staticmethod
    def get_active_card_for_customer(customer_id: int) -> Optional[Dict[str, Any]]:
        """
        Get active prepaid card for customer (defaults to earliest expiry if multiple)
        
        Returns None if no active card exists.
        """
        cards = KlippekortService.get_active_cards_for_customer(customer_id)
        return cards[0] if cards else None
    
    @staticmethod
    def check_balance(customer_id: int) -> Dict[str, Any]:
        """
        Check prepaid card balance for customer
        
        Args:
            customer_id: Customer ID
            
        Returns:
            Dict with balance info: {
                "has_card": bool,
                "card_id": int or None,
                "card_number": str or None,
                "balance_hours": Decimal,
                "status": str or None,
                "expires_at": datetime or None
            }
        """
        card = KlippekortService.get_active_card_for_customer(customer_id)
        
        if not card:
            return {
                "has_card": False,
                "card_id": None,
                "card_number": None,
                "balance_hours": Decimal('0'),
                "status": None,
                "expires_at": None
            }
        
        return {
            "has_card": True,
            "card_id": card['id'],
            "card_number": card['card_number'],
            "balance_hours": card['remaining_hours'],
            "status": card['status'],
            "expires_at": card['expires_at']
        }
    
    @staticmethod
    def can_deduct(customer_id: int, hours: Decimal) -> tuple[bool, Optional[str]]:
        """
        Check if customer has sufficient balance for deduction
        
        Args:
            customer_id: Customer ID
            hours: Hours to deduct
            
        Returns:
            (can_deduct, error_message)
        """
        balance_info = KlippekortService.check_balance(customer_id)
        
        if not balance_info['has_card']:
            return False, f"Customer {customer_id} has no active prepaid card"
        
        if balance_info['status'] != 'active':
            return False, f"Prepaid card is not active (status: {balance_info['status']})"
        
        if balance_info['balance_hours'] < hours:
            return False, (
                f"Insufficient balance: {balance_info['balance_hours']}h available, "
                f"{hours}h required"
            )
        
        # Check expiration
        if balance_info['expires_at']:
            if balance_info['expires_at'] < datetime.now():
                return False, f"Prepaid card expired on {balance_info['expires_at']}"
        
        return True, None
    
    @staticmethod
    def deduct_hours(
        customer_id: int,
        hours: Decimal,
        worklog_id: int,
        user_id: Optional[int] = None,
        description: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Deduct hours from customer's active prepaid card
        
        Args:
            customer_id: Customer ID
            hours: Hours to deduct
            worklog_id: Worklog entry consuming the hours
            user_id: User performing deduction
            description: Optional description
            
        Returns:
            Transaction dict
            
        Raises:
            ValueError: If insufficient balance or no active card
        """
        # Get active card
        card = KlippekortService.get_active_card_for_customer(customer_id)

        rounding_minutes = int(card.get('rounding_minutes') or 0)
        rounded_hours = hours
        if rounding_minutes > 0:
            interval = Decimal(rounding_minutes) / Decimal(60)
            rounded_hours = (Decimal(str(hours)) / interval).to_integral_value(rounding=ROUND_CEILING) * interval

        if Decimal(str(card['remaining_hours'])) < Decimal(str(rounded_hours)):
            raise ValueError(f"Insufficient hours on prepaid card (Remaining: {card['remaining_hours']})")
        
        logger.info(f"⏱️ Deducting {rounded_hours}h from card {card['card_number']} for worklog {worklog_id}")
        
        # Update card usage
        new_used = Decimal(str(card['used_hours'])) + rounded_hours
        execute_update(
            "UPDATE tticket_prepaid_cards SET used_hours = %s WHERE id = %s",
            (new_used, card['id'])
        )
        
        # Calculate new balance
        new_balance = Decimal(str(card['remaining_hours'])) - rounded_hours
        
        # Create transaction
        transaction_id = execute_insert(
            """
            INSERT INTO tticket_prepaid_transactions 
            (card_id, worklog_id, transaction_type, hours, balance_after, description, created_by_user_id)
            VALUES (%s, %s, %s, %s, %s, %s, %s)
            """,
            (
                card['id'],
                worklog_id,
                'usage',
                -rounded_hours,  # Negative for deduction
                new_balance,
                description or f"Worklog #{worklog_id}: {hours}h (rounded to {rounded_hours}h)",
                user_id
            )
        )
        
        # Check if card is now depleted
        if new_balance <= Decimal('0'):
            execute_update(
                "UPDATE tticket_prepaid_cards SET status = 'depleted' WHERE id = %s",
                (card['id'],)
            )
            logger.warning(f"💳 Card {card['card_number']} is now depleted")
        
        # Fetch transaction
        transaction = execute_query_single(
            "SELECT * FROM tticket_prepaid_transactions WHERE id = %s",
            (transaction_id,))
        
        logger.info(f"✅ Deducted {hours}h from card {card['card_number']}, new balance: {new_balance}h")
        return transaction
    
    @staticmethod
    def top_up_card(
        card_id: int,
        hours: Decimal,
        user_id: Optional[int] = None,
        note: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Add hours to existing prepaid card
        
        Args:
            card_id: Card ID
            hours: Hours to add
            user_id: User performing top-up
            note: Optional note
            
        Returns:
            Transaction dict
            
        Raises:
            ValueError: If card not found or not active
        """
        # Get card
        card = KlippekortService.get_card(card_id)
        
        if not card:
            raise ValueError(f"Prepaid card {card_id} not found")
        
        if card['status'] not in ['active', 'depleted']:
            raise ValueError(f"Cannot top up card with status: {card['status']}")
        
        logger.info(f"💰 Topping up card {card['card_number']} with {hours}h")
        
        # Update purchased hours
        new_purchased = Decimal(str(card['purchased_hours'])) + hours
        execute_update(
            "UPDATE tticket_prepaid_cards SET purchased_hours = %s, status = 'active' WHERE id = %s",
            (new_purchased, card_id)
        )
        
        # Calculate new balance
        new_balance = Decimal(str(card['remaining_hours'])) + hours
        
        # Create transaction
        transaction_id = execute_insert(
            """
            INSERT INTO tticket_prepaid_transactions 
            (card_id, transaction_type, hours, balance_after, description, created_by_user_id)
            VALUES (%s, %s, %s, %s, %s, %s)
            """,
            (
                card_id,
                'top_up',
                hours,  # Positive for addition
                new_balance,
                note or f"Top-up: {hours}h added",
                user_id
            )
        )
        
        transaction = execute_query_single(
            "SELECT * FROM tticket_prepaid_transactions WHERE id = %s",
            (transaction_id,))
        
        logger.info(f"✅ Topped up card {card['card_number']} with {hours}h, new balance: {new_balance}h")
        return transaction
    
    @staticmethod
    def get_transactions(
        card_id: int,
        limit: int = 100
    ) -> List[Dict[str, Any]]:
        """
        Get transaction history for card
        
        Args:
            card_id: Card ID
            limit: Max number of transactions
            
        Returns:
            List of transaction dicts
        """
        transactions = execute_query_single(
            """
            SELECT * FROM tticket_prepaid_transactions 
            WHERE card_id = %s 
            ORDER BY created_at DESC 
            LIMIT %s
            """,
            (card_id, limit)
        )
        
        return transactions or []
    
    @staticmethod
    def list_cards(
        customer_id: Optional[int] = None,
        status: Optional[str] = None,
        limit: int = 50,
        offset: int = 0
    ) -> List[Dict[str, Any]]:
        """
        List prepaid cards with optional filters
        
        Args:
            customer_id: Filter by customer
            status: Filter by status
            limit: Number of results
            offset: Offset for pagination
            
        Returns:
            List of card dicts
        """
        query = "SELECT * FROM tticket_prepaid_cards WHERE 1=1"
        params = []
        
        if customer_id:
            query += " AND customer_id = %s"
            params.append(customer_id)
        
        if status:
            query += " AND status = %s"
            params.append(status)
        
        query += " ORDER BY purchased_at DESC LIMIT %s OFFSET %s"
        params.extend([limit, offset])
        
        cards = execute_query(query, tuple(params))
        return cards or []
    
    @staticmethod
    def cancel_card(
        card_id: int,
        user_id: Optional[int] = None,
        reason: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Cancel/deactivate a prepaid card
        
        Args:
            card_id: Card ID
            user_id: User cancelling card
            reason: Cancellation reason
            
        Returns:
            Updated card dict
            
        Raises:
            ValueError: If card not found or already cancelled
        """
        card = KlippekortService.get_card(card_id)
        
        if not card:
            raise ValueError(f"Prepaid card {card_id} not found")
        
        if card['status'] == 'cancelled':
            raise ValueError(f"Card {card['card_number']} is already cancelled")
        
        logger.info(f"❌ Cancelling card {card['card_number']}")
        
        # Update status
        execute_update(
            "UPDATE tticket_prepaid_cards SET status = 'cancelled' WHERE id = %s",
            (card_id,)
        )
        
        # Log transaction
        execute_insert(
            """
            INSERT INTO tticket_prepaid_transactions 
            (card_id, transaction_type, hours, balance_after, description, created_by_user_id)
            VALUES (%s, %s, %s, %s, %s, %s)
            """,
            (
                card_id,
                'cancellation',
                Decimal('0'),
                card['remaining_hours'],
                reason or "Card cancelled",
                user_id
            )
        )
        
        # Fetch updated card
        updated = execute_query(
            "SELECT * FROM tticket_prepaid_cards WHERE id = %s",
            (card_id,))
        
        logger.info(f"✅ Cancelled card {card['card_number']}")
        return updated
