# tests/integration/api/v1/test_inventory_endpoints.py
import pytest

from models.database.inventory import Inventory


@pytest.mark.integration
@pytest.mark.api
@pytest.mark.inventory
class TestInventoryAPI:
    def test_set_inventory_new_success(self, client, auth_headers):
        """Test setting inventory for new GTIN successfully"""
        inventory_data = {
            "gtin": "1234567890123",
            "location": "WAREHOUSE_A",
            "quantity": 100,
        }

        response = client.post(
            "/api/v1/inventory", headers=auth_headers, json=inventory_data
        )

        assert response.status_code == 200
        data = response.json()
        assert data["gtin"] == "1234567890123"
        assert data["location"] == "WAREHOUSE_A"
        assert data["quantity"] == 100

    def test_set_inventory_existing_success(self, client, auth_headers, db):
        """Test updating existing inventory successfully"""
        # Create initial inventory
        inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
        db.add(inventory)
        db.commit()

        inventory_data = {
            "gtin": "1234567890123",
            "location": "WAREHOUSE_A",
            "quantity": 75,
        }

        response = client.post(
            "/api/v1/inventory", headers=auth_headers, json=inventory_data
        )

        assert response.status_code == 200
        data = response.json()
        assert data["quantity"] == 75  # Should be replaced, not added

    def test_set_inventory_invalid_gtin_validation_error(self, client, auth_headers):
        """Test setting inventory with invalid GTIN returns ValidationException"""
        inventory_data = {
            "gtin": "",  # Empty GTIN
            "location": "WAREHOUSE_A",
            "quantity": 100,
        }

        response = client.post(
            "/api/v1/inventory", headers=auth_headers, json=inventory_data
        )

        assert response.status_code == 422
        data = response.json()
        assert data["error_code"] == "INVENTORY_VALIDATION_FAILED"
        assert data["status_code"] == 422
        assert "GTIN is required" in data["message"]

    def test_set_inventory_invalid_quantity_validation_error(
        self, client, auth_headers
    ):
        """Test setting inventory with invalid quantity returns InvalidQuantityException"""
        inventory_data = {
            "gtin": "1234567890123",
            "location": "WAREHOUSE_A",
            "quantity": -10,  # Negative quantity
        }

        response = client.post(
            "/api/v1/inventory", headers=auth_headers, json=inventory_data
        )

        assert response.status_code in [400, 422]
        data = response.json()
        assert data["error_code"] in ["INVALID_QUANTITY", "VALIDATION_ERROR"]
        if data["error_code"] == "INVALID_QUANTITY":
            assert data["status_code"] == 422
            assert data["details"]["field"] == "quantity"

    def test_add_inventory_success(self, client, auth_headers, db):
        """Test adding to existing inventory successfully"""
        # Create initial inventory
        inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
        db.add(inventory)
        db.commit()

        inventory_data = {
            "gtin": "1234567890123",
            "location": "WAREHOUSE_A",
            "quantity": 25,
        }

        response = client.post(
            "/api/v1/inventory/add", headers=auth_headers, json=inventory_data
        )

        assert response.status_code == 200
        data = response.json()
        assert data["quantity"] == 75  # 50 + 25

    def test_add_inventory_creates_new_if_not_exists(self, client, auth_headers):
        """Test adding to nonexistent inventory creates new inventory entry"""
        inventory_data = {
            "gtin": "9999999999999",
            "location": "WAREHOUSE_A",
            "quantity": 25,
        }

        response = client.post(
            "/api/v1/inventory/add", headers=auth_headers, json=inventory_data
        )

        # Your service creates new inventory if it doesn't exist (upsert behavior)
        assert response.status_code == 200
        data = response.json()
        assert data["gtin"] == "9999999999999"
        assert data["location"] == "WAREHOUSE_A"
        assert data["quantity"] == 25

    def test_remove_inventory_success(self, client, auth_headers, db):
        """Test removing from existing inventory successfully"""
        # Create initial inventory
        inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
        db.add(inventory)
        db.commit()

        inventory_data = {
            "gtin": "1234567890123",
            "location": "WAREHOUSE_A",
            "quantity": 15,
        }

        response = client.post(
            "/api/v1/inventory/remove", headers=auth_headers, json=inventory_data
        )

        assert response.status_code == 200
        data = response.json()
        assert data["quantity"] == 35  # 50 - 15

    def test_remove_inventory_insufficient_returns_business_logic_error(
        self, client, auth_headers, db
    ):
        """Test removing more inventory than available returns InsufficientInventoryException"""
        # Create initial inventory
        inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=10)
        db.add(inventory)
        db.commit()

        inventory_data = {
            "gtin": "1234567890123",
            "location": "WAREHOUSE_A",
            "quantity": 20,
        }

        response = client.post(
            "/api/v1/inventory/remove", headers=auth_headers, json=inventory_data
        )

        assert response.status_code == 400
        data = response.json()
        assert data["error_code"] == "INSUFFICIENT_INVENTORY"
        assert data["status_code"] == 400
        assert "Insufficient inventory" in data["message"]
        assert data["details"]["gtin"] == "1234567890123"
        assert data["details"]["location"] == "WAREHOUSE_A"
        assert data["details"]["requested_quantity"] == 20
        assert data["details"]["available_quantity"] == 10

    def test_remove_inventory_not_found(self, client, auth_headers):
        """Test removing from nonexistent inventory returns InventoryNotFoundException"""
        inventory_data = {
            "gtin": "9999999999999",
            "location": "WAREHOUSE_A",
            "quantity": 15,
        }

        response = client.post(
            "/api/v1/inventory/remove", headers=auth_headers, json=inventory_data
        )

        # This should actually return 404 since you can't remove from non-existent inventory
        # If it returns 200, your service might create inventory with negative quantity
        assert response.status_code == 404
        data = response.json()
        assert data["error_code"] == "INVENTORY_NOT_FOUND"
        assert data["status_code"] == 404

    def test_negative_inventory_not_allowed_business_logic_error(
        self, client, auth_headers, db
    ):
        """Test operations resulting in negative inventory returns NegativeInventoryException"""
        # Create initial inventory
        inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=5)
        db.add(inventory)
        db.commit()

        inventory_data = {
            "gtin": "1234567890123",
            "location": "WAREHOUSE_A",
            "quantity": 10,
        }

        response = client.post(
            "/api/v1/inventory/remove", headers=auth_headers, json=inventory_data
        )

        assert response.status_code == 400
        data = response.json()
        # This might be caught as INSUFFICIENT_INVENTORY or NEGATIVE_INVENTORY_NOT_ALLOWED
        assert data["error_code"] in [
            "INSUFFICIENT_INVENTORY",
            "NEGATIVE_INVENTORY_NOT_ALLOWED",
        ]
        assert data["status_code"] == 400

    def test_get_inventory_by_gtin_success(self, client, auth_headers, db):
        """Test getting inventory summary for GTIN successfully"""
        # Create inventory in multiple locations
        inventory1 = Inventory(
            gtin="1234567890123", location="WAREHOUSE_A", quantity=50
        )
        inventory2 = Inventory(
            gtin="1234567890123", location="WAREHOUSE_B", quantity=25
        )
        db.add_all([inventory1, inventory2])
        db.commit()

        response = client.get("/api/v1/inventory/1234567890123", headers=auth_headers)

        assert response.status_code == 200
        data = response.json()
        assert data["gtin"] == "1234567890123"
        assert data["total_quantity"] == 75
        assert len(data["locations"]) == 2

    def test_get_inventory_by_gtin_not_found(self, client, auth_headers):
        """Test getting inventory for nonexistent GTIN returns InventoryNotFoundException"""
        response = client.get("/api/v1/inventory/9999999999999", headers=auth_headers)

        assert response.status_code == 404
        data = response.json()
        assert data["error_code"] == "INVENTORY_NOT_FOUND"
        assert data["status_code"] == 404
        assert "9999999999999" in data["message"]
        assert data["details"]["resource_type"] == "Inventory"
        assert data["details"]["identifier"] == "9999999999999"

    def test_get_total_inventory_success(self, client, auth_headers, db):
        """Test getting total inventory for GTIN successfully"""
        # Create inventory in multiple locations
        inventory1 = Inventory(
            gtin="1234567890123", location="WAREHOUSE_A", quantity=50
        )
        inventory2 = Inventory(
            gtin="1234567890123", location="WAREHOUSE_B", quantity=25
        )
        db.add_all([inventory1, inventory2])
        db.commit()

        response = client.get(
            "/api/v1/inventory/1234567890123/total", headers=auth_headers
        )

        assert response.status_code == 200
        data = response.json()
        assert data["gtin"] == "1234567890123"
        assert data["total_quantity"] == 75
        assert data["locations_count"] == 2

    def test_get_total_inventory_not_found(self, client, auth_headers):
        """Test getting total inventory for nonexistent GTIN returns InventoryNotFoundException"""
        response = client.get(
            "/api/v1/inventory/9999999999999/total", headers=auth_headers
        )

        assert response.status_code == 404
        data = response.json()
        assert data["error_code"] == "INVENTORY_NOT_FOUND"
        assert data["status_code"] == 404

    def test_get_all_inventory_success(self, client, auth_headers, db):
        """Test getting all inventory entries successfully"""
        # Create some inventory entries
        inventory1 = Inventory(
            gtin="1234567890123", location="WAREHOUSE_A", quantity=50
        )
        inventory2 = Inventory(
            gtin="9876543210987", location="WAREHOUSE_B", quantity=25
        )
        db.add_all([inventory1, inventory2])
        db.commit()

        response = client.get("/api/v1/inventory", headers=auth_headers)

        assert response.status_code == 200
        data = response.json()
        assert len(data) >= 2

    def test_get_all_inventory_with_filters(self, client, auth_headers, db):
        """Test getting inventory entries with filtering"""
        # Create inventory entries
        inventory1 = Inventory(
            gtin="1234567890123", location="WAREHOUSE_A", quantity=50
        )
        inventory2 = Inventory(
            gtin="9876543210987", location="WAREHOUSE_B", quantity=25
        )
        db.add_all([inventory1, inventory2])
        db.commit()

        # Filter by location
        response = client.get(
            "/api/v1/inventory?location=WAREHOUSE_A", headers=auth_headers
        )
        assert response.status_code == 200
        data = response.json()
        for inventory in data:
            assert inventory["location"] == "WAREHOUSE_A"

        # Filter by GTIN
        response = client.get(
            "/api/v1/inventory?gtin=1234567890123", headers=auth_headers
        )
        assert response.status_code == 200
        data = response.json()
        for inventory in data:
            assert inventory["gtin"] == "1234567890123"

    def test_update_inventory_success(self, client, auth_headers, db):
        """Test updating inventory quantity successfully"""
        # Create initial inventory
        inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
        db.add(inventory)
        db.commit()
        db.refresh(inventory)

        update_data = {"quantity": 75}

        response = client.put(
            f"/api/v1/inventory/{inventory.id}",
            headers=auth_headers,
            json=update_data,
        )

        assert response.status_code == 200
        data = response.json()
        assert data["quantity"] == 75

    def test_update_inventory_not_found(self, client, auth_headers):
        """Test updating nonexistent inventory returns InventoryNotFoundException"""
        update_data = {"quantity": 75}

        response = client.put(
            "/api/v1/inventory/99999",
            headers=auth_headers,
            json=update_data,
        )

        assert response.status_code == 404
        data = response.json()
        assert data["error_code"] == "INVENTORY_NOT_FOUND"
        assert data["status_code"] == 404

    def test_update_inventory_invalid_quantity(self, client, auth_headers, db):
        """Test updating inventory with invalid quantity returns ValidationException"""
        # Create initial inventory
        inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
        db.add(inventory)
        db.commit()
        db.refresh(inventory)

        update_data = {"quantity": -10}  # Negative quantity

        response = client.put(
            f"/api/v1/inventory/{inventory.id}",
            headers=auth_headers,
            json=update_data,
        )

        assert response.status_code == 422
        data = response.json()
        assert data["error_code"] == "INVALID_QUANTITY"
        assert data["status_code"] == 422
        assert "Quantity cannot be negative" in data["message"]
        assert data["details"]["field"] == "quantity"

    def test_delete_inventory_success(self, client, auth_headers, db):
        """Test deleting inventory entry successfully"""
        # Create initial inventory
        inventory = Inventory(gtin="1234567890123", location="WAREHOUSE_A", quantity=50)
        db.add(inventory)
        db.commit()
        db.refresh(inventory)

        response = client.delete(
            f"/api/v1/inventory/{inventory.id}",
            headers=auth_headers,
        )

        assert response.status_code == 200
        assert "deleted successfully" in response.json()["message"]

    def test_delete_inventory_not_found(self, client, auth_headers):
        """Test deleting nonexistent inventory returns InventoryNotFoundException"""
        response = client.delete(
            "/api/v1/inventory/99999",
            headers=auth_headers,
        )

        assert response.status_code == 404
        data = response.json()
        assert data["error_code"] == "INVENTORY_NOT_FOUND"
        assert data["status_code"] == 404

    def test_location_not_found_error(self, client, auth_headers):
        """Test operations on nonexistent location returns LocationNotFoundException (if implemented)"""
        inventory_data = {
            "gtin": "1234567890123",
            "location": "NONEXISTENT_LOCATION",
            "quantity": 100,
        }

        response = client.post(
            "/api/v1/inventory", headers=auth_headers, json=inventory_data
        )

        # This depends on whether your service validates locations
        if response.status_code == 404:
            data = response.json()
            assert data["error_code"] == "LOCATION_NOT_FOUND"
            assert data["status_code"] == 404

    def test_invalid_inventory_operation_error(self, client, auth_headers):
        """Test invalid inventory operations return InvalidInventoryOperationException"""
        # This would test business logic validation
        # The exact scenario depends on your business rules
        # Implementation depends on specific business rules

    def test_get_inventory_without_auth_returns_invalid_token(self, client):
        """Test that inventory endpoints require authentication returns InvalidTokenException"""
        response = client.get("/api/v1/inventory")

        assert response.status_code == 401
        data = response.json()
        assert data["error_code"] == "INVALID_TOKEN"
        assert data["status_code"] == 401

    def test_pagination_validation_errors(self, client, auth_headers):
        """Test pagination parameter validation"""
        # Test negative skip
        response = client.get("/api/v1/inventory?skip=-1", headers=auth_headers)
        assert response.status_code == 422
        data = response.json()
        assert data["error_code"] == "VALIDATION_ERROR"

        # Test zero limit
        response = client.get("/api/v1/inventory?limit=0", headers=auth_headers)
        assert response.status_code == 422
        data = response.json()
        assert data["error_code"] == "VALIDATION_ERROR"

        # Test excessive limit
        response = client.get("/api/v1/inventory?limit=10000", headers=auth_headers)
        assert response.status_code == 422
        data = response.json()
        assert data["error_code"] == "VALIDATION_ERROR"

    def test_exception_structure_consistency(self, client, auth_headers):
        """Test that all inventory exceptions follow the consistent WizamartException structure"""
        # Test with a known error case
        response = client.get("/api/v1/inventory/9999999999999", headers=auth_headers)

        assert response.status_code == 404
        data = response.json()

        # Verify exception structure matches WizamartException.to_dict()
        required_fields = ["error_code", "message", "status_code"]
        for field in required_fields:
            assert field in data, f"Missing required field: {field}"

        assert isinstance(data["error_code"], str)
        assert isinstance(data["message"], str)
        assert isinstance(data["status_code"], int)

        # Details field should be present for domain-specific exceptions
        if "details" in data:
            assert isinstance(data["details"], dict)
