"""
POS Event Consumer
Processes POS webhook events from RabbitMQ queue
Handles sales transactions, refunds, and inventory updates from various POS systems
"""
import json
import structlog
from typing import Dict, Any, Optional
from datetime import datetime
from decimal import Decimal

from shared.messaging import RabbitMQClient
from app.services.webhook_service import WebhookService
from sqlalchemy.ext.asyncio import AsyncSession

logger = structlog.get_logger()


class POSEventConsumer:
    """
    Consumes POS webhook events from RabbitMQ and processes them
    Supports multiple POS systems: Square, Shopify, Toast, etc.
    """

    def __init__(self, db_session: AsyncSession):
        self.db_session = db_session
        self.webhook_service = WebhookService()

    async def consume_pos_events(
        self,
        rabbitmq_client: RabbitMQClient
    ):
        """
        Start consuming POS events from RabbitMQ
        """
        async def process_message(message):
            """Process a single POS event message"""
            try:
                async with message.process():
                    # Parse event data
                    event_data = json.loads(message.body.decode())
                    logger.info(
                        "Received POS event",
                        event_id=event_data.get('event_id'),
                        event_type=event_data.get('event_type'),
                        pos_system=event_data.get('data', {}).get('pos_system')
                    )

                    # Process the event
                    await self.process_pos_event(event_data)

            except Exception as e:
                logger.error(
                    "Error processing POS event",
                    error=str(e),
                    exc_info=True
                )

        # Start consuming events
        await rabbitmq_client.consume_events(
            exchange_name="pos.events",
            queue_name="pos.processing.queue",
            routing_key="pos.*",
            callback=process_message
        )

        logger.info("Started consuming POS events")

    async def process_pos_event(self, event_data: Dict[str, Any]) -> bool:
        """
        Process a POS event based on type

        Args:
            event_data: Full event payload from RabbitMQ

        Returns:
            bool: True if processed successfully
        """
        try:
            data = event_data.get('data', {})
            webhook_log_id = data.get('webhook_log_id')
            pos_system = data.get('pos_system', 'unknown')
            webhook_type = data.get('webhook_type')
            payload = data.get('payload', {})
            tenant_id = event_data.get('tenant_id')

            if not webhook_log_id:
                logger.warning("POS event missing webhook_log_id", event_data=event_data)
                return False

            # Update webhook log status to processing
            await self.webhook_service.update_webhook_status(
                webhook_log_id,
                status="processing",
                notes="Event consumer processing"
            )

            # Route to appropriate handler based on webhook type
            success = False
            if webhook_type in ['sale.completed', 'transaction.completed', 'order.completed']:
                success = await self._handle_sale_completed(tenant_id, pos_system, payload)
            elif webhook_type in ['sale.refunded', 'transaction.refunded', 'order.refunded']:
                success = await self._handle_sale_refunded(tenant_id, pos_system, payload)
            elif webhook_type in ['inventory.updated', 'stock.updated']:
                success = await self._handle_inventory_updated(tenant_id, pos_system, payload)
            else:
                logger.warning("Unknown POS webhook type", webhook_type=webhook_type)
                success = True  # Mark as processed to avoid retry

            # Update webhook log with final status
            if success:
                await self.webhook_service.update_webhook_status(
                    webhook_log_id,
                    status="completed",
                    notes="Successfully processed"
                )
                logger.info(
                    "POS event processed successfully",
                    webhook_log_id=webhook_log_id,
                    webhook_type=webhook_type
                )
            else:
                await self.webhook_service.update_webhook_status(
                    webhook_log_id,
                    status="failed",
                    notes="Processing failed"
                )
                logger.error(
                    "POS event processing failed",
                    webhook_log_id=webhook_log_id,
                    webhook_type=webhook_type
                )

            return success

        except Exception as e:
            logger.error(
                "Error in process_pos_event",
                error=str(e),
                event_id=event_data.get('event_id'),
                exc_info=True
            )
            return False

    async def _handle_sale_completed(
        self,
        tenant_id: str,
        pos_system: str,
        payload: Dict[str, Any]
    ) -> bool:
        """
        Handle completed sale transaction

        Updates:
        - Inventory quantities (decrease stock)
        - Sales analytics data
        - Revenue tracking

        Args:
            tenant_id: Tenant ID
            pos_system: POS system name (square, shopify, toast, etc.)
            payload: Sale data from POS system

        Returns:
            bool: True if handled successfully
        """
        try:
            # Extract transaction data based on POS system format
            transaction_data = self._parse_sale_data(pos_system, payload)

            if not transaction_data:
                logger.warning("Failed to parse sale data", pos_system=pos_system)
                return False

            # Update inventory via inventory service client
            from shared.clients.inventory_client import InventoryServiceClient
            from shared.config.base import get_settings

            config = get_settings()
            inventory_client = InventoryServiceClient(config, "pos")

            for item in transaction_data.get('items', []):
                product_id = item.get('product_id')
                quantity = item.get('quantity', 0)
                unit_of_measure = item.get('unit_of_measure', 'units')

                if not product_id or quantity <= 0:
                    continue

                # Decrease inventory stock
                try:
                    await inventory_client.adjust_stock(
                        tenant_id=tenant_id,
                        product_id=product_id,
                        quantity=-quantity,  # Negative for sale
                        unit_of_measure=unit_of_measure,
                        reason=f"POS sale - {pos_system}",
                        reference_id=transaction_data.get('transaction_id')
                    )
                    logger.info(
                        "Inventory updated for sale",
                        product_id=product_id,
                        quantity=quantity,
                        pos_system=pos_system
                    )
                except Exception as inv_error:
                    logger.error(
                        "Failed to update inventory",
                        product_id=product_id,
                        error=str(inv_error)
                    )
                    # Continue processing other items even if one fails

            # Publish sales data to sales service via RabbitMQ
            from shared.messaging import get_rabbitmq_client
            import uuid

            rabbitmq_client = get_rabbitmq_client()
            if rabbitmq_client:
                sales_event = {
                    "event_id": str(uuid.uuid4()),
                    "event_type": "sales.transaction.completed",
                    "timestamp": datetime.utcnow().isoformat(),
                    "tenant_id": tenant_id,
                    "data": {
                        "transaction_id": transaction_data.get('transaction_id'),
                        "pos_system": pos_system,
                        "total_amount": transaction_data.get('total_amount', 0),
                        "items": transaction_data.get('items', []),
                        "payment_method": transaction_data.get('payment_method'),
                        "transaction_date": transaction_data.get('transaction_date'),
                        "customer_id": transaction_data.get('customer_id')
                    }
                }

                await rabbitmq_client.publish_event(
                    exchange_name="sales.events",
                    routing_key="sales.transaction.completed",
                    event_data=sales_event
                )

                logger.info(
                    "Published sales event",
                    event_id=sales_event["event_id"],
                    transaction_id=transaction_data.get('transaction_id')
                )

            return True

        except Exception as e:
            logger.error(
                "Error handling sale completed",
                error=str(e),
                pos_system=pos_system,
                exc_info=True
            )
            return False

    async def _handle_sale_refunded(
        self,
        tenant_id: str,
        pos_system: str,
        payload: Dict[str, Any]
    ) -> bool:
        """
        Handle refunded sale transaction

        Updates:
        - Inventory quantities (increase stock)
        - Sales analytics (negative transaction)

        Args:
            tenant_id: Tenant ID
            pos_system: POS system name
            payload: Refund data from POS system

        Returns:
            bool: True if handled successfully
        """
        try:
            # Extract refund data based on POS system format
            refund_data = self._parse_refund_data(pos_system, payload)

            if not refund_data:
                logger.warning("Failed to parse refund data", pos_system=pos_system)
                return False

            # Update inventory via inventory service client
            from shared.clients.inventory_client import InventoryServiceClient
            from shared.config.base import get_settings

            config = get_settings()
            inventory_client = InventoryServiceClient(config, "pos")

            for item in refund_data.get('items', []):
                product_id = item.get('product_id')
                quantity = item.get('quantity', 0)
                unit_of_measure = item.get('unit_of_measure', 'units')

                if not product_id or quantity <= 0:
                    continue

                # Increase inventory stock (return to stock)
                try:
                    await inventory_client.adjust_stock(
                        tenant_id=tenant_id,
                        product_id=product_id,
                        quantity=quantity,  # Positive for refund
                        unit_of_measure=unit_of_measure,
                        reason=f"POS refund - {pos_system}",
                        reference_id=refund_data.get('refund_id')
                    )
                    logger.info(
                        "Inventory updated for refund",
                        product_id=product_id,
                        quantity=quantity,
                        pos_system=pos_system
                    )
                except Exception as inv_error:
                    logger.error(
                        "Failed to update inventory for refund",
                        product_id=product_id,
                        error=str(inv_error)
                    )

            # Publish refund event to sales service
            from shared.messaging import get_rabbitmq_client
            import uuid

            rabbitmq_client = get_rabbitmq_client()
            if rabbitmq_client:
                refund_event = {
                    "event_id": str(uuid.uuid4()),
                    "event_type": "sales.transaction.refunded",
                    "timestamp": datetime.utcnow().isoformat(),
                    "tenant_id": tenant_id,
                    "data": {
                        "refund_id": refund_data.get('refund_id'),
                        "original_transaction_id": refund_data.get('original_transaction_id'),
                        "pos_system": pos_system,
                        "refund_amount": refund_data.get('refund_amount', 0),
                        "items": refund_data.get('items', []),
                        "refund_date": refund_data.get('refund_date')
                    }
                }

                await rabbitmq_client.publish_event(
                    exchange_name="sales.events",
                    routing_key="sales.transaction.refunded",
                    event_data=refund_event
                )

                logger.info(
                    "Published refund event",
                    event_id=refund_event["event_id"],
                    refund_id=refund_data.get('refund_id')
                )

            return True

        except Exception as e:
            logger.error(
                "Error handling sale refunded",
                error=str(e),
                pos_system=pos_system,
                exc_info=True
            )
            return False

    async def _handle_inventory_updated(
        self,
        tenant_id: str,
        pos_system: str,
        payload: Dict[str, Any]
    ) -> bool:
        """
        Handle inventory update from POS system

        Syncs inventory levels from POS to our system

        Args:
            tenant_id: Tenant ID
            pos_system: POS system name
            payload: Inventory data from POS system

        Returns:
            bool: True if handled successfully
        """
        try:
            # Extract inventory data
            inventory_data = self._parse_inventory_data(pos_system, payload)

            if not inventory_data:
                logger.warning("Failed to parse inventory data", pos_system=pos_system)
                return False

            # Update inventory via inventory service client
            from shared.clients.inventory_client import InventoryServiceClient
            from shared.config.base import get_settings

            config = get_settings()
            inventory_client = InventoryServiceClient(config, "pos")

            for item in inventory_data.get('items', []):
                product_id = item.get('product_id')
                new_quantity = item.get('quantity', 0)

                if not product_id:
                    continue

                # Sync inventory level
                try:
                    await inventory_client.sync_stock_level(
                        tenant_id=tenant_id,
                        product_id=product_id,
                        quantity=new_quantity,
                        source=f"POS sync - {pos_system}"
                    )
                    logger.info(
                        "Inventory synced from POS",
                        product_id=product_id,
                        new_quantity=new_quantity,
                        pos_system=pos_system
                    )
                except Exception as inv_error:
                    logger.error(
                        "Failed to sync inventory",
                        product_id=product_id,
                        error=str(inv_error)
                    )

            return True

        except Exception as e:
            logger.error(
                "Error handling inventory updated",
                error=str(e),
                pos_system=pos_system,
                exc_info=True
            )
            return False

    def _parse_sale_data(self, pos_system: str, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """
        Parse sale data from various POS system formats

        Args:
            pos_system: POS system name
            payload: Raw payload from POS webhook

        Returns:
            Normalized transaction data
        """
        try:
            if pos_system.lower() == 'square':
                return self._parse_square_sale(payload)
            elif pos_system.lower() == 'shopify':
                return self._parse_shopify_sale(payload)
            elif pos_system.lower() == 'toast':
                return self._parse_toast_sale(payload)
            else:
                # Generic parser for custom POS systems
                return self._parse_generic_sale(payload)

        except Exception as e:
            logger.error("Error parsing sale data", pos_system=pos_system, error=str(e))
            return None

    def _parse_square_sale(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Parse Square POS sale format"""
        payment = payload.get('payment', {})
        order = payment.get('order', {})
        line_items = order.get('line_items', [])

        items = []
        for item in line_items:
            items.append({
                'product_id': item.get('catalog_object_id'),
                'product_name': item.get('name'),
                'quantity': float(item.get('quantity', 1)),
                'unit_price': float(item.get('base_price_money', {}).get('amount', 0)) / 100,
                'unit_of_measure': 'units'
            })

        return {
            'transaction_id': payment.get('id'),
            'total_amount': float(payment.get('amount_money', {}).get('amount', 0)) / 100,
            'items': items,
            'payment_method': payment.get('card_details', {}).get('card', {}).get('card_brand', 'unknown'),
            'transaction_date': payment.get('created_at'),
            'customer_id': payment.get('customer_id')
        }

    def _parse_shopify_sale(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Parse Shopify POS sale format"""
        line_items = payload.get('line_items', [])

        items = []
        for item in line_items:
            items.append({
                'product_id': str(item.get('product_id')),
                'product_name': item.get('title'),
                'quantity': float(item.get('quantity', 1)),
                'unit_price': float(item.get('price', 0)),
                'unit_of_measure': 'units'
            })

        return {
            'transaction_id': str(payload.get('id')),
            'total_amount': float(payload.get('total_price', 0)),
            'items': items,
            'payment_method': payload.get('payment_gateway_names', ['unknown'])[0],
            'transaction_date': payload.get('created_at'),
            'customer_id': str(payload.get('customer', {}).get('id')) if payload.get('customer') else None
        }

    def _parse_toast_sale(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Parse Toast POS sale format"""
        selections = payload.get('selections', [])

        items = []
        for item in selections:
            items.append({
                'product_id': item.get('guid'),
                'product_name': item.get('displayName'),
                'quantity': float(item.get('quantity', 1)),
                'unit_price': float(item.get('preDiscountPrice', 0)),
                'unit_of_measure': 'units'
            })

        return {
            'transaction_id': payload.get('guid'),
            'total_amount': float(payload.get('totalAmount', 0)),
            'items': items,
            'payment_method': payload.get('payments', [{}])[0].get('type', 'unknown'),
            'transaction_date': payload.get('closedDate'),
            'customer_id': payload.get('customer', {}).get('guid')
        }

    def _parse_generic_sale(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Parse generic/custom POS sale format"""
        items = []
        for item in payload.get('items', []):
            items.append({
                'product_id': item.get('product_id') or item.get('id'),
                'product_name': item.get('name') or item.get('description'),
                'quantity': float(item.get('quantity', 1)),
                'unit_price': float(item.get('price', 0)),
                'unit_of_measure': item.get('unit_of_measure', 'units')
            })

        return {
            'transaction_id': payload.get('transaction_id') or payload.get('id'),
            'total_amount': float(payload.get('total', 0)),
            'items': items,
            'payment_method': payload.get('payment_method', 'unknown'),
            'transaction_date': payload.get('timestamp') or payload.get('created_at'),
            'customer_id': payload.get('customer_id')
        }

    def _parse_refund_data(self, pos_system: str, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """Parse refund data from various POS systems"""
        # Similar parsing logic as sales, but for refunds
        # Simplified for now - would follow same pattern as _parse_sale_data
        return {
            'refund_id': payload.get('refund_id') or payload.get('id'),
            'original_transaction_id': payload.get('original_transaction_id'),
            'refund_amount': float(payload.get('amount', 0)),
            'items': payload.get('items', []),
            'refund_date': payload.get('refund_date') or payload.get('created_at')
        }

    def _parse_inventory_data(self, pos_system: str, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
        """Parse inventory data from various POS systems"""
        return {
            'items': payload.get('items', [])
        }


# Factory function for creating consumer instance
def create_pos_event_consumer(db_session: AsyncSession) -> POSEventConsumer:
    """Create POS event consumer instance"""
    return POSEventConsumer(db_session)
