# app/modules/customers/services/customer_address_service.py
"""
Customer Address Service

Business logic for managing customer addresses with store isolation.
"""

import logging

from sqlalchemy.orm import Session

from app.modules.customers.exceptions import (
    AddressLimitExceededException,
    AddressNotFoundException,
)
from app.modules.customers.models import CustomerAddress
from app.modules.customers.schemas import CustomerAddressCreate, CustomerAddressUpdate

logger = logging.getLogger(__name__)


class CustomerAddressService:
    """Service for managing customer addresses with store isolation."""

    MAX_ADDRESSES_PER_CUSTOMER = 10

    def list_addresses(
        self, db: Session, store_id: int, customer_id: int
    ) -> list[CustomerAddress]:
        """
        Get all addresses for a customer.

        Args:
            db: Database session
            store_id: Store ID for isolation
            customer_id: Customer ID

        Returns:
            List of customer addresses
        """
        return (
            db.query(CustomerAddress)
            .filter(
                CustomerAddress.store_id == store_id,
                CustomerAddress.customer_id == customer_id,
            )
            .order_by(CustomerAddress.is_default.desc(), CustomerAddress.created_at.desc())
            .all()
        )

    def get_address(
        self, db: Session, store_id: int, customer_id: int, address_id: int
    ) -> CustomerAddress:
        """
        Get a specific address with ownership validation.

        Args:
            db: Database session
            store_id: Store ID for isolation
            customer_id: Customer ID
            address_id: Address ID

        Returns:
            Customer address

        Raises:
            AddressNotFoundException: If address not found or doesn't belong to customer
        """
        address = (
            db.query(CustomerAddress)
            .filter(
                CustomerAddress.id == address_id,
                CustomerAddress.store_id == store_id,
                CustomerAddress.customer_id == customer_id,
            )
            .first()
        )

        if not address:
            raise AddressNotFoundException(address_id)

        return address

    def get_default_address(
        self, db: Session, store_id: int, customer_id: int, address_type: str
    ) -> CustomerAddress | None:
        """
        Get the default address for a specific type.

        Args:
            db: Database session
            store_id: Store ID for isolation
            customer_id: Customer ID
            address_type: 'shipping' or 'billing'

        Returns:
            Default address or None if not set
        """
        return (
            db.query(CustomerAddress)
            .filter(
                CustomerAddress.store_id == store_id,
                CustomerAddress.customer_id == customer_id,
                CustomerAddress.address_type == address_type,
                CustomerAddress.is_default == True,  # noqa: E712
            )
            .first()
        )

    def create_address(
        self,
        db: Session,
        store_id: int,
        customer_id: int,
        address_data: CustomerAddressCreate,
    ) -> CustomerAddress:
        """
        Create a new address for a customer.

        Args:
            db: Database session
            store_id: Store ID for isolation
            customer_id: Customer ID
            address_data: Address creation data

        Returns:
            Created customer address

        Raises:
            AddressLimitExceededException: If max addresses reached
        """
        # Check address limit
        current_count = (
            db.query(CustomerAddress)
            .filter(
                CustomerAddress.store_id == store_id,
                CustomerAddress.customer_id == customer_id,
            )
            .count()
        )

        if current_count >= self.MAX_ADDRESSES_PER_CUSTOMER:
            raise AddressLimitExceededException(self.MAX_ADDRESSES_PER_CUSTOMER)

        # If setting as default, clear other defaults of same type
        if address_data.is_default:
            self._clear_other_defaults(
                db, store_id, customer_id, address_data.address_type
            )

        # Create the address
        address = CustomerAddress(
            store_id=store_id,
            customer_id=customer_id,
            address_type=address_data.address_type,
            first_name=address_data.first_name,
            last_name=address_data.last_name,
            company=address_data.company,
            address_line_1=address_data.address_line_1,
            address_line_2=address_data.address_line_2,
            city=address_data.city,
            postal_code=address_data.postal_code,
            country_name=address_data.country_name,
            country_iso=address_data.country_iso,
            is_default=address_data.is_default,
        )

        db.add(address)
        db.flush()

        logger.info(
            f"Created address {address.id} for customer {customer_id} "
            f"(type={address_data.address_type}, default={address_data.is_default})"
        )

        return address

    def update_address(
        self,
        db: Session,
        store_id: int,
        customer_id: int,
        address_id: int,
        address_data: CustomerAddressUpdate,
    ) -> CustomerAddress:
        """
        Update an existing address.

        Args:
            db: Database session
            store_id: Store ID for isolation
            customer_id: Customer ID
            address_id: Address ID
            address_data: Address update data

        Returns:
            Updated customer address

        Raises:
            AddressNotFoundException: If address not found
        """
        address = self.get_address(db, store_id, customer_id, address_id)

        # Update only provided fields
        update_data = address_data.model_dump(exclude_unset=True)

        # Handle default flag - clear others if setting to default
        if update_data.get("is_default") is True:
            # Use updated type if provided, otherwise current type
            address_type = update_data.get("address_type", address.address_type)
            self._clear_other_defaults(
                db, store_id, customer_id, address_type, exclude_id=address_id
            )

        for field, value in update_data.items():
            setattr(address, field, value)

        db.flush()

        logger.info(f"Updated address {address_id} for customer {customer_id}")

        return address

    def delete_address(
        self, db: Session, store_id: int, customer_id: int, address_id: int
    ) -> None:
        """
        Delete an address.

        Args:
            db: Database session
            store_id: Store ID for isolation
            customer_id: Customer ID
            address_id: Address ID

        Raises:
            AddressNotFoundException: If address not found
        """
        address = self.get_address(db, store_id, customer_id, address_id)

        db.delete(address)
        db.flush()

        logger.info(f"Deleted address {address_id} for customer {customer_id}")

    def set_default(
        self, db: Session, store_id: int, customer_id: int, address_id: int
    ) -> CustomerAddress:
        """
        Set an address as the default for its type.

        Args:
            db: Database session
            store_id: Store ID for isolation
            customer_id: Customer ID
            address_id: Address ID

        Returns:
            Updated customer address

        Raises:
            AddressNotFoundException: If address not found
        """
        address = self.get_address(db, store_id, customer_id, address_id)

        # Clear other defaults of same type
        self._clear_other_defaults(
            db, store_id, customer_id, address.address_type, exclude_id=address_id
        )

        # Set this one as default
        address.is_default = True
        db.flush()

        logger.info(
            f"Set address {address_id} as default {address.address_type} "
            f"for customer {customer_id}"
        )

        return address

    def _clear_other_defaults(
        self,
        db: Session,
        store_id: int,
        customer_id: int,
        address_type: str,
        exclude_id: int | None = None,
    ) -> None:
        """
        Clear the default flag on other addresses of the same type.

        Args:
            db: Database session
            store_id: Store ID for isolation
            customer_id: Customer ID
            address_type: 'shipping' or 'billing'
            exclude_id: Address ID to exclude from clearing
        """
        query = db.query(CustomerAddress).filter(
            CustomerAddress.store_id == store_id,
            CustomerAddress.customer_id == customer_id,
            CustomerAddress.address_type == address_type,
            CustomerAddress.is_default == True,  # noqa: E712
        )

        if exclude_id:
            query = query.filter(CustomerAddress.id != exclude_id)

        query.update({"is_default": False}, synchronize_session=False)


# Singleton instance
customer_address_service = CustomerAddressService()
