from typing import Optional, Type

import strawberry
from pydantic import BaseModel
from pydantic.alias_generators import to_pascal, to_snake
from strawberry.exceptions import GraphQLError
from strawberry.types import Info

from ..exceptions import DocumentNotFoundException
from ..permissions import PermissionDenied, PermissionManager
from ..protocol import TInput, TOutput
from ..service import LazyQL


class MutationBuilder:
    """Builds GraphQL Mutation types with CRUD resolvers."""

    def build(
        self,
        name: str,
        collection_name: Optional[str],
        model: Type[BaseModel],
        input_type: Type[TInput],
        output_type: Type[TOutput],
        permission_manager: PermissionManager,
    ):
        """
        Create Mutation type with CRUD operations and permission checks.

        Generated mutations:
        - create: Insert new document with audit logging
        - update: Modify existing document with change tracking
        - delete: Remove document with audit trail
        """
        fields = {}
        if collection_name is None:
            collection_name = model.__name__.lower() + "s"

        # CREATE
        async def resolve_create(info: Info, input: TInput) -> TOutput:
            permission_manager.ensure_permission("create", info)

            db = info.context["db"]
            user = info.context["user"]
            service = LazyQL(db, collection_name, model)

            data = input.to_pydantic()
            res = await service.create(data, user)
            return output_type.from_pydantic(res)

        resolve_create.__annotations__["input"] = input_type
        resolve_create.__annotations__["return"] = output_type

        fields[f"create_{to_snake(name)}"] = strawberry.mutation(
            resolver=resolve_create, name=f"create{to_pascal(name)}"
        )

        # UPDATE
        async def resolve_update(
            info: Info, id: str, input: TInput
        ) -> TOutput:
            permission_manager.ensure_permission("update", info)

            db = info.context["db"]
            user = info.context["user"]
            service = LazyQL(db, collection_name, model)
            res = await service.update(id, input.to_pydantic(), user)
            return output_type.from_pydantic(res)

        resolve_update.__annotations__["input"] = input_type
        resolve_update.__annotations__["return"] = output_type

        fields[f"update_{to_snake(name)}"] = strawberry.mutation(
            resolver=resolve_update, name=f"update{to_pascal(name)}"
        )

        # DELETE
        async def resolve_delete(info: Info, id: str) -> bool:
            """Delete document and return success status."""
            permission_manager.ensure_permission("delete", info)

            db = info.context["db"]
            user = info.context["user"]
            service = LazyQL(db, collection_name, model)
            try:
                await service.delete(id, user)
                return True
            except DocumentNotFoundException:
                raise GraphQLError(f"Document with id {id} not found.")
            except PermissionDenied as e:
                raise GraphQLError(str(e))
            except Exception as e:
                raise GraphQLError(
                    f"Error deleting document with id {id}: {e}"
                )

        fields[f"delete_{to_snake(name)}"] = strawberry.mutation(
            resolver=resolve_delete, name=f"delete{to_pascal(name)}"
        )

        # HARD DELETE
        async def resolve_hard_delete(info: Info, id: str) -> bool:
            """Permanently delete document and return success status."""
            permission_manager.ensure_permission("delete", info)

            db = info.context["db"]
            user = info.context["user"]
            service = LazyQL(db, collection_name, model)
            try:
                await service.hard_delete(id, user)
                return True
            except DocumentNotFoundException:
                raise GraphQLError(f"Document with id {id} not found.")
            except PermissionDenied as e:
                raise GraphQLError(str(e))
            except Exception as e:
                raise GraphQLError(
                    f"Error hard deleting document with id {id}: {e}"
                )

        fields[f"hard_delete_{to_snake(name)}"] = strawberry.mutation(
            resolver=resolve_hard_delete,
            name=f"hardDelete{to_pascal(name)}",
        )

        # RESTORE
        async def resolve_restore(info: Info, id: str) -> TOutput:
            """Restore a soft-deleted document."""
            permission_manager.ensure_permission("update", info)

            db = info.context["db"]
            user = info.context["user"]
            service = LazyQL(db, collection_name, model)
            res = await service.restore(id, user)
            return output_type.from_pydantic(res)

        resolve_restore.__annotations__["return"] = output_type

        fields[f"restore_{to_snake(name)}"] = strawberry.mutation(
            resolver=resolve_restore, name=f"restore{to_pascal(name)}"
        )

        # Create Mutation class
        _Mutation = type(f"{to_pascal(name)}Mutation", (), fields)
        return strawberry.type(_Mutation)
