# tests/integration/api/v1/storefront/test_addresses.py
"""Integration tests for shop addresses API endpoints.

Tests the /api/v1/storefront/addresses/* endpoints.
All endpoints require customer JWT authentication with vendor context.
"""

from datetime import UTC, datetime, timedelta
from unittest.mock import patch

import pytest
from jose import jwt

from app.modules.customers.models.customer import Customer, CustomerAddress


@pytest.fixture
def shop_customer(db, test_vendor):
    """Create a test customer for shop API tests."""
    from middleware.auth import AuthManager

    auth_manager = AuthManager()
    customer = Customer(
        vendor_id=test_vendor.id,
        email="shopcustomer@example.com",
        hashed_password=auth_manager.hash_password("testpass123"),
        first_name="Shop",
        last_name="Customer",
        customer_number="SHOP001",
        is_active=True,
    )
    db.add(customer)
    db.commit()
    db.refresh(customer)
    return customer


@pytest.fixture
def shop_customer_token(shop_customer, test_vendor):
    """Create JWT token for shop customer."""
    from middleware.auth import AuthManager

    auth_manager = AuthManager()

    expires_delta = timedelta(minutes=auth_manager.token_expire_minutes)
    expire = datetime.now(UTC) + expires_delta

    payload = {
        "sub": str(shop_customer.id),
        "email": shop_customer.email,
        "vendor_id": test_vendor.id,
        "type": "customer",
        "exp": expire,
        "iat": datetime.now(UTC),
    }

    token = jwt.encode(
        payload, auth_manager.secret_key, algorithm=auth_manager.algorithm
    )
    return token


@pytest.fixture
def shop_customer_headers(shop_customer_token):
    """Get authentication headers for shop customer."""
    return {"Authorization": f"Bearer {shop_customer_token}"}


@pytest.fixture
def customer_address(db, test_vendor, shop_customer):
    """Create a test address for shop customer."""
    address = CustomerAddress(
        vendor_id=test_vendor.id,
        customer_id=shop_customer.id,
        address_type="shipping",
        first_name="Ship",
        last_name="Address",
        address_line_1="123 Shipping St",
        city="Luxembourg",
        postal_code="L-1234",
        country_name="Luxembourg",
        country_iso="LU",
        is_default=True,
    )
    db.add(address)
    db.commit()
    db.refresh(address)
    return address


@pytest.fixture
def customer_billing_address(db, test_vendor, shop_customer):
    """Create a billing address for shop customer."""
    address = CustomerAddress(
        vendor_id=test_vendor.id,
        customer_id=shop_customer.id,
        address_type="billing",
        first_name="Bill",
        last_name="Address",
        company="Test Company",
        address_line_1="456 Billing Ave",
        city="Esch-sur-Alzette",
        postal_code="L-5678",
        country_name="Luxembourg",
        country_iso="LU",
        is_default=True,
    )
    db.add(address)
    db.commit()
    db.refresh(address)
    return address


@pytest.fixture
def other_customer(db, test_vendor):
    """Create another customer for testing access controls."""
    from middleware.auth import AuthManager

    auth_manager = AuthManager()
    customer = Customer(
        vendor_id=test_vendor.id,
        email="othercustomer@example.com",
        hashed_password=auth_manager.hash_password("otherpass123"),
        first_name="Other",
        last_name="Customer",
        customer_number="OTHER001",
        is_active=True,
    )
    db.add(customer)
    db.commit()
    db.refresh(customer)
    return customer


