from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
from typing import Callable
from analogai.foundation.modules.user.user import User
from analogai.foundation.modules.agent.agent import Agent
from analogai.foundation.extensions.authorization.core.services.authorization_service import AuthorizationService
from analogai.foundation.extensions.authorization.core.enums.operation import AuthorizationOperation
from analogai.foundation.common.logging.app_logger import app_logger

class AuthorizationErrorReason(Enum):
    NOT_FOUND = "not_found"
    ACCESS_DENIED = "access_denied"
    INVALID_REQUEST = "invalid_request"
    INTERNAL_ERROR = "internal_error"

class AuthorizationGuardError(Exception):
    def __init__(self, message: str, reason: AuthorizationErrorReason):
        super().__init__(message)
        self.reason = reason

@dataclass(frozen=True)
class ErrorReasonRule:
    predicate: Callable[[str], bool]
    reason: AuthorizationErrorReason

class AuthorizationErrorReasonResolver:
    def __init__(self, rules: list[ErrorReasonRule] | None = None):
        self._rules = rules or [
            ErrorReasonRule(
                predicate=lambda message: "not found" in message,
                reason=AuthorizationErrorReason.NOT_FOUND,
            ),
        ]
        self._default_reason = AuthorizationErrorReason.ACCESS_DENIED

    def resolve(self, message: str) -> AuthorizationErrorReason:
        lowered = message.lower()
        for rule in self._rules:
            if rule.predicate(lowered):
                return rule.reason
        return self._default_reason

class AuthorizationGuard(ABC):

    def __init__(self, authorization_service: AuthorizationService):
        self.authorization_service = authorization_service
        self._error_reason_resolver = AuthorizationErrorReasonResolver()
    
    def authorize_agent(
        self,
        agent_id: str,
        current_user: User,
        operation: AuthorizationOperation = AuthorizationOperation.CHAT
    ) -> Agent:

        if not current_user:
            app_logger.error("Authorization failed: No authenticated user")
            self._raise_error("User not authenticated", AuthorizationErrorReason.INVALID_REQUEST)

        try:
            agent = self.authorization_service.find_and_authorize_agent(
                current_user,
                agent_id,
                operation,
            )
            if operation == AuthorizationOperation.EDIT and not self._has_edit_access(current_user, agent):
                message = f"User {current_user.id} does not have edit access to agent {agent_id}"
                app_logger.error(f"Authorization failed: {message}")
                self._raise_error(message, AuthorizationErrorReason.ACCESS_DENIED)

            app_logger.info(f"Authorized user {current_user.id} for agent {agent.id}")
            return agent

        except ValueError as error:
            message = str(error)
            app_logger.error(f"Authorization failed: {message}")
            self._raise_error(message, self._error_reason_resolver.resolve(message))

        except Exception as error:
            message = str(error)
            app_logger.error(f"Authorization failed with unexpected error: {message}")
            self._raise_error("Authorization failed", AuthorizationErrorReason.INTERNAL_ERROR)

    def _has_edit_access(self, current_user: User, agent: Agent) -> bool:
        return any(role.user and role.user.id == current_user.id for role in agent.roles or [])

    def _raise_error(self, message: str, reason: AuthorizationErrorReason) -> None:
        raise AuthorizationGuardError(message, reason)

    @abstractmethod
    async def get_accessible_agent(
        self,
        agent_id: str,
        current_user: User = None,
        operation: AuthorizationOperation = AuthorizationOperation.CHAT,
        **context
    ) -> Agent:

        pass

