"""Extended tests for mutations parser to improve coverage."""

import types
from typing import Any, Optional, Union
from unittest.mock import MagicMock
from uuid import uuid4

import pytest

import fraiseql
from fraiseql.mutations.decorators import failure, success
from fraiseql.mutations.parser import (
    _extract_field_value,
    _find_main_field,
    _instantiate_type,
    _is_error_status,
    _is_matching_type,
    _parse_error,
    _parse_success,
    parse_mutation_result,
)
from fraiseql.mutations.types import MutationResult

pytestmark = pytest.mark.integration


@pytest.mark.unit
@fraiseql.type
class Product:
    """Test product type."""

    id: str
    name: str
    price: float


@fraiseql.type
class Category:
    """Test category type."""

    id: str
    name: str
    products: list[Product]


@fraiseql.type
class NestedObject:
    """Test nested object type."""

    value: str
    nested: dict[str, Any]


@success
class SimpleSuccess:
    """Simple success type."""

    message: str


@success
class ProductSuccess:
    """Product success type."""

    message: str
    product: Product


@success
class ComplexSuccess:
    """Complex success type with multiple fields."""

    message: str
    products: list[Product]
    category: Category
    total_count: int
    metadata: dict[str, Any]


@success
class OptionalFieldsSuccess:
    """Success type with optional fields."""

    message: str
    product: Optional[Product] = None
    count: Optional[int] = None


@failure
class SimpleError:
    """Simple error type."""

    message: str


@failure
class DetailedError:
    """Detailed error type."""

    message: str
    code: str
    details: dict[str, Any]
    related_products: Optional[list[Product]] = None


@failure
class ValidationError:
    """Validation error type."""

    message: str
    code: str
    field_errors: Optional[dict[str, str]] = None
    invalid_values: Optional[list[str]] = None


class TestIsErrorStatus:
    """Test _is_error_status function comprehensively."""

    def test_empty_or_none_status(self) -> None:
        """Test empty or None status values."""
        assert not _is_error_status("")
        assert not _is_error_status(None)

    def test_success_statuses(self) -> None:
        """Test all success status variations."""
        success_statuses = [
            """success"""
            """SUCCESS"""
            """Success"""
            """completed"""
            """COMPLETED"""
            """Completed"""
            """ok"""
            """OK"""
            """Ok"""
            """done"""
            """DONE"""
            """Done"""
        ]
        for status in success_statuses:
            assert not _is_error_status(status)

    def test_error_statuses(self) -> None:
        """Test all error status variations."""
        error_statuses = [
            """error"""
            """ERROR"""
            """Error"""
            """failed"""
            """FAILED"""
            """Failed"""
            """fail"""
            """FAIL"""
            """Fail"""
            """not_found"""
            """NOT_FOUND"""
            """Not_Found"""
            """forbidden"""
            """FORBIDDEN"""
            """Forbidden"""
            """unauthorized"""
            """UNAUTHORIZED"""
            """Unauthorized"""
            """conflict"""
            """CONFLICT"""
            """Conflict"""
            """validation_error"""
            """VALIDATION_ERROR"""
            """Validation_Error"""
            """invalid"""
            """INVALID"""
            """Invalid"""
            """email_exists"""
            """EMAIL_EXISTS"""
            """Email_Exists"""
            """exists"""
            """EXISTS"""
            """Exists"""
            """duplicate"""
            """DUPLICATE"""
            """Duplicate"""
            """timeout"""
            """TIMEOUT"""
            """Timeout"""
        ]
        for status in error_statuses:
            assert _is_error_status(status)

    def test_status_contains_error_keywords(self) -> None:
        """Test status containing error keywords."""
        assert _is_error_status("user_not_found")
        assert _is_error_status("operation_failed")
        assert _is_error_status("email_already_exists")
        assert _is_error_status("request_timeout_error")
        assert _is_error_status("validation_error_occurred")

    def test_status_without_error_keywords(self) -> None:
        """Test status without error keywords."""
        assert not _is_error_status("processing")
        assert not _is_error_status("pending")
        assert not _is_error_status("in_progress")
        assert not _is_error_status("queued")
        assert not _is_error_status("started")