@pytest.fixture
def other_customer_address(db, test_vendor, other_customer):
    """Create an address for another customer."""
    address = CustomerAddress(
        vendor_id=test_vendor.id,
        customer_id=other_customer.id,
        address_type="shipping",
        first_name="Other",
        last_name="Address",
        address_line_1="999 Other St",
        city="Differdange",
        postal_code="L-9999",
        country_name="Luxembourg",
        country_iso="LU",
        is_default=True,
    )
    db.add(address)
    db.commit()
    db.refresh(address)
    return address


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressesListAPI:
    """Test shop addresses list endpoint at /api/v1/storefront/addresses."""

    def test_list_addresses_requires_authentication(self, client, test_vendor):
        """Test that listing addresses requires authentication."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            response = client.get("/api/v1/storefront/addresses")
            assert response.status_code in [401, 403]

    def test_list_addresses_success(
        self,
        client,
        shop_customer_headers,
        customer_address,
        test_vendor,
        shop_customer,
    ):
        """Test listing customer addresses successfully."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.get(
                    "/api/v1/storefront/addresses",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 200
                data = response.json()
                assert "addresses" in data
                assert "total" in data
                assert data["total"] == 1
                assert data["addresses"][0]["first_name"] == "Ship"

    def test_list_addresses_empty(
        self, client, shop_customer_headers, test_vendor, shop_customer
    ):
        """Test listing addresses when customer has none."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.get(
                    "/api/v1/storefront/addresses",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 200
                data = response.json()
                assert data["total"] == 0
                assert data["addresses"] == []

    def test_list_addresses_multiple_types(
        self,
        client,
        shop_customer_headers,
        customer_address,
        customer_billing_address,
        test_vendor,
        shop_customer,
    ):
        """Test listing addresses includes both shipping and billing."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.get(
                    "/api/v1/storefront/addresses",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 200
                data = response.json()
                assert data["total"] == 2

                types = {addr["address_type"] for addr in data["addresses"]}
                assert "shipping" in types
                assert "billing" in types


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressDetailAPI:
    """Test shop address detail endpoint at /api/v1/storefront/addresses/{address_id}."""

    def test_get_address_success(
        self,
        client,
        shop_customer_headers,
        customer_address,
        test_vendor,
        shop_customer,
    ):
        """Test getting address details successfully."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.get(
                    f"/api/v1/storefront/addresses/{customer_address.id}",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 200
                data = response.json()
                assert data["id"] == customer_address.id
                assert data["first_name"] == "Ship"
                assert data["country_iso"] == "LU"
                assert data["country_name"] == "Luxembourg"

    def test_get_address_not_found(
        self, client, shop_customer_headers, test_vendor, shop_customer
    ):
        """Test getting non-existent address returns 404."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.get(
                    "/api/v1/storefront/addresses/99999",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 404

    def test_get_address_other_customer(
        self,
        client,
        shop_customer_headers,
        other_customer_address,
        test_vendor,
        shop_customer,
    ):
        """Test cannot access another customer's address."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.get(
                    f"/api/v1/storefront/addresses/{other_customer_address.id}",
                    headers=shop_customer_headers,
                )

                # Should return 404 to prevent enumeration
                assert response.status_code == 404


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressCreateAPI:
    """Test shop address creation at POST /api/v1/storefront/addresses."""

    def test_create_address_success(
        self, client, shop_customer_headers, test_vendor, shop_customer
    ):
        """Test creating a new address."""
        address_data = {
            "address_type": "shipping",
            "first_name": "New",
            "last_name": "Address",
            "address_line_1": "789 New St",
            "city": "Luxembourg",
            "postal_code": "L-1111",
            "country_name": "Luxembourg",
            "country_iso": "LU",
            "is_default": False,
        }

        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.post(
                    "/api/v1/storefront/addresses",
                    headers=shop_customer_headers,
                    json=address_data,
                )

                assert response.status_code == 201
                data = response.json()
                assert data["first_name"] == "New"
                assert data["last_name"] == "Address"
                assert data["country_iso"] == "LU"
                assert "id" in data

    def test_create_address_with_company(
        self, client, shop_customer_headers, test_vendor, shop_customer
    ):
        """Test creating address with company name."""
        address_data = {
            "address_type": "billing",
            "first_name": "Business",
            "last_name": "Address",
            "company": "Acme Corp",
            "address_line_1": "100 Business Park",
            "city": "Luxembourg",
            "postal_code": "L-2222",
            "country_name": "Luxembourg",
            "country_iso": "LU",
            "is_default": True,
        }

        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.post(
                    "/api/v1/storefront/addresses",
                    headers=shop_customer_headers,
                    json=address_data,
                )

                assert response.status_code == 201
                data = response.json()
                assert data["company"] == "Acme Corp"

    def test_create_address_validation_error(
        self, client, shop_customer_headers, test_vendor, shop_customer
    ):
        """Test validation error for missing required fields."""
        address_data = {
            "address_type": "shipping",
            "first_name": "Test",
            # Missing required fields
        }

        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.post(
                    "/api/v1/storefront/addresses",
                    headers=shop_customer_headers,
                    json=address_data,
                )

                assert response.status_code == 422


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressUpdateAPI:
    """Test shop address update at PUT /api/v1/storefront/addresses/{address_id}."""

    def test_update_address_success(
        self,
        client,
        shop_customer_headers,
        customer_address,
        test_vendor,
        shop_customer,
    ):
        """Test updating an address."""
        update_data = {
            "first_name": "Updated",
            "city": "Esch-sur-Alzette",
        }

        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.put(
                    f"/api/v1/storefront/addresses/{customer_address.id}",
                    headers=shop_customer_headers,
                    json=update_data,
                )

                assert response.status_code == 200
                data = response.json()
                assert data["first_name"] == "Updated"
                assert data["city"] == "Esch-sur-Alzette"

    def test_update_address_not_found(
        self, client, shop_customer_headers, test_vendor, shop_customer
    ):
        """Test updating non-existent address returns 404."""
        update_data = {"first_name": "Test"}

        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.put(
                    "/api/v1/storefront/addresses/99999",
                    headers=shop_customer_headers,
                    json=update_data,
                )

                assert response.status_code == 404

    def test_update_address_other_customer(
        self,
        client,
        shop_customer_headers,
        other_customer_address,
        test_vendor,
        shop_customer,
    ):
        """Test cannot update another customer's address."""
        update_data = {"first_name": "Hacked"}

        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.put(
                    f"/api/v1/storefront/addresses/{other_customer_address.id}",
                    headers=shop_customer_headers,
                    json=update_data,
                )

                assert response.status_code == 404


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressDeleteAPI:
    """Test shop address deletion at DELETE /api/v1/storefront/addresses/{address_id}."""

    def test_delete_address_success(
        self,
        client,
        shop_customer_headers,
        customer_address,
        test_vendor,
        shop_customer,
    ):
        """Test deleting an address."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.delete(
                    f"/api/v1/storefront/addresses/{customer_address.id}",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 204

    def test_delete_address_not_found(
        self, client, shop_customer_headers, test_vendor, shop_customer
    ):
        """Test deleting non-existent address returns 404."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.delete(
                    "/api/v1/storefront/addresses/99999",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 404

    def test_delete_address_other_customer(
        self,
        client,
        shop_customer_headers,
        other_customer_address,
        test_vendor,
        shop_customer,
    ):
        """Test cannot delete another customer's address."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.delete(
                    f"/api/v1/storefront/addresses/{other_customer_address.id}",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 404


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.shop
class TestShopAddressSetDefaultAPI:
    """Test set address as default at PUT /api/v1/storefront/addresses/{address_id}/default."""

    def test_set_default_success(
        self,
        client,
        shop_customer_headers,
        customer_address,
        test_vendor,
        shop_customer,
        db,
    ):
        """Test setting address as default."""
        # Create a second non-default address
        second_address = CustomerAddress(
            vendor_id=test_vendor.id,
            customer_id=shop_customer.id,
            address_type="shipping",
            first_name="Second",
            last_name="Address",
            address_line_1="222 Second St",
            city="Dudelange",
            postal_code="L-3333",
            country_name="Luxembourg",
            country_iso="LU",
            is_default=False,
        )
        db.add(second_address)
        db.commit()
        db.refresh(second_address)

        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.put(
                    f"/api/v1/storefront/addresses/{second_address.id}/default",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 200
                data = response.json()
                assert data["is_default"] is True

    def test_set_default_not_found(
        self, client, shop_customer_headers, test_vendor, shop_customer
    ):
        """Test setting default on non-existent address returns 404."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.put(
                    "/api/v1/storefront/addresses/99999/default",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 404

    def test_set_default_other_customer(
        self,
        client,
        shop_customer_headers,
        other_customer_address,
        test_vendor,
        shop_customer,
    ):
        """Test cannot set default on another customer's address."""
        with patch("app.modules.customers.routes.api.storefront.addresses.getattr") as mock_getattr:
            mock_getattr.return_value = test_vendor
            with patch("app.api.deps._validate_customer_token") as mock_validate:
                mock_validate.return_value = shop_customer
                response = client.put(
                    f"/api/v1/storefront/addresses/{other_customer_address.id}/default",
                    headers=shop_customer_headers,
                )

                assert response.status_code == 404
