# services/pos/app/integrations/base_pos_client.py
"""
Base POS Client
Abstract base class for all POS system integrations
"""

from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Any, Tuple
from datetime import datetime
from dataclasses import dataclass

import structlog

logger = structlog.get_logger()


@dataclass
class POSCredentials:
    """POS system credentials"""
    pos_system: str
    environment: str
    api_key: Optional[str] = None
    api_secret: Optional[str] = None
    access_token: Optional[str] = None
    application_id: Optional[str] = None
    merchant_id: Optional[str] = None
    location_id: Optional[str] = None
    webhook_secret: Optional[str] = None
    additional_params: Optional[Dict[str, Any]] = None


@dataclass
class POSTransaction:
    """Standardized POS transaction"""
    external_id: str
    transaction_type: str
    status: str
    total_amount: float
    subtotal: float
    tax_amount: float
    tip_amount: float
    discount_amount: float
    currency: str
    transaction_date: datetime
    payment_method: Optional[str] = None
    payment_status: Optional[str] = None
    location_id: Optional[str] = None
    location_name: Optional[str] = None
    staff_id: Optional[str] = None
    staff_name: Optional[str] = None
    customer_id: Optional[str] = None
    customer_email: Optional[str] = None
    order_type: Optional[str] = None
    table_number: Optional[str] = None
    receipt_number: Optional[str] = None
    external_order_id: Optional[str] = None
    items: List['POSTransactionItem']
    raw_data: Dict[str, Any]


@dataclass
class POSTransactionItem:
    """Standardized POS transaction item"""
    external_id: Optional[str]
    sku: Optional[str]
    name: str
    category: Optional[str]
    quantity: float
    unit_price: float
    total_price: float
    discount_amount: float
    tax_amount: float
    modifiers: Optional[Dict[str, Any]] = None
    raw_data: Optional[Dict[str, Any]] = None


@dataclass
class POSProduct:
    """Standardized POS product"""
    external_id: str
    name: str
    sku: Optional[str]
    category: Optional[str]
    subcategory: Optional[str]
    price: float
    description: Optional[str]
    is_active: bool
    raw_data: Dict[str, Any]


@dataclass
class SyncResult:
    """Result of a sync operation"""
    success: bool
    records_processed: int
    records_created: int
    records_updated: int
    records_skipped: int
    records_failed: int
    errors: List[str]
    warnings: List[str]
    duration_seconds: float
    api_calls_made: int


class POSClientError(Exception):
    """Base exception for POS client errors"""
    pass


class POSAuthenticationError(POSClientError):
    """Authentication failed"""
    pass


class POSRateLimitError(POSClientError):
    """Rate limit exceeded"""
    pass


class POSConnectionError(POSClientError):
    """Connection to POS system failed"""
    pass