class TestInstantiateType:
    """Test _instantiate_type function comprehensively."""

    def test_none_values(self) -> None:
        """Test handling None values."""
        assert _instantiate_type(str, None) is None
        assert _instantiate_type(int, None) is None
        assert _instantiate_type(Product, None) is None
        assert _instantiate_type(list[Product], None) is None

    def test_primitive_types(self) -> None:
        """Test primitive type instantiation."""
        assert _instantiate_type(str, "test") == "test"
        assert _instantiate_type(int, 42) == 42
        assert _instantiate_type(float, 3.14) == 3.14
        assert _instantiate_type(bool, True) is True
        assert _instantiate_type(bool, False) is False

    def test_primitive_type_conversion(self) -> None:
        """Test primitive type conversion."""
        assert _instantiate_type(str, 123) == "123"
        assert _instantiate_type(int, "42") == 42
        assert _instantiate_type(float, "3.14") == 3.14
        assert _instantiate_type(bool, 1) is True

    def test_optional_types_with_none(self) -> None:
        """Test Optional types with None values."""
        optional_str = Union[str, type(None)]
        assert _instantiate_type(optional_str, None) is None

    def test_optional_types_with_values(self) -> None:
        """Test Optional types with actual values."""
        optional_str = Union[str, type(None)]
        assert _instantiate_type(optional_str, "test") == "test"

        optional_product = Union[Product, type(None)]
        data = {"id": "1", "name": "Test", "price": 10.0}
        result = _instantiate_type(optional_product, data)
        assert isinstance(result, Product)
        assert result.name == "Test"

    def test_new_union_type_syntax(self) -> None:
        """Test new Python 3.10+ union type syntax."""
        # Test with types.UnionType if available
        if hasattr(types, "UnionType"):
            union_type = str | int
            assert _instantiate_type(union_type, "test") == "test"

    def test_list_types(self) -> None:
        """Test list type instantiation."""
        data = [
            {"id": "1", "name": "Product1", "price": 10.0},
            {"id": "2", "name": "Product2", "price": 20.0},
        ]
        result = _instantiate_type(list[Product], data)

        assert isinstance(result, list)
        assert len(result) == 2
        assert all(isinstance(p, Product) for p in result)
        assert result[0].name == "Product1"
        assert result[1].price == 20.0

    def test_list_types_with_non_list_data(self) -> None:
        """Test list type with non-list data."""
        result = _instantiate_type(list[Product], "not_a_list")
        assert result == "not_a_list"

    def test_dict_types(self) -> None:
        """Test dict type instantiation."""
        data = {"key1": "value1", "key2": "value2"}
        result = _instantiate_type(dict, data)
        assert result == data

        result = _instantiate_type(dict[str, str], data)
        assert result == data

    def test_fraiseql_types_direct_construction(self) -> None:
        """Test FraiseQL types with direct construction."""
        data = {"id": "1", "name": "Test Product", "price": 99.99}
        result = _instantiate_type(Product, data)

        assert isinstance(result, Product)
        assert result.id == "1"
        assert result.name == "Test Product"
        assert result.price == 99.99

    def test_fraiseql_types_with_from_dict(self) -> None:
        """Test FraiseQL types with from_dict fallback."""
        # Mock a type with from_dict method but construction fails
        mock_type = MagicMock()
        mock_type.__fraiseql_definition__ = True
        mock_type.side_effect = TypeError("Constructor failed")
        mock_type.from_dict.return_value = "from_dict_result"

        result = _instantiate_type(mock_type, {"data": "test"})
        assert result == "from_dict_result"
        mock_type.from_dict.assert_called_once_with({"data": "test"})

    def test_success_decorated_types(self) -> None:
        """Test success decorated types."""

        @success
        class TestSuccess:
            message: str
            value: int

        data = {"message": "Success", "value": 42}
        result = _instantiate_type(TestSuccess, data)

        assert isinstance(result, TestSuccess)
        assert result.message == "Success"
        assert result.value == 42

    def test_failure_decorated_types(self) -> None:
        """Test failure decorated types."""

        @failure
        class TestFailure:
            message: str
            code: str

        data = {"message": "Error", "code": "E001"}
        result = _instantiate_type(TestFailure, data)

        assert isinstance(result, TestFailure)
        assert result.message == "Error"
        assert result.code == "E001"

    def test_types_with_from_dict_only(self) -> None:
        """Test types that only have from_dict method."""
        mock_type = MagicMock()
        mock_type.from_dict.return_value = "from_dict_only"

        result = _instantiate_type(mock_type, {"data": "test"})
        assert result == "from_dict_only"

    def test_unhandled_types(self) -> None:
        """Test unhandled types return as-is."""
        custom_object = object()
        result = _instantiate_type(str, custom_object)
        # str() converts object to string representation
        assert result == str(custom_object)


