# app/services/vendor_service.py
"""
Vendor service for managing vendor operations and product catalog.

This module provides classes and functions for:
- Vendor creation and management
- Vendor access control and validation
- Vendor product catalog operations
- Vendor filtering and search
"""

import logging

from sqlalchemy import func
from sqlalchemy.orm import Session

from app.exceptions import (
    InvalidVendorDataException,
    MarketplaceProductNotFoundException,
    ProductAlreadyExistsException,
    UnauthorizedVendorAccessException,
    ValidationException,
    VendorAlreadyExistsException,
    VendorNotFoundException,
)
from models.database.marketplace_product import MarketplaceProduct
from models.database.product import Product
from models.database.user import User
from models.database.vendor import Vendor
from models.schema.product import ProductCreate
from models.schema.vendor import VendorCreate

logger = logging.getLogger(__name__)


class VendorService:
    """Service class for vendor operations following the application's service pattern."""

    def create_vendor(
        self, db: Session, vendor_data: VendorCreate, current_user: User
    ) -> Vendor:
        """
        Create a new vendor under a company.

        DEPRECATED: This method is for self-service vendor creation by company owners.
        For admin operations, use admin_service.create_vendor() instead.

        The new architecture:
        - Companies are the business entities with owners and contact info
        - Vendors are storefronts/brands under companies
        - The company_id is required in vendor_data

        Args:
            db: Database session
            vendor_data: Vendor creation data (must include company_id)
            current_user: User creating the vendor (must be company owner or admin)

        Returns:
            Created vendor object

        Raises:
            VendorAlreadyExistsException: If vendor code already exists
            UnauthorizedVendorAccessException: If user is not company owner
            InvalidVendorDataException: If vendor data is invalid
        """
        from models.database.company import Company

        try:
            # Validate company_id is provided
            if not hasattr(vendor_data, "company_id") or not vendor_data.company_id:
                raise InvalidVendorDataException(
                    "company_id is required to create a vendor", field="company_id"
                )

            # Get company and verify ownership
            company = (
                db.query(Company).filter(Company.id == vendor_data.company_id).first()
            )
            if not company:
                raise InvalidVendorDataException(
                    f"Company with ID {vendor_data.company_id} not found",
                    field="company_id",
                )

            # Check if user is company owner or admin
            if (
                current_user.role != "admin"
                and company.owner_user_id != current_user.id
            ):
                raise UnauthorizedVendorAccessException(
                    f"company-{vendor_data.company_id}", current_user.id
                )

            # Normalize vendor code to uppercase
            normalized_vendor_code = vendor_data.vendor_code.upper()

            # Check if vendor code already exists (case-insensitive check)
            if self._vendor_code_exists(db, normalized_vendor_code):
                raise VendorAlreadyExistsException(normalized_vendor_code)

            # Create vendor linked to company
            new_vendor = Vendor(
                company_id=company.id,
                vendor_code=normalized_vendor_code,
                subdomain=vendor_data.subdomain.lower(),
                name=vendor_data.name,
                description=vendor_data.description,
                letzshop_csv_url_fr=vendor_data.letzshop_csv_url_fr,
                letzshop_csv_url_en=vendor_data.letzshop_csv_url_en,
                letzshop_csv_url_de=vendor_data.letzshop_csv_url_de,
                is_active=True,
                is_verified=(current_user.role == "admin"),
            )

            db.add(new_vendor)
            db.flush()  # Get ID without committing - endpoint handles commit

            logger.info(
                f"New vendor created: {new_vendor.vendor_code} under company {company.name} by {current_user.username}"
            )
            return new_vendor

        except (
            VendorAlreadyExistsException,
            UnauthorizedVendorAccessException,
            InvalidVendorDataException,
        ):
            raise  # Re-raise custom exceptions - endpoint handles rollback
        except Exception as e:
            logger.error(f"Error creating vendor: {str(e)}")
            raise ValidationException("Failed to create vendor")

    def get_vendors(
        self,
        db: Session,
        current_user: User,
        skip: int = 0,
        limit: int = 100,
        active_only: bool = True,
        verified_only: bool = False,
    ) -> tuple[list[Vendor], int]:
        """
        Get vendors with filtering.

        Args:
            db: Database session
            current_user: Current user requesting vendors
            skip: Number of records to skip
            limit: Maximum number of records to return
            active_only: Filter for active vendors only
            verified_only: Filter for verified vendors only

        Returns:
            Tuple of (vendors_list, total_count)
        """
        try:
            query = db.query(Vendor)

            # Non-admin users can only see active and verified vendors, plus their own
            if current_user.role != "admin":
                # Get vendor IDs the user owns through companies
                from models.database.company import Company

                owned_vendor_ids = (
                    db.query(Vendor.id)
                    .join(Company)
                    .filter(Company.owner_user_id == current_user.id)
                    .subquery()
                )
                query = query.filter(
                    (Vendor.is_active == True)
                    & ((Vendor.is_verified == True) | (Vendor.id.in_(owned_vendor_ids)))
                )
            else:
                # Admin can apply filters
                if active_only:
                    query = query.filter(Vendor.is_active == True)
                if verified_only:
                    query = query.filter(Vendor.is_verified == True)

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

            return vendors, total

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

    def get_vendor_by_code(
        self, db: Session, vendor_code: str, current_user: User
    ) -> Vendor:
        """
        Get vendor by vendor code with access control.

        Args:
            db: Database session
            vendor_code: Vendor code to find
            current_user: Current user requesting the vendor

        Returns:
            Vendor object

        Raises:
            VendorNotFoundException: If vendor not found
            UnauthorizedVendorAccessException: If access denied
        """
        try:
            vendor = (
                db.query(Vendor)
                .filter(func.upper(Vendor.vendor_code) == vendor_code.upper())
                .first()
            )

            if not vendor:
                raise VendorNotFoundException(vendor_code)

            # Check access permissions
            if not self._can_access_vendor(vendor, current_user):
                raise UnauthorizedVendorAccessException(vendor_code, current_user.id)

            return vendor

        except (VendorNotFoundException, UnauthorizedVendorAccessException):
            raise  # Re-raise custom exceptions
        except Exception as e:
            logger.error(f"Error getting vendor {vendor_code}: {str(e)}")
            raise ValidationException("Failed to retrieve vendor")

    def get_vendor_by_id(self, db: Session, vendor_id: int) -> Vendor:
        """
        Get vendor by ID (admin use - no access control).

        Args:
            db: Database session
            vendor_id: Vendor ID to find

        Returns:
            Vendor object with company and owner loaded

        Raises:
            VendorNotFoundException: If vendor not found
        """
        from sqlalchemy.orm import joinedload

        from models.database.company import Company

        vendor = (
            db.query(Vendor)
            .options(joinedload(Vendor.company).joinedload(Company.owner))
            .filter(Vendor.id == vendor_id)
            .first()
        )

        if not vendor:
            raise VendorNotFoundException(str(vendor_id), identifier_type="id")

        return vendor

    def get_active_vendor_by_code(self, db: Session, vendor_code: str) -> Vendor:
        """
        Get active vendor by vendor_code for public access (no auth required).

        This method is specifically designed for public endpoints where:
        - No authentication is required
        - Only active vendors should be returned
        - Inactive/disabled vendors are hidden

        Args:
            db: Database session
            vendor_code: Vendor code (case-insensitive)

        Returns:
            Vendor object with company and owner loaded

        Raises:
            VendorNotFoundException: If vendor not found or inactive
        """
        from sqlalchemy.orm import joinedload

        from models.database.company import Company

        vendor = (
            db.query(Vendor)
            .options(joinedload(Vendor.company).joinedload(Company.owner))
            .filter(
                func.upper(Vendor.vendor_code) == vendor_code.upper(),
                Vendor.is_active == True,
            )
            .first()
        )

        if not vendor:
            logger.warning(f"Vendor not found or inactive: {vendor_code}")
            raise VendorNotFoundException(vendor_code, identifier_type="code")

        return vendor

    def get_vendor_by_identifier(self, db: Session, identifier: str) -> Vendor:
        """
        Get vendor by ID or vendor_code (admin use - no access control).

        Args:
            db: Database session
            identifier: Either vendor ID (int as string) or vendor_code (string)

        Returns:
            Vendor object with company and owner loaded

        Raises:
            VendorNotFoundException: If vendor not found
        """
        from sqlalchemy.orm import joinedload

        from models.database.company import Company

        # Try as integer ID first
        try:
            vendor_id = int(identifier)
            return self.get_vendor_by_id(db, vendor_id)
        except (ValueError, TypeError):
            pass  # Not an integer, treat as vendor_code
        except VendorNotFoundException:
            pass  # ID not found, try as vendor_code

        # Try as vendor_code (case-insensitive)
        vendor = (
            db.query(Vendor)
            .options(joinedload(Vendor.company).joinedload(Company.owner))
            .filter(func.upper(Vendor.vendor_code) == identifier.upper())
            .first()
        )

        if not vendor:
            raise VendorNotFoundException(identifier, identifier_type="code")

        return vendor

    def toggle_verification(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
        """
        Toggle vendor verification status.

        Args:
            db: Database session
            vendor_id: Vendor ID

        Returns:
            Tuple of (updated vendor, status message)

        Raises:
            VendorNotFoundException: If vendor not found
        """
        vendor = self.get_vendor_by_id(db, vendor_id)
        vendor.is_verified = not vendor.is_verified
        # No commit here - endpoint handles transaction

        status = "verified" if vendor.is_verified else "unverified"
        logger.info(f"Vendor {vendor.vendor_code} {status}")
        return vendor, f"Vendor {vendor.vendor_code} is now {status}"

    def set_verification(
        self, db: Session, vendor_id: int, is_verified: bool
    ) -> tuple[Vendor, str]:
        """
        Set vendor verification status to specific value.

        Args:
            db: Database session
            vendor_id: Vendor ID
            is_verified: Target verification status

        Returns:
            Tuple of (updated vendor, status message)

        Raises:
            VendorNotFoundException: If vendor not found
        """
        vendor = self.get_vendor_by_id(db, vendor_id)
        vendor.is_verified = is_verified
        # No commit here - endpoint handles transaction

        status = "verified" if is_verified else "unverified"
        logger.info(f"Vendor {vendor.vendor_code} set to {status}")
        return vendor, f"Vendor {vendor.vendor_code} is now {status}"

    def toggle_status(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
        """
        Toggle vendor active status.

        Args:
            db: Database session
            vendor_id: Vendor ID

        Returns:
            Tuple of (updated vendor, status message)

        Raises:
            VendorNotFoundException: If vendor not found
        """
        vendor = self.get_vendor_by_id(db, vendor_id)
        vendor.is_active = not vendor.is_active
        # No commit here - endpoint handles transaction

        status = "active" if vendor.is_active else "inactive"
        logger.info(f"Vendor {vendor.vendor_code} {status}")
        return vendor, f"Vendor {vendor.vendor_code} is now {status}"

    def set_status(
        self, db: Session, vendor_id: int, is_active: bool
    ) -> tuple[Vendor, str]:
        """
        Set vendor active status to specific value.

        Args:
            db: Database session
            vendor_id: Vendor ID
            is_active: Target active status

        Returns:
            Tuple of (updated vendor, status message)

        Raises:
            VendorNotFoundException: If vendor not found
        """
        vendor = self.get_vendor_by_id(db, vendor_id)
        vendor.is_active = is_active
        # No commit here - endpoint handles transaction

        status = "active" if is_active else "inactive"
        logger.info(f"Vendor {vendor.vendor_code} set to {status}")
        return vendor, f"Vendor {vendor.vendor_code} is now {status}"

    def add_product_to_catalog(
        self, db: Session, vendor: Vendor, product: ProductCreate
    ) -> Product:
        """
        Add existing product to vendor catalog with vendor -specific settings.

        Args:
            db: Database session
            vendor : Vendor to add product to
            product: Vendor product data

        Returns:
            Created Product object

        Raises:
            MarketplaceProductNotFoundException: If product not found
            ProductAlreadyExistsException: If product already in vendor
        """
        try:
            # Check if product exists
            marketplace_product = self._get_product_by_id_or_raise(
                db, product.marketplace_product_id
            )

            # Check if product already in vendor
            if self._product_in_catalog(db, vendor.id, marketplace_product.id):
                raise ProductAlreadyExistsException(
                    vendor.vendor_code, product.marketplace_product_id
                )

            # Create vendor-product association
            new_product = Product(
                vendor_id=vendor.id,
                marketplace_product_id=marketplace_product.id,
                **product.model_dump(exclude={"marketplace_product_id"}),
            )

            db.add(new_product)
            db.flush()  # Get ID without committing - endpoint handles commit

            logger.info(
                f"MarketplaceProduct {product.marketplace_product_id} added to vendor {vendor.vendor_code}"
            )
            return new_product

        except (MarketplaceProductNotFoundException, ProductAlreadyExistsException):
            raise  # Re-raise custom exceptions - endpoint handles rollback
        except Exception as e:
            logger.error(f"Error adding product to vendor : {str(e)}")
            raise ValidationException("Failed to add product to vendor ")

    def get_products(
        self,
        db: Session,
        vendor: Vendor,
        current_user: User,
        skip: int = 0,
        limit: int = 100,
        active_only: bool = True,
        featured_only: bool = False,
    ) -> tuple[list[Product], int]:
        """
        Get products in vendor catalog with filtering.

        Args:
            db: Database session
            vendor : Vendor to get products from
            current_user: Current user requesting products
            skip: Number of records to skip
            limit: Maximum number of records to return
            active_only: Filter for active products only
            featured_only: Filter for featured products only

        Returns:
            Tuple of (products_list, total_count)

        Raises:
            UnauthorizedVendorAccessException: If vendor access denied
        """
        try:
            # Check access permissions
            if not self._can_access_vendor(vendor, current_user):
                raise UnauthorizedVendorAccessException(
                    vendor.vendor_code, current_user.id
                )

            # Query vendor products
            query = db.query(Product).filter(Product.vendor_id == vendor.id)

            if active_only:
                query = query.filter(Product.is_active == True)
            if featured_only:
                query = query.filter(Product.is_featured == True)

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

            return products, total

        except UnauthorizedVendorAccessException:
            raise  # Re-raise custom exceptions
        except Exception as e:
            logger.error(f"Error getting vendor products: {str(e)}")
            raise ValidationException("Failed to retrieve vendor products")

    # Private helper methods
    def _vendor_code_exists(self, db: Session, vendor_code: str) -> bool:
        """Check if vendor code already exists (case-insensitive)."""
        return (
            db.query(Vendor)
            .filter(func.upper(Vendor.vendor_code) == vendor_code.upper())
            .first()
            is not None
        )

    def _get_product_by_id_or_raise(
        self, db: Session, marketplace_product_id: int
    ) -> MarketplaceProduct:
        """Get marketplace product by database ID or raise exception."""
        product = (
            db.query(MarketplaceProduct)
            .filter(MarketplaceProduct.id == marketplace_product_id)
            .first()
        )
        if not product:
            raise MarketplaceProductNotFoundException(str(marketplace_product_id))
        return product

    def _product_in_catalog(
        self, db: Session, vendor_id: int, marketplace_product_id: int
    ) -> bool:
        """Check if product is already in vendor."""
        return (
            db.query(Product)
            .filter(
                Product.vendor_id == vendor_id,
                Product.marketplace_product_id == marketplace_product_id,
            )
            .first()
            is not None
        )

    def _can_access_vendor(self, vendor: Vendor, user: User) -> bool:
        """Check if user can access vendor."""
        # Admins can always access
        if user.role == "admin":
            return True

        # Company owners can access their vendors
        if vendor.company and vendor.company.owner_user_id == user.id:
            return True

        # Others can only access active and verified vendors
        return vendor.is_active and vendor.is_verified

    def _is_vendor_owner(self, vendor: Vendor, user: User) -> bool:
        """Check if user is vendor owner (via company ownership)."""
        return vendor.company and vendor.company.owner_user_id == user.id

    def can_update_vendor(self, vendor: Vendor, user: User) -> bool:
        """
        Check if user has permission to update vendor settings.

        Permission granted to:
        - Admins (always)
        - Vendor owners (company owner)
        - Team members with appropriate role (owner role in VendorUser)
        """
        # Admins can always update
        if user.role == "admin":
            return True

        # Check if user is vendor owner via company
        if self._is_vendor_owner(vendor, user):
            return True

        # Check if user is owner via VendorUser relationship
        if user.is_owner_of(vendor.id):
            return True

        return False

    def update_vendor(
        self,
        db: Session,
        vendor_id: int,
        vendor_update,
        current_user: User,
    ) -> "Vendor":
        """
        Update vendor profile with permission checking.

        Raises:
            VendorNotFoundException: If vendor not found
            InsufficientPermissionsException: If user lacks permission
        """
        from app.exceptions import InsufficientPermissionsException

        vendor = self.get_vendor_by_id(db, vendor_id)

        # Check permissions in service layer
        if not self.can_update_vendor(vendor, current_user):
            raise InsufficientPermissionsException(
                required_permission="vendor:profile:update"
            )

        # Apply updates
        update_data = vendor_update.model_dump(exclude_unset=True)
        for field, value in update_data.items():
            if hasattr(vendor, field):
                setattr(vendor, field, value)

        db.add(vendor)
        db.flush()
        db.refresh(vendor)
        return vendor

    def update_marketplace_settings(
        self,
        db: Session,
        vendor_id: int,
        marketplace_config: dict,
        current_user: User,
    ) -> dict:
        """
        Update marketplace integration settings with permission checking.

        Raises:
            VendorNotFoundException: If vendor not found
            InsufficientPermissionsException: If user lacks permission
        """
        from app.exceptions import InsufficientPermissionsException

        vendor = self.get_vendor_by_id(db, vendor_id)

        # Check permissions in service layer
        if not self.can_update_vendor(vendor, current_user):
            raise InsufficientPermissionsException(
                required_permission="vendor:settings:update"
            )

        # Update Letzshop URLs
        if "letzshop_csv_url_fr" in marketplace_config:
            vendor.letzshop_csv_url_fr = marketplace_config["letzshop_csv_url_fr"]
        if "letzshop_csv_url_en" in marketplace_config:
            vendor.letzshop_csv_url_en = marketplace_config["letzshop_csv_url_en"]
        if "letzshop_csv_url_de" in marketplace_config:
            vendor.letzshop_csv_url_de = marketplace_config["letzshop_csv_url_de"]

        db.add(vendor)
        db.flush()
        db.refresh(vendor)

        return {
            "message": "Marketplace settings updated successfully",
            "letzshop_csv_url_fr": vendor.letzshop_csv_url_fr,
            "letzshop_csv_url_en": vendor.letzshop_csv_url_en,
            "letzshop_csv_url_de": vendor.letzshop_csv_url_de,
        }


# Create service instance following the same pattern as other services
vendor_service = VendorService()