class BasePOSClient(ABC):
    """
    Abstract base class for POS system integrations
    
    Provides common interface for all POS providers:
    - Square, Toast, Lightspeed, etc.
    """
    
    def __init__(self, credentials: POSCredentials):
        self.credentials = credentials
        self.pos_system = credentials.pos_system
        self.logger = logger.bind(pos_system=self.pos_system)
        
    @abstractmethod
    async def test_connection(self) -> Tuple[bool, str]:
        """
        Test connection to POS system
        
        Returns:
            Tuple of (success: bool, message: str)
        """
        pass
    
    @abstractmethod
    async def get_transactions(
        self, 
        start_date: datetime, 
        end_date: datetime,
        location_id: Optional[str] = None,
        limit: int = 100,
        cursor: Optional[str] = None
    ) -> Tuple[List[POSTransaction], Optional[str]]:
        """
        Get transactions from POS system
        
        Args:
            start_date: Start date for transaction query
            end_date: End date for transaction query
            location_id: Optional location filter
            limit: Maximum number of records to return
            cursor: Pagination cursor for next page
            
        Returns:
            Tuple of (transactions: List[POSTransaction], next_cursor: Optional[str])
        """
        pass
    
    @abstractmethod
    async def get_transaction(self, transaction_id: str) -> Optional[POSTransaction]:
        """
        Get a specific transaction by ID
        
        Args:
            transaction_id: External transaction ID
            
        Returns:
            POSTransaction if found, None otherwise
        """
        pass
    
    @abstractmethod
    async def get_products(
        self,
        location_id: Optional[str] = None,
        limit: int = 100,
        cursor: Optional[str] = None
    ) -> Tuple[List[POSProduct], Optional[str]]:
        """
        Get products/menu items from POS system
        
        Args:
            location_id: Optional location filter
            limit: Maximum number of records to return
            cursor: Pagination cursor for next page
            
        Returns:
            Tuple of (products: List[POSProduct], next_cursor: Optional[str])
        """
        pass
    
    @abstractmethod
    def verify_webhook_signature(self, payload: bytes, signature: str) -> bool:
        """
        Verify webhook signature
        
        Args:
            payload: Raw webhook payload
            signature: Signature from webhook headers
            
        Returns:
            True if signature is valid
        """
        pass
    
    @abstractmethod
    def parse_webhook_payload(self, payload: Dict[str, Any]) -> Optional[POSTransaction]:
        """
        Parse webhook payload into standardized transaction
        
        Args:
            payload: Webhook payload
            
        Returns:
            POSTransaction if parseable, None otherwise
        """
        pass
    
    @abstractmethod
    def get_webhook_events(self) -> List[str]:
        """
        Get list of supported webhook events
        
        Returns:
            List of supported event types
        """
        pass
    
    @abstractmethod
    def get_rate_limits(self) -> Dict[str, Any]:
        """
        Get rate limit information
        
        Returns:
            Dictionary with rate limit details
        """
        pass
    
    # Common utility methods
    
    def get_pos_system(self) -> str:
        """Get POS system identifier"""
        return self.pos_system
    
    def get_environment(self) -> str:
        """Get environment (sandbox/production)"""
        return self.credentials.environment
    
    def is_production(self) -> bool:
        """Check if running in production environment"""
        return self.credentials.environment.lower() == "production"
    
    def log_api_call(self, method: str, endpoint: str, status_code: int, duration_ms: int):
        """Log API call for monitoring"""
        self.logger.info(
            "POS API call",
            method=method,
            endpoint=endpoint,
            status_code=status_code,
            duration_ms=duration_ms,
            environment=self.get_environment()
        )
    
    def log_error(self, error: Exception, context: str):
        """Log error with context"""
        self.logger.error(
            f"POS client error: {context}",
            error=str(error),
            error_type=type(error).__name__,
            pos_system=self.pos_system
        )
    
    async def sync_transactions(
        self,
        start_date: datetime,
        end_date: datetime,
        location_id: Optional[str] = None,
        batch_size: int = 100
    ) -> SyncResult:
        """
        Sync transactions from POS system with error handling and batching
        
        Args:
            start_date: Start date for sync
            end_date: End date for sync
            location_id: Optional location filter
            batch_size: Number of records per batch
            
        Returns:
            SyncResult with operation details
        """
        start_time = datetime.utcnow()
        result = SyncResult(
            success=False,
            records_processed=0,
            records_created=0,
            records_updated=0,
            records_skipped=0,
            records_failed=0,
            errors=[],
            warnings=[],
            duration_seconds=0,
            api_calls_made=0
        )
        
        try:
            cursor = None
            while True:
                try:
                    transactions, next_cursor = await self.get_transactions(
                        start_date=start_date,
                        end_date=end_date,
                        location_id=location_id,
                        limit=batch_size,
                        cursor=cursor
                    )
                    
                    result.api_calls_made += 1
                    result.records_processed += len(transactions)
                    
                    if not transactions:
                        break
                    
                    # Process transactions would be implemented by the service layer
                    self.logger.info(
                        "Synced transaction batch",
                        batch_size=len(transactions),
                        total_processed=result.records_processed
                    )
                    
                    cursor = next_cursor
                    if not cursor:
                        break
                        
                except Exception as e:
                    result.errors.append(f"Batch sync error: {str(e)}")
                    result.records_failed += batch_size
                    self.log_error(e, "Transaction sync batch")
                    break
            
            result.success = len(result.errors) == 0
            
        except Exception as e:
            result.errors.append(f"Sync operation failed: {str(e)}")
            self.log_error(e, "Transaction sync operation")
        
        finally:
            end_time = datetime.utcnow()
            result.duration_seconds = (end_time - start_time).total_seconds()
        
        return result