class TestExtractFieldValue:
    """Test _extract_field_value function."""

    def test_extract_from_metadata(self) -> None:
        """Test extracting field from metadata."""
        metadata = {"product": {"id": "1", "name": "Test", "price": 10.0}}
        object_data = {"other": "data"}

        result = _extract_field_value("product", Product, object_data, metadata)
        assert isinstance(result, Product)
        assert result.name == "Test"

    def test_extract_from_object_data(self) -> None:
        """Test extracting field from object_data."""
        metadata = {"other": "data"}
        object_data = {"product": {"id": "1", "name": "Test", "price": 10.0}}

        result = _extract_field_value("product", Product, object_data, metadata)
        assert isinstance(result, Product)
        assert result.name == "Test"

    def test_metadata_takes_precedence(self) -> None:
        """Test that metadata takes precedence over object_data."""
        metadata = {"field": "metadata_value"}
        object_data = {"field": "object_data_value"}

        result = _extract_field_value("field", str, object_data, metadata)
        assert result == "metadata_value"

    def test_extract_when_object_data_matches_type(self) -> None:
        """Test extracting when object_data itself matches the type."""
        object_data = {"id": "1", "name": "Test", "price": 10.0}

        result = _extract_field_value("product", Product, object_data, None)
        assert isinstance(result, Product)
        assert result.name == "Test"

    def test_extract_field_not_found(self) -> None:
        """Test extracting non-existent field."""
        metadata = {"other": "data"}
        object_data = {"other": "data"}

        result = _extract_field_value("missing_field", str, object_data, metadata)
        assert result is None

    def test_extract_with_none_inputs(self) -> None:
        """Test extracting with None metadata and object_data."""
        result = _extract_field_value("field", str, None, None)
        assert result is None


class TestIsMatchingType:
    """Test _is_matching_type function."""

    def test_list_type_matching(self) -> None:
        """Test list type matching."""
        assert _is_matching_type(list[Product], [{"id": "1"}])
        assert not _is_matching_type(list[Product], {"id": "1"})
        assert not _is_matching_type(list[Product], "not_a_list")

    def test_complex_type_matching(self) -> None:
        """Test complex type matching with annotations."""
        # Test with matching fields
        data = {"id": "1", "name": "Test"}
        assert _is_matching_type(Product, data)

        # Test with no matching fields
        data = {"other_field": "value"}
        assert not _is_matching_type(Product, data)

    def test_non_dict_data(self) -> None:
        """Test matching with non-dict data."""
        assert not _is_matching_type(Product, "string_data")
        assert not _is_matching_type(Product, 123)
        assert not _is_matching_type(Product, None)

    def test_type_without_annotations(self) -> None:
        """Test matching with type without annotations."""

        class SimpleType:
            pass

        assert not _is_matching_type(SimpleType, {"any": "data"})


