# app/services/marketplace_product_service.py
"""
MarketplaceProduct service for managing product operations and data processing.

This module provides classes and functions for:
- MarketplaceProduct CRUD operations with validation
- Advanced product filtering and search
- Inventory information integration
- CSV export functionality
"""

import csv
import logging
from collections.abc import Generator
from datetime import UTC, datetime
from io import StringIO

from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from app.exceptions import (
    InvalidMarketplaceProductDataException,
    MarketplaceProductAlreadyExistsException,
    MarketplaceProductNotFoundException,
    MarketplaceProductValidationException,
    ValidationException,
)
from app.utils.data_processing import GTINProcessor, PriceProcessor
from models.database.inventory import Inventory
from models.database.marketplace_product import MarketplaceProduct
from models.schema.inventory import InventoryLocationResponse, InventorySummaryResponse
from models.schema.marketplace_product import (
    MarketplaceProductCreate,
    MarketplaceProductUpdate,
)

logger = logging.getLogger(__name__)


class MarketplaceProductService:
    """Service class for MarketplaceProduct operations following the application's service pattern."""

    def __init__(self):
        """Class constructor."""
        self.gtin_processor = GTINProcessor()
        self.price_processor = PriceProcessor()

    def create_product(
        self, db: Session, product_data: MarketplaceProductCreate
    ) -> MarketplaceProduct:
        """Create a new product with validation."""
        try:
            # Process and validate GTIN if provided
            if product_data.gtin:
                normalized_gtin = self.gtin_processor.normalize(product_data.gtin)
                if not normalized_gtin:
                    raise InvalidMarketplaceProductDataException(
                        "Invalid GTIN format", field="gtin"
                    )
                product_data.gtin = normalized_gtin

            # Process price if provided
            if product_data.price:
                try:
                    parsed_price, currency = self.price_processor.parse_price_currency(
                        product_data.price
                    )
                    if parsed_price:
                        product_data.price = parsed_price
                        product_data.currency = currency
                except ValueError as e:
                    # Convert ValueError to domain-specific exception
                    raise InvalidMarketplaceProductDataException(str(e), field="price")

            # Set default marketplace if not provided
            if not product_data.marketplace:
                product_data.marketplace = "Letzshop"

            # Validate required fields
            if (
                not product_data.marketplace_product_id
                or not product_data.marketplace_product_id.strip()
            ):
                raise MarketplaceProductValidationException(
                    "MarketplaceProduct ID is required", field="marketplace_product_id"
                )

            if not product_data.title or not product_data.title.strip():
                raise MarketplaceProductValidationException(
                    "MarketplaceProduct title is required", field="title"
                )

            db_product = MarketplaceProduct(**product_data.model_dump())
            db.add(db_product)
            db.flush()
            db.refresh(db_product)

            logger.info(f"Created product {db_product.marketplace_product_id}")
            return db_product

        except (
            InvalidMarketplaceProductDataException,
            MarketplaceProductValidationException,
        ):
            raise  # Re-raise custom exceptions
        except IntegrityError as e:
            logger.error(f"Database integrity error: {str(e)}")
            if "marketplace_product_id" in str(e).lower() or "unique" in str(e).lower():
                raise MarketplaceProductAlreadyExistsException(
                    product_data.marketplace_product_id
                )
            raise MarketplaceProductValidationException(
                "Data integrity constraint violation"
            )
        except Exception as e:
            logger.error(f"Error creating product: {str(e)}")
            raise ValidationException("Failed to create product")

    def get_product_by_id(
        self, db: Session, marketplace_product_id: str
    ) -> MarketplaceProduct | None:
        """Get a product by its ID."""
        try:
            return (
                db.query(MarketplaceProduct)
                .filter(
                    MarketplaceProduct.marketplace_product_id == marketplace_product_id
                )
                .first()
            )
        except Exception as e:
            logger.error(f"Error getting product {marketplace_product_id}: {str(e)}")
            return None

    def get_product_by_id_or_raise(
        self, db: Session, marketplace_product_id: str
    ) -> MarketplaceProduct:
        """
        Get a product by its ID or raise exception.

        Args:
            db: Database session
            marketplace_product_id: MarketplaceProduct ID to find

        Returns:
            MarketplaceProduct object

        Raises:
            MarketplaceProductNotFoundException: If product doesn't exist
        """
        product = self.get_product_by_id(db, marketplace_product_id)
        if not product:
            raise MarketplaceProductNotFoundException(marketplace_product_id)
        return product

    def get_products_with_filters(
        self,
        db: Session,
        skip: int = 0,
        limit: int = 100,
        brand: str | None = None,
        category: str | None = None,
        availability: str | None = None,
        marketplace: str | None = None,
        vendor_name: str | None = None,
        search: str | None = None,
    ) -> tuple[list[MarketplaceProduct], int]:
        """
        Get products with filtering and pagination.

        Args:
            db: Database session
            skip: Number of records to skip
            limit: Maximum records to return
            brand: Brand filter
            category: Category filter
            availability: Availability filter
            marketplace: Marketplace filter
            vendor_name: Vendor name filter
            search: Search term

        Returns:
            Tuple of (products_list, total_count)
        """
        try:
            query = db.query(MarketplaceProduct)

            # Apply filters
            if brand:
                query = query.filter(MarketplaceProduct.brand.ilike(f"%{brand}%"))
            if category:
                query = query.filter(
                    MarketplaceProduct.google_product_category.ilike(f"%{category}%")
                )
            if availability:
                query = query.filter(MarketplaceProduct.availability == availability)
            if marketplace:
                query = query.filter(
                    MarketplaceProduct.marketplace.ilike(f"%{marketplace}%")
                )
            if vendor_name:
                query = query.filter(
                    MarketplaceProduct.vendor_name.ilike(f"%{vendor_name}%")
                )
            if search:
                # Search in title, description, marketplace, and name
                search_term = f"%{search}%"
                query = query.filter(
                    (MarketplaceProduct.title.ilike(search_term))
                    | (MarketplaceProduct.description.ilike(search_term))
                    | (MarketplaceProduct.marketplace.ilike(search_term))
                    | (MarketplaceProduct.vendor_name.ilike(search_term))
                )

            total = query.count()
            products = query.offset(skip).limit(limit).all()

            return products, total

        except Exception as e:
            logger.error(f"Error getting products with filters: {str(e)}")
            raise ValidationException("Failed to retrieve products")

    def update_product(
        self,
        db: Session,
        marketplace_product_id: str,
        product_update: MarketplaceProductUpdate,
    ) -> MarketplaceProduct:
        """Update product with validation."""
        try:
            product = self.get_product_by_id_or_raise(db, marketplace_product_id)

            # Update fields
            update_data = product_update.model_dump(exclude_unset=True)

            # Validate GTIN if being updated
            if "gtin" in update_data and update_data["gtin"]:
                normalized_gtin = self.gtin_processor.normalize(update_data["gtin"])
                if not normalized_gtin:
                    raise InvalidMarketplaceProductDataException(
                        "Invalid GTIN format", field="gtin"
                    )
                update_data["gtin"] = normalized_gtin

            # Process price if being updated
            if "price" in update_data and update_data["price"]:
                try:
                    parsed_price, currency = self.price_processor.parse_price_currency(
                        update_data["price"]
                    )
                    if parsed_price:
                        update_data["price"] = parsed_price
                        update_data["currency"] = currency
                except ValueError as e:
                    # Convert ValueError to domain-specific exception
                    raise InvalidMarketplaceProductDataException(str(e), field="price")

            # Validate required fields if being updated
            if "title" in update_data and (
                not update_data["title"] or not update_data["title"].strip()
            ):
                raise MarketplaceProductValidationException(
                    "MarketplaceProduct title cannot be empty", field="title"
                )

            for key, value in update_data.items():
                setattr(product, key, value)

            product.updated_at = datetime.now(UTC)
            db.flush()
            db.refresh(product)

            logger.info(f"Updated product {marketplace_product_id}")
            return product

        except (
            MarketplaceProductNotFoundException,
            InvalidMarketplaceProductDataException,
            MarketplaceProductValidationException,
        ):
            raise  # Re-raise custom exceptions
        except Exception as e:
            logger.error(f"Error updating product {marketplace_product_id}: {str(e)}")
            raise ValidationException("Failed to update product")

    def delete_product(self, db: Session, marketplace_product_id: str) -> bool:
        """
        Delete product and associated inventory.

        Args:
            db: Database session
            marketplace_product_id: MarketplaceProduct ID to delete

        Returns:
            True if deletion successful

        Raises:
            MarketplaceProductNotFoundException: If product doesn't exist
        """
        try:
            product = self.get_product_by_id_or_raise(db, marketplace_product_id)

            # Delete associated inventory entries if GTIN exists
            if product.gtin:
                db.query(Inventory).filter(Inventory.gtin == product.gtin).delete()

            db.delete(product)

            logger.info(f"Deleted product {marketplace_product_id}")
            return True

        except MarketplaceProductNotFoundException:
            raise  # Re-raise custom exceptions
        except Exception as e:
            logger.error(f"Error deleting product {marketplace_product_id}: {str(e)}")
            raise ValidationException("Failed to delete product")

    def get_inventory_info(
        self, db: Session, gtin: str
    ) -> InventorySummaryResponse | None:
        """
        Get inventory information for a product by GTIN.

        Args:
            db: Database session
            gtin: GTIN to look up inventory for

        Returns:
            InventorySummaryResponse if inventory found, None otherwise
        """
        try:
            inventory_entries = db.query(Inventory).filter(Inventory.gtin == gtin).all()
            if not inventory_entries:
                return None

            total_quantity = sum(entry.quantity for entry in inventory_entries)
            locations = [
                InventoryLocationResponse(
                    location=entry.location,
                    quantity=entry.quantity,
                    reserved_quantity=entry.reserved_quantity or 0,
                    available_quantity=entry.quantity - (entry.reserved_quantity or 0),
                )
                for entry in inventory_entries
            ]

            return InventorySummaryResponse(
                gtin=gtin, total_quantity=total_quantity, locations=locations
            )

        except Exception as e:
            logger.error(f"Error getting inventory info for GTIN {gtin}: {str(e)}")
            return None

    import csv
    from io import StringIO

    from sqlalchemy.orm import Session

    def generate_csv_export(
        self,
        db: Session,
        marketplace: str | None = None,
        vendor_name: str | None = None,
    ) -> Generator[str, None, None]:
        """
        Generate CSV export with streaming for memory efficiency and proper CSV escaping.

        Args:
            db: Database session
            marketplace: Optional marketplace filter
            vendor_name: Optional vendor name filter

        Yields:
            CSV content as strings with proper escaping
        """
        try:
            # Create a StringIO buffer for CSV writing
            output = StringIO()
            writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)

            # Write header row
            headers = [
                "marketplace_product_id",
                "title",
                "description",
                "link",
                "image_link",
                "availability",
                "price",
                "currency",
                "brand",
                "gtin",
                "marketplace",
                "name",
            ]
            writer.writerow(headers)
            yield output.getvalue()

            # Clear buffer for reuse
            output.seek(0)
            output.truncate(0)

            batch_size = 1000
            offset = 0

            while True:
                query = db.query(MarketplaceProduct)

                # Apply marketplace filters
                if marketplace:
                    query = query.filter(
                        MarketplaceProduct.marketplace.ilike(f"%{marketplace}%")
                    )
                if vendor_name:
                    query = query.filter(
                        MarketplaceProduct.vendor_name.ilike(f"%{vendor_name}%")
                    )

                products = query.offset(offset).limit(batch_size).all()
                if not products:
                    break

                for product in products:
                    # Create CSV row with proper escaping
                    row_data = [
                        product.marketplace_product_id or "",
                        product.title or "",
                        product.description or "",
                        product.link or "",
                        product.image_link or "",
                        product.availability or "",
                        product.price or "",
                        product.currency or "",
                        product.brand or "",
                        product.gtin or "",
                        product.marketplace or "",
                        product.vendor_name or "",
                    ]

                    writer.writerow(row_data)
                    yield output.getvalue()

                    # Clear buffer for next row
                    output.seek(0)
                    output.truncate(0)

                offset += batch_size

        except Exception as e:
            logger.error(f"Error generating CSV export: {str(e)}")
            raise ValidationException("Failed to generate CSV export")

    def product_exists(self, db: Session, marketplace_product_id: str) -> bool:
        """Check if product exists by ID."""
        try:
            return (
                db.query(MarketplaceProduct)
                .filter(
                    MarketplaceProduct.marketplace_product_id == marketplace_product_id
                )
                .first()
                is not None
            )
        except Exception as e:
            logger.error(f"Error checking if product exists: {str(e)}")
            return False

    # Private helper methods
    def _validate_product_data(self, product_data: dict) -> None:
        """Validate product data structure."""
        required_fields = ["marketplace_product_id", "title"]

        for field in required_fields:
            if field not in product_data or not product_data[field]:
                raise MarketplaceProductValidationException(
                    f"{field} is required", field=field
                )

    def _normalize_product_data(self, product_data: dict) -> dict:
        """Normalize and clean product data."""
        normalized = product_data.copy()

        # Trim whitespace from string fields
        string_fields = [
            "marketplace_product_id",
            "title",
            "description",
            "brand",
            "marketplace",
            "name",
        ]
        for field in string_fields:
            if field in normalized and normalized[field]:
                normalized[field] = normalized[field].strip()

        return normalized


# Create service instance
marketplace_product_service = MarketplaceProductService()
