# app/modules/tenancy/services/merchant_service.py
"""
Merchant service for managing merchant operations.

This service handles CRUD operations for merchants and merchant-store relationships.
"""

import logging
import secrets
import string

from sqlalchemy import func, select
from sqlalchemy.orm import Session, joinedload

from app.modules.tenancy.exceptions import (
    MerchantNotFoundException,
    UserNotFoundException,
)
from app.modules.tenancy.models import Merchant, Store, User
from app.modules.tenancy.schemas.merchant import (
    MerchantCreate,
    MerchantTransferOwnership,
    MerchantUpdate,
)

logger = logging.getLogger(__name__)


class MerchantService:
    """Service for managing merchants."""

    def __init__(self):
        """Initialize merchant service."""

    def create_merchant_with_owner(
        self, db: Session, merchant_data: MerchantCreate
    ) -> tuple[Merchant, User, str]:
        """
        Create a new merchant with an owner user account.

        Args:
            db: Database session
            merchant_data: Merchant creation data

        Returns:
            Tuple of (merchant, owner_user, temporary_password)
        """
        # Import AuthManager for password hashing (same pattern as admin_service)
        from middleware.auth import AuthManager

        auth_manager = AuthManager()

        # Check if owner email already exists
        existing_user = db.execute(
            select(User).where(User.email == merchant_data.owner_email)
        ).scalar_one_or_none()

        if existing_user:
            # Use existing user as owner
            owner_user = existing_user
            temp_password = None
            logger.info(f"Using existing user {owner_user.email} as merchant owner")
        else:
            # Generate temporary password for owner
            temp_password = self._generate_temp_password()

            # Create new owner user
            owner_user = User(
                username=merchant_data.owner_email.split("@")[0],
                email=merchant_data.owner_email,
                hashed_password=auth_manager.hash_password(temp_password),
                role="merchant_owner",
                is_active=True,
                is_email_verified=False,
            )
            db.add(owner_user)
            db.flush()  # Get owner_user.id
            logger.info(f"Created new owner user: {owner_user.email}")

        # Create merchant
        merchant = Merchant(
            name=merchant_data.name,
            description=merchant_data.description,
            owner_user_id=owner_user.id,
            contact_email=merchant_data.contact_email,
            contact_phone=merchant_data.contact_phone,
            website=merchant_data.website,
            business_address=merchant_data.business_address,
            tax_number=merchant_data.tax_number,
            is_active=True,
            is_verified=False,
        )
        db.add(merchant)
        db.flush()
        logger.info(f"Created merchant: {merchant.name} (ID: {merchant.id})")

        return merchant, owner_user, temp_password

    def get_merchant_by_id(self, db: Session, merchant_id: int) -> Merchant:
        """
        Get merchant by ID.

        Args:
            db: Database session
            merchant_id: Merchant ID

        Returns:
            Merchant object

        Raises:
            MerchantNotFoundException: If merchant not found
        """
        merchant = (
            db.execute(
                select(Merchant)
                .where(Merchant.id == merchant_id)
                .options(joinedload(Merchant.stores))
            )
            .unique()
            .scalar_one_or_none()
        )

        if not merchant:
            raise MerchantNotFoundException(merchant_id)

        return merchant

    def get_merchant_by_id_optional(
        self, db: Session, merchant_id: int
    ) -> Merchant | None:
        """
        Get merchant by ID, returns None if not found.

        Args:
            db: Database session
            merchant_id: Merchant ID

        Returns:
            Merchant object or None
        """
        return db.query(Merchant).filter(Merchant.id == merchant_id).first()

    def get_merchants(
        self,
        db: Session,
        skip: int = 0,
        limit: int = 100,
        search: str | None = None,
        is_active: bool | None = None,
        is_verified: bool | None = None,
        include_deleted: bool = False,
        only_deleted: bool = False,
    ) -> tuple[list[Merchant], int]:
        """
        Get paginated list of merchants with optional filters.

        Args:
            db: Database session
            skip: Number of records to skip
            limit: Maximum number of records to return
            search: Search term for merchant name
            is_active: Filter by active status
            is_verified: Filter by verified status
            include_deleted: Include soft-deleted merchants
            only_deleted: Show only soft-deleted merchants (trash view)

        Returns:
            Tuple of (merchants list, total count)
        """
        exec_opts = {}
        if include_deleted or only_deleted:
            exec_opts["include_deleted"] = True

        query = select(Merchant).options(
            joinedload(Merchant.stores),
            joinedload(Merchant.owner),
        )

        # Soft-delete filter
        if only_deleted:
            query = query.where(Merchant.deleted_at.isnot(None))

        # Apply filters
        if search:
            query = query.where(Merchant.name.ilike(f"%{search}%"))
        if is_active is not None:
            query = query.where(Merchant.is_active == is_active)
        if is_verified is not None:
            query = query.where(Merchant.is_verified == is_verified)

        # Get total count
        count_query = select(func.count()).select_from(query.subquery())
        total = db.execute(count_query, execution_options=exec_opts).scalar()

        # Apply pagination and order
        query = query.order_by(Merchant.name).offset(skip).limit(limit)

        # Use unique() when using joinedload with collections to avoid duplicate rows
        merchants = list(db.execute(query, execution_options=exec_opts).scalars().unique().all())

        return merchants, total

    def update_merchant(
        self, db: Session, merchant_id: int, merchant_data: MerchantUpdate
    ) -> Merchant:
        """
        Update merchant information.

        Args:
            db: Database session
            merchant_id: Merchant ID
            merchant_data: Updated merchant data

        Returns:
            Updated merchant

        Raises:
            MerchantNotFoundException: If merchant not found
        """
        merchant = self.get_merchant_by_id(db, merchant_id)

        # Update only provided fields
        update_data = merchant_data.model_dump(exclude_unset=True)
        for field, value in update_data.items():
            setattr(merchant, field, value)

        db.flush()
        logger.info(f"Updated merchant ID {merchant_id}")

        return merchant

    def delete_merchant(self, db: Session, merchant_id: int) -> None:
        """
        Delete a merchant and all associated stores.

        Args:
            db: Database session
            merchant_id: Merchant ID

        Raises:
            MerchantNotFoundException: If merchant not found
        """
        from app.core.soft_delete import soft_delete_cascade

        merchant = self.get_merchant_by_id(db, merchant_id)

        MERCHANT_CASCADE = [
            ("stores", [
                ("products", []),
                ("customers", []),
                ("orders", []),
                ("store_users", []),
            ]),
        ]
        soft_delete_cascade(db, merchant, deleted_by_id=None, cascade_rels=MERCHANT_CASCADE)
        logger.info(f"Deleted merchant ID {merchant_id} and associated stores")

    def toggle_verification(
        self, db: Session, merchant_id: int, is_verified: bool
    ) -> Merchant:
        """
        Toggle merchant verification status.

        Args:
            db: Database session
            merchant_id: Merchant ID
            is_verified: New verification status

        Returns:
            Updated merchant

        Raises:
            MerchantNotFoundException: If merchant not found
        """
        merchant = self.get_merchant_by_id(db, merchant_id)
        merchant.is_verified = is_verified
        db.flush()
        logger.info(f"Merchant ID {merchant_id} verification set to {is_verified}")

        return merchant

    def toggle_active(self, db: Session, merchant_id: int, is_active: bool) -> Merchant:
        """
        Toggle merchant active status.

        Args:
            db: Database session
            merchant_id: Merchant ID
            is_active: New active status

        Returns:
            Updated merchant

        Raises:
            MerchantNotFoundException: If merchant not found
        """
        merchant = self.get_merchant_by_id(db, merchant_id)
        merchant.is_active = is_active
        db.flush()
        logger.info(f"Merchant ID {merchant_id} active status set to {is_active}")

        return merchant

    def transfer_ownership(
        self,
        db: Session,
        merchant_id: int,
        transfer_data: MerchantTransferOwnership,
    ) -> tuple[Merchant, User, User]:
        """
        Transfer merchant ownership to another user.

        This is a critical operation that:
        - Changes the merchant's owner_user_id
        - All stores under the merchant automatically inherit the new owner
        - Logs the transfer for audit purposes

        Args:
            db: Database session
            merchant_id: Merchant ID
            transfer_data: Transfer ownership data

        Returns:
            Tuple of (merchant, old_owner, new_owner)

        Raises:
            MerchantNotFoundException: If merchant not found
            UserNotFoundException: If new owner user not found
            ValueError: If trying to transfer to current owner
        """
        # Get merchant
        merchant = self.get_merchant_by_id(db, merchant_id)
        old_owner_id = merchant.owner_user_id

        # Get old owner
        old_owner = db.execute(
            select(User).where(User.id == old_owner_id)
        ).scalar_one_or_none()
        if not old_owner:
            raise UserNotFoundException(str(old_owner_id))

        # Get new owner
        new_owner = db.execute(
            select(User).where(User.id == transfer_data.new_owner_user_id)
        ).scalar_one_or_none()
        if not new_owner:
            raise UserNotFoundException(str(transfer_data.new_owner_user_id))

        # Prevent transferring to same owner
        if old_owner_id == transfer_data.new_owner_user_id:
            raise ValueError("Cannot transfer ownership to the current owner")

        # Update merchant owner (stores inherit ownership via merchant relationship)
        merchant.owner_user_id = new_owner.id

        db.flush()

        logger.info(
            f"Merchant {merchant.id} ({merchant.name}) ownership transferred "
            f"from user {old_owner.id} ({old_owner.email}) "
            f"to user {new_owner.id} ({new_owner.email}). "
            f"Reason: {transfer_data.transfer_reason or 'Not specified'}"
        )

        return merchant, old_owner, new_owner

    def get_merchant_by_owner_id(
        self, db: Session, owner_user_id: int
    ) -> Merchant | None:
        """
        Get merchant by owner user ID.

        Args:
            db: Database session
            owner_user_id: Owner user ID

        Returns:
            First active merchant owned by the user, or None
        """
        return (
            db.query(Merchant)
            .filter(
                Merchant.owner_user_id == owner_user_id,
                Merchant.is_active == True,  # noqa: E712
            )
            .first()
        )

    def get_merchant_count_for_owner(
        self, db: Session, owner_user_id: int, active_only: bool = True
    ) -> int:
        """
        Count merchants owned by a user.

        Args:
            db: Database session
            owner_user_id: Owner user ID
            active_only: Only count active merchants

        Returns:
            Number of merchants
        """
        query = db.query(func.count(Merchant.id)).filter(
            Merchant.owner_user_id == owner_user_id
        )
        if active_only:
            query = query.filter(Merchant.is_active == True)  # noqa: E712
        return query.scalar() or 0

    def get_merchant_stores(
        self, db: Session, merchant_id: int, skip: int = 0, limit: int = 100
    ) -> tuple[list, int]:
        """Get paginated stores for a merchant."""
        query = db.query(Store).filter(Store.merchant_id == merchant_id)
        total = query.count()
        stores = query.order_by(Store.id).offset(skip).limit(limit).all()
        return stores, total

    def _generate_temp_password(self, length: int = 12) -> str:
        """Generate secure temporary password."""
        alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
        return "".join(secrets.choice(alphabet) for _ in range(length))


# Create service instance following the same pattern as other services
merchant_service = MerchantService()