class TestFindMainField:
    """Test _find_main_field function."""

    def test_find_with_entity_hint_exact_match(self) -> None:
        """Test finding field with exact entity hint match."""
        annotations = {"message": str, "product": Product, "count": int}
        metadata = {"entity": "product"}

        result = _find_main_field(annotations, metadata)
        assert result == "product"

    def test_find_with_entity_hint_suffix_matching(self) -> None:
        """Test finding field with entity hint suffix matching."""
        annotations = {"message": str, "products": list[Product], "count": int}

        # Test with 's' suffix
        metadata = {"entity": "product"}
        result = _find_main_field(annotations, metadata)
        assert result == "products"

        # Test with '_list' suffix
        annotations = {"message": str, "product_list": list[Product]}
        result = _find_main_field(annotations, metadata)
        assert result == "product_list"

        # Test with '_data' suffix
        annotations = {"message": str, "product_data": Product}
        result = _find_main_field(annotations, metadata)
        assert result == "product_data"

    def test_find_first_non_message_field(self) -> None:
        """Test finding first non-message field."""
        annotations = {"message": str, "product": Product, "category": Category}

        result = _find_main_field(annotations, None)
        assert result == "product"

    def test_find_with_only_message(self) -> None:
        """Test finding when only message field exists."""
        annotations = {"message": str}

        result = _find_main_field(annotations, None)
        assert result is None

    def test_find_with_no_annotations(self) -> None:
        """Test finding with empty annotations."""
        result = _find_main_field({}, None)
        assert result is None

    def test_find_with_entity_hint_no_match(self) -> None:
        """Test finding with entity hint that doesn't match any field."""
        annotations = {"message": str, "product": Product}
        metadata = {"entity": "category"}

        # Should fall back to first non-message field
        result = _find_main_field(annotations, metadata)
        assert result == "product"


class TestParseSuccess:
    """Test _parse_success function."""

    def test_parse_simple_success(self) -> None:
        """Test parsing simple success with message only."""
        mutation_result = MutationResult(
            status="success", message="Operation completed", object_data=None, extra_metadata=None
        )

        result = _parse_success(mutation_result, SimpleSuccess)
        assert isinstance(result, SimpleSuccess)
        assert result.message == "Operation completed"

    def test_parse_success_with_object_data_field_match(self) -> None:
        """Test parsing success with object_data matching a specific field."""
        mutation_result = MutationResult(
            status="success",
            message="Product created",
            object_data={"id": "1", "name": "Test", "price": 10.0},
            extra_metadata={"product": {"id": "1", "name": "Test", "price": 10.0}},
        )

        result = _parse_success(mutation_result, ProductSuccess)
        assert isinstance(result, ProductSuccess)
        assert result.message == "Product created"
        assert isinstance(result.product, Product)
        assert result.product.name == "Test"

    def test_parse_success_with_main_field_detection(self) -> None:
        """Test parsing success using main field detection."""
        mutation_result = MutationResult(
            status="success",
            message="Product created",
            object_data={"id": "1", "name": "Test", "price": 10.0},
            extra_metadata={"entity": "product"},
        )

        result = _parse_success(mutation_result, ProductSuccess)
        assert isinstance(result, ProductSuccess)
        assert result.message == "Product created"
        assert isinstance(result.product, Product)
        assert result.product.name == "Test"

    def test_parse_success_with_multiple_fields(self) -> None:
        """Test parsing success with multiple fields from metadata."""
        mutation_result = MutationResult(
            status="success",
            message="Bulk operation completed",
            object_data=[{"id": "1", "name": "Product1", "price": 10.0}],
            extra_metadata={
                "entity": "products",
                "category": {"id": "cat1", "name": "Category", "products": []},
                "total_count": 5,
                "metadata": {"operation": "bulk_create"},
            },
        )

        result = _parse_success(mutation_result, ComplexSuccess)
        assert isinstance(result, ComplexSuccess)
        assert result.message == "Bulk operation completed"
        assert len(result.products) == 1
        assert isinstance(result.category, Category)
        assert result.total_count == 5
        assert result.metadata["operation"] == "bulk_create"

    def test_parse_success_with_optional_fields(self) -> None:
        """Test parsing success with optional fields."""
        mutation_result = MutationResult(
            status="success",
            message="Partial success",
            object_data=None,
            extra_metadata={"count": 3},
        )

        result = _parse_success(mutation_result, OptionalFieldsSuccess)
        assert isinstance(result, OptionalFieldsSuccess)
        assert result.message == "Partial success"
        assert result.product is None
        assert result.count == 3


