# tests/unit/services/test_customer_address_service.py
"""
Unit tests for CustomerAddressService.
"""

import pytest

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


@pytest.fixture
def address_service():
    """Create CustomerAddressService instance."""
    return CustomerAddressService()


@pytest.fixture
def multiple_addresses(db, test_store, test_customer):
    """Create multiple addresses for testing."""
    addresses = []
    for i in range(3):
        address = CustomerAddress(
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_type="shipping" if i < 2 else "billing",
            first_name=f"First{i}",
            last_name=f"Last{i}",
            address_line_1=f"{i+1} Test Street",
            city="Luxembourg",
            postal_code=f"L-{1000+i}",
            country_name="Luxembourg",
            country_iso="LU",
            is_default=(i == 0),  # First shipping is default
        )
        db.add(address)  # noqa: PERF006
        addresses.append(address)

    db.commit()
    for a in addresses:
        db.refresh(a)

    return addresses


@pytest.mark.unit
class TestCustomerAddressServiceList:
    """Tests for list_addresses method."""

    def test_list_addresses_empty(self, db, address_service, test_store, test_customer):
        """Test listing addresses when none exist."""
        addresses = address_service.list_addresses(
            db, store_id=test_store.id, customer_id=test_customer.id
        )

        assert addresses == []

    def test_list_addresses_basic(
        self, db, address_service, test_store, test_customer, test_customer_address
    ):
        """Test basic address listing."""
        addresses = address_service.list_addresses(
            db, store_id=test_store.id, customer_id=test_customer.id
        )

        assert len(addresses) == 1
        assert addresses[0].id == test_customer_address.id

    def test_list_addresses_ordered_by_default(
        self, db, address_service, test_store, test_customer, multiple_addresses
    ):
        """Test addresses are ordered by default flag first."""
        addresses = address_service.list_addresses(
            db, store_id=test_store.id, customer_id=test_customer.id
        )

        # Default address should be first
        assert addresses[0].is_default is True

    def test_list_addresses_store_isolation(
        self, db, address_service, test_store, test_customer, test_customer_address
    ):
        """Test addresses are isolated by store."""
        # Query with different store ID
        addresses = address_service.list_addresses(
            db, store_id=99999, customer_id=test_customer.id
        )

        assert addresses == []


@pytest.mark.unit
class TestCustomerAddressServiceGet:
    """Tests for get_address method."""

    def test_get_address_success(
        self, db, address_service, test_store, test_customer, test_customer_address
    ):
        """Test getting address by ID."""
        address = address_service.get_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_id=test_customer_address.id,
        )

        assert address.id == test_customer_address.id
        assert address.first_name == test_customer_address.first_name

    def test_get_address_not_found(
        self, db, address_service, test_store, test_customer
    ):
        """Test error when address not found."""
        with pytest.raises(AddressNotFoundException):
            address_service.get_address(
                db,
                store_id=test_store.id,
                customer_id=test_customer.id,
                address_id=99999,
            )

    def test_get_address_wrong_customer(
        self, db, address_service, test_store, test_customer, test_customer_address
    ):
        """Test cannot get another customer's address."""
        with pytest.raises(AddressNotFoundException):
            address_service.get_address(
                db,
                store_id=test_store.id,
                customer_id=99999,  # Different customer
                address_id=test_customer_address.id,
            )


@pytest.mark.unit
class TestCustomerAddressServiceGetDefault:
    """Tests for get_default_address method."""

    def test_get_default_address_exists(
        self, db, address_service, test_store, test_customer, multiple_addresses
    ):
        """Test getting default shipping address."""
        address = address_service.get_default_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_type="shipping",
        )

        assert address is not None
        assert address.is_default is True
        assert address.address_type == "shipping"

    def test_get_default_address_not_set(
        self, db, address_service, test_store, test_customer, multiple_addresses
    ):
        """Test getting default billing when none is set."""
        # Remove default from billing (none was set as default)
        address = address_service.get_default_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_type="billing",
        )

        # The billing address exists but is not default
        assert address is None