class TestParseError:
    """Test _parse_error function."""

    def test_parse_simple_error(self) -> None:
        """Test parsing simple error with message only."""
        mutation_result = MutationResult(
            status="error", message="Something went wrong", object_data=None, extra_metadata=None
        )

        result = _parse_error(mutation_result, SimpleError)
        assert isinstance(result, SimpleError)
        assert result.message == "Something went wrong"

    def test_parse_error_with_code(self) -> None:
        """Test parsing error with status as code."""
        mutation_result = MutationResult(
            status="validation_error",
            message="Invalid input",
            object_data=None,
            extra_metadata={"details": {"error": "validation"}},
        )

        result = _parse_error(mutation_result, DetailedError)
        assert isinstance(result, DetailedError)
        assert result.message == "Invalid input"
        assert result.code == "validation_error"
        assert result.details["error"] == "validation"

    def test_parse_error_with_metadata_fields(self) -> None:
        """Test parsing error with additional fields from metadata."""
        mutation_result = MutationResult(
            status="validation_error",
            message="Validation failed",
            object_data=None,
            extra_metadata={
                "details": {"field": "name", "reason": "required"},
                "related_products": [{"id": "1", "name": "Product", "price": 10.0}],
            },
        )

        result = _parse_error(mutation_result, DetailedError)
        assert isinstance(result, DetailedError)
        assert result.message == "Validation failed"
        assert result.code == "validation_error"
        assert result.details["field"] == "name"
        assert len(result.related_products) == 1
        assert isinstance(result.related_products[0], Product)

    def test_parse_error_with_optional_fields(self) -> None:
        """Test parsing error with optional fields."""
        mutation_result = MutationResult(
            status="validation_error",
            message="Field validation failed",
            object_data=None,
            extra_metadata={
                "field_errors": {"name": "Required", "email": "Invalid format"},
                "invalid_values": ["", "invalid-email"],
            },
        )

        result = _parse_error(mutation_result, ValidationError)
        assert isinstance(result, ValidationError)
        assert result.message == "Field validation failed"
        assert result.code == "validation_error"
        assert result.field_errors["name"] == "Required"
        assert "invalid-email" in result.invalid_values

    def test_parse_error_no_metadata(self) -> None:
        """Test parsing error with no metadata."""
        mutation_result = MutationResult(
            status="error", message="Basic error", object_data=None, extra_metadata={}
        )

        # Use SimpleError since DetailedError requires details field
        result = _parse_error(mutation_result, SimpleError)
        assert isinstance(result, SimpleError)
        assert result.message == "Basic error"


class TestParseMutationResult:
    """Test parse_mutation_result integration."""

    def test_parse_mutation_success_integration(self) -> None:
        """Test complete success parsing integration."""
        result = {
            "id": str(uuid4()),
            "updated_fields": ["name", "price"],
            "status": "success",
            "message": "Product updated successfully",
            "object_data": {"id": "1", "name": "Updated Product", "price": 99.99},
            "extra_metadata": {"entity": "product", "version": 2},
        }

        parsed = parse_mutation_result(result, ProductSuccess, DetailedError)
        assert isinstance(parsed, ProductSuccess)
        assert parsed.message == "Product updated successfully"
        assert parsed.product.name == "Updated Product"

    def test_parse_mutation_error_integration(self) -> None:
        """Test complete error parsing integration."""
        result = {
            "status": "validation_error",
            "message": "Product validation failed",
            "object_data": None,
            "extra_metadata": {"details": {"price": "Must be positive"}, "related_products": []},
        }

        parsed = parse_mutation_result(result, ProductSuccess, DetailedError)
        assert isinstance(parsed, DetailedError)
        assert parsed.message == "Product validation failed"
        assert parsed.code == "validation_error"
        assert parsed.details["price"] == "Must be positive"

    def test_parse_mutation_ambiguous_status(self) -> None:
        """Test parsing with ambiguous status that's not clearly success/error."""
        result = {
            "status": "processing",
            "message": "Operation in progress",
            "object_data": {"id": "1", "name": "Product", "price": 10.0},
        }

        # Should be treated as success since no error keywords
        parsed = parse_mutation_result(result, ProductSuccess, DetailedError)
        assert isinstance(parsed, ProductSuccess)

    def test_parse_mutation_with_mutation_result_object(self) -> None:
        """Test that from_db_row is used correctly."""
        result = {
            "id": str(uuid4()),
            "status": "success",
            "message": "Success",
            "object_data": {"id": "1", "name": "Test", "price": 10.0},
        }

        # This tests the MutationResult.from_db_row path
        parsed = parse_mutation_result(result, ProductSuccess, DetailedError)
        assert isinstance(parsed, ProductSuccess)
        assert parsed.product.name == "Test"


class TestEdgeCases:
    """Test edge cases and error conditions."""

    def test_instantiate_type_with_complex_union(self) -> None:
        """Test instantiate type with complex union types."""
        complex_union = Union[str, int, Product]

        # Should handle string - first non-None type is str
        result = _instantiate_type(complex_union, "test")
        assert result == "test"

        # For dict data with complex union, it tries str first
        data = {"id": "1", "name": "Test", "price": 10.0}
        result = _instantiate_type(complex_union, data)
        # Union handling tries first non-None type (str) so it converts dict to string
        assert isinstance(result, str)

    def test_instantiate_type_with_nested_generics(self) -> None:
        """Test instantiate type with nested generic types."""
        nested_type = list[dict[str, Product]]
        data = [{"product1": {"id": "1", "name": "Test", "price": 10.0}}]

        # Should return as-is for unsupported nested generics
        result = _instantiate_type(nested_type, data)
        assert result == data

    def test_extract_field_value_with_type_mismatch(self) -> None:
        """Test extract field value when type doesn't match data."""
        object_data = {"field": 42}  # Use valid int instead of string

        # Should successfully convert
        result = _extract_field_value("field", int, object_data, None)
        assert result == 42

    def test_parse_success_with_constructor_failure(self) -> None:
        """Test parse success when constructor fails."""
        # Create a mock success class that fails construction
        mock_success_cls = MagicMock()
        mock_success_cls.__annotations__ = {"message": str}
        mock_success_cls.side_effect = TypeError("Constructor failed")

        mutation_result = MutationResult(status="success", message="Test message")

        # Should raise the TypeError
        with pytest.raises(TypeError):
            _parse_success(mutation_result, mock_success_cls)

    def test_parse_error_with_constructor_failure(self) -> None:
        """Test parse error when constructor fails."""
        mock_error_cls = MagicMock()
        mock_error_cls.__annotations__ = {"message": str}
        mock_error_cls.side_effect = TypeError("Constructor failed")

        mutation_result = MutationResult(status="error", message="Test error")

        # Should raise the TypeError
        with pytest.raises(TypeError):
            _parse_error(mutation_result, mock_error_cls)

    def test_instantiate_type_with_empty_list(self) -> None:
        """Test instantiate type with empty list."""
        result = _instantiate_type(list[Product], [])
        assert result == []

    def test_instantiate_type_with_malformed_data(self) -> None:
        """Test instantiate type with malformed data for complex types."""
        # Missing required fields
        incomplete_data = {"id": "1"}  # Missing name and price

        # Should raise TypeError during construction
        with pytest.raises(TypeError):
            _instantiate_type(Product, incomplete_data)

    def test_find_main_field_with_empty_entity(self) -> None:
        """Test find main field with empty entity in metadata."""
        annotations = {"message": str, "product": Product}
        metadata = {"entity": ""}

        # Should fall back to first non-message field
        result = _find_main_field(annotations, metadata)
        assert result == "product"

    def test_is_matching_type_with_partial_field_match(self) -> None:
        """Test is matching type with partial field matches."""
        # Data has some but not all expected fields
        data = {"id": "1", "unknown_field": "value"}

        # Should still match since 'id' is in Product annotations
        assert _is_matching_type(Product, data)

    def test_extract_field_value_with_nested_object_data(self) -> None:
        """Test extract field value with nested object data structure."""
        object_data = {"nested": {"value": "test_value", "nested": {"deep": "data"}}}

        result = _extract_field_value("nested", NestedObject, object_data, None)
        assert isinstance(result, NestedObject)
        assert result.value == "test_value"
        assert result.nested["deep"] == "data"