@pytest.mark.unit
class TestCustomerAddressServiceCreate:
    """Tests for create_address method."""

    def test_create_address_success(
        self, db, address_service, test_store, test_customer
    ):
        """Test creating a new address."""
        address_data = CustomerAddressCreate(
            address_type="shipping",
            first_name="John",
            last_name="Doe",
            address_line_1="123 New Street",
            city="Luxembourg",
            postal_code="L-1234",
            country_name="Luxembourg",
            country_iso="LU",
            is_default=False,
        )

        address = address_service.create_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_data=address_data,
        )
        db.commit()

        assert address.id is not None
        assert address.first_name == "John"
        assert address.last_name == "Doe"
        assert address.country_iso == "LU"
        assert address.country_name == "Luxembourg"

    def test_create_address_with_merchant(
        self, db, address_service, test_store, test_customer
    ):
        """Test creating address with merchant name."""
        address_data = CustomerAddressCreate(
            address_type="billing",
            first_name="Jane",
            last_name="Doe",
            company="Acme Corp",
            address_line_1="456 Business Ave",
            city="Luxembourg",
            postal_code="L-5678",
            country_name="Luxembourg",
            country_iso="LU",
            is_default=False,
        )

        address = address_service.create_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_data=address_data,
        )
        db.commit()

        assert address.company == "Acme Corp"

    def test_create_address_default_clears_others(
        self, db, address_service, test_store, test_customer, multiple_addresses
    ):
        """Test creating default address clears other defaults of same type."""
        # First address is default shipping
        assert multiple_addresses[0].is_default is True

        address_data = CustomerAddressCreate(
            address_type="shipping",
            first_name="New",
            last_name="Default",
            address_line_1="789 Main St",
            city="Luxembourg",
            postal_code="L-9999",
            country_name="Luxembourg",
            country_iso="LU",
            is_default=True,
        )

        new_address = address_service.create_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_data=address_data,
        )
        db.commit()

        # New address should be default
        assert new_address.is_default is True

        # Old default should be cleared
        db.refresh(multiple_addresses[0])
        assert multiple_addresses[0].is_default is False

    def test_create_address_limit_exceeded(
        self, db, address_service, test_store, test_customer
    ):
        """Test error when max addresses reached."""
        # Create 10 addresses (max limit)
        for i in range(10):
            addr = CustomerAddress(
                store_id=test_store.id,
                customer_id=test_customer.id,
                address_type="shipping",
                first_name=f"Test{i}",
                last_name="User",
                address_line_1=f"{i} Street",
                city="City",
                postal_code="12345",
                country_name="Luxembourg",
                country_iso="LU",
            )
            db.add(addr)  # noqa: PERF006
        db.commit()

        # Try to create 11th address
        address_data = CustomerAddressCreate(
            address_type="shipping",
            first_name="Eleventh",
            last_name="User",
            address_line_1="11 Street",
            city="City",
            postal_code="12345",
            country_name="Luxembourg",
            country_iso="LU",
            is_default=False,
        )

        with pytest.raises(AddressLimitExceededException):
            address_service.create_address(
                db,
                store_id=test_store.id,
                customer_id=test_customer.id,
                address_data=address_data,
            )


@pytest.mark.unit
class TestCustomerAddressServiceUpdate:
    """Tests for update_address method."""

    def test_update_address_success(
        self, db, address_service, test_store, test_customer, test_customer_address
    ):
        """Test updating an address."""
        update_data = CustomerAddressUpdate(
            first_name="Updated",
            city="New City",
        )

        address = address_service.update_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_id=test_customer_address.id,
            address_data=update_data,
        )
        db.commit()

        assert address.first_name == "Updated"
        assert address.city == "New City"
        # Unchanged fields should remain
        assert address.last_name == test_customer_address.last_name

    def test_update_address_set_default(
        self, db, address_service, test_store, test_customer, multiple_addresses
    ):
        """Test setting address as default clears others."""
        # Second address is not default
        assert multiple_addresses[1].is_default is False

        update_data = CustomerAddressUpdate(is_default=True)

        address = address_service.update_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_id=multiple_addresses[1].id,
            address_data=update_data,
        )
        db.commit()

        assert address.is_default is True

        # Old default should be cleared
        db.refresh(multiple_addresses[0])
        assert multiple_addresses[0].is_default is False

    def test_update_address_not_found(
        self, db, address_service, test_store, test_customer
    ):
        """Test error when address not found."""
        update_data = CustomerAddressUpdate(first_name="Test")

        with pytest.raises(AddressNotFoundException):
            address_service.update_address(
                db,
                store_id=test_store.id,
                customer_id=test_customer.id,
                address_id=99999,
                address_data=update_data,
            )


@pytest.mark.unit
class TestCustomerAddressServiceDelete:
    """Tests for delete_address method."""

    def test_delete_address_success(
        self, db, address_service, test_store, test_customer, test_customer_address
    ):
        """Test deleting an address."""
        address_id = test_customer_address.id

        address_service.delete_address(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_id=address_id,
        )
        db.commit()

        # Address should be gone
        with pytest.raises(AddressNotFoundException):
            address_service.get_address(
                db,
                store_id=test_store.id,
                customer_id=test_customer.id,
                address_id=address_id,
            )

    def test_delete_address_not_found(
        self, db, address_service, test_store, test_customer
    ):
        """Test error when deleting non-existent address."""
        with pytest.raises(AddressNotFoundException):
            address_service.delete_address(
                db,
                store_id=test_store.id,
                customer_id=test_customer.id,
                address_id=99999,
            )


@pytest.mark.unit
class TestCustomerAddressServiceSetDefault:
    """Tests for set_default method."""

    def test_set_default_success(
        self, db, address_service, test_store, test_customer, multiple_addresses
    ):
        """Test setting address as default."""
        # Second shipping address is not default
        assert multiple_addresses[1].is_default is False
        assert multiple_addresses[1].address_type == "shipping"

        address = address_service.set_default(
            db,
            store_id=test_store.id,
            customer_id=test_customer.id,
            address_id=multiple_addresses[1].id,
        )
        db.commit()

        assert address.is_default is True

        # Old default should be cleared
        db.refresh(multiple_addresses[0])
        assert multiple_addresses[0].is_default is False

    def test_set_default_not_found(
        self, db, address_service, test_store, test_customer
    ):
        """Test error when address not found."""
        with pytest.raises(AddressNotFoundException):
            address_service.set_default(
                db,
                store_id=test_store.id,
                customer_id=test_customer.id,
                address_id=99999,
            )
