from __future__ import annotations

from dataclasses import dataclass
from typing import Optional
from fastapi import HTTPException, Request, status
from analogai.foundation.modules.user.user import User
from analogai.foundation.modules.agent.agent import Agent
from analogai.foundation.modules.agent.agent_service import AgentService
from analogai.foundation.extensions.authentication.interfaces.authentication_factory_interface import (
	AuthenticationFactoryInterface,
)
from analogai.foundation.extensions.authorization.interfaces.authorization_factory_interface import (
	AuthorizationDependency,
	AuthorizationFactoryInterface,
)
from analogai.foundation.extensions.authorization.core import AuthorizationOperation, AuthorizationService
from analogai.foundation.extensions.authorization.models import AuthorizationContext
from analogai.foundation.common.logging.app_logger import app_logger
from analogai.foundation.extensions.authorization.authority_policy import (
	AuthorityPolicy,
	DefaultAuthorityPolicy,
)
from analogai.foundation.extensions.authorization.error_mappers import AuthorizationHttpErrorMapper
from analogai.foundation.extensions.authentication.authentication_factory import AuthenticationFactory
from analogai.foundation.setup.factories.repository_selector import create_repository_strategy


@dataclass(frozen=True)
class ApiAuthorizationInput:
	request: Request
	user: User
	agent_id: str
	operation_override: AuthorizationOperation
	authorization_service: AuthorizationService
	agent_service: AgentService
	authority_policy: AuthorityPolicy
	error_mapper: AuthorizationHttpErrorMapper


class ApiAuthorizationContextResolver:
	auth_type: str

	def resolve(self, data: ApiAuthorizationInput) -> AuthorizationContext:
		raise NotImplementedError

	def _resolve_agent_id(self, data: ApiAuthorizationInput) -> str:
		token_agent_id = getattr(data.request.state, "api_agent_id", None)
		if token_agent_id and data.agent_id and token_agent_id != data.agent_id:
			raise HTTPException(
				status_code=status.HTTP_403_FORBIDDEN,
				detail="Agent mismatch for API token",
			)
		resolved_agent_id = token_agent_id or data.agent_id
		if not resolved_agent_id:
			raise HTTPException(
				status_code=status.HTTP_400_BAD_REQUEST,
				detail="Missing agent_id",
			)
		return resolved_agent_id


class UserApiAuthorizationResolver(ApiAuthorizationContextResolver):
	auth_type = "user"

	def resolve(self, data: ApiAuthorizationInput) -> AuthorizationContext:
		resolved_agent_id = self._resolve_agent_id(data)
		try:
			agent = data.authorization_service.find_and_authorize_agent(
				data.user,
				resolved_agent_id,
				data.operation_override,
			)
		except ValueError as error:
			raise data.error_mapper.map(error) from error

		authority = data.authority_policy.resolve_api(self.auth_type)
		app_logger.info(
			"API authorization context resolved: user_id=%s agent_id=%s user_email=%s authority=%s auth_type=%s",
			data.user.id if data.user else None,
			agent.id,
			data.user.email if data.user else None,
			authority,
			self.auth_type,
		)
		return AuthorizationContext(
			user=data.user,
			agent=agent,
			authority=authority,
			operation=data.operation_override,
		)


class SystemApiAuthorizationResolver(ApiAuthorizationContextResolver):
	auth_type = "system"

	def resolve(self, data: ApiAuthorizationInput) -> AuthorizationContext:
		resolved_agent_id = self._resolve_agent_id(data)
		agent = data.agent_service.find(resolved_agent_id)
		if not agent:
			raise HTTPException(
				status_code=status.HTTP_404_NOT_FOUND,
				detail="Agent not found",
			)

		authority = data.authority_policy.resolve_api(self.auth_type)
		app_logger.info(
			"API authorization context resolved: user_id=%s agent_id=%s user_email=%s authority=%s auth_type=%s",
			data.user.id if data.user else None,
			agent.id,
			data.user.email if data.user else None,
			authority,
			self.auth_type,
		)
		return AuthorizationContext(
			user=data.user,
			agent=agent,
			authority=authority,
			operation=data.operation_override,
		)


class GuestApiAuthorizationResolver(SystemApiAuthorizationResolver):
	auth_type = "guest"


class AuthorizationFactory(AuthorizationFactoryInterface):
	def __init__(self):
		self._authorization_service: Optional[AuthorizationService] = None
		self._authentication_factory: Optional[AuthenticationFactoryInterface] = None
		self._agent_service: Optional[AgentService] = None
		self._authority_policy: Optional[AuthorityPolicy] = None
		self._error_mapper: Optional[AuthorizationHttpErrorMapper] = None
		self._api_resolvers: Optional[dict[str, ApiAuthorizationContextResolver]] = None

	def create_authorization_dependency(
		self,
		operation: AuthorizationOperation = AuthorizationOperation.CHAT,
	) -> AuthorizationDependency:
		authentication_dependency = self._get_authentication_factory().create_authentication_dependency()
		authorization_service = self._get_authorization_service()
		agent_service = self._get_agent_service()

		async def dependency(
			request: Request,
			agent_id: str,
			operation_override: AuthorizationOperation = operation,
		) -> AuthorizationContext:
			user = await authentication_dependency(request)

			if getattr(request.state, "api_auth_type", None) in {"user", "system", "guest"}:
				return self._resolve_api_authorization_context(
					request=request,
					user=user,
					agent_id=agent_id,
					operation_override=operation_override,
					authorization_service=authorization_service,
					agent_service=agent_service,
				)

			try:
				agent = authorization_service.find_and_authorize_agent(
					user,
					agent_id,
					operation_override,
				)
			except ValueError as error:
				raise self._get_error_mapper().map(error) from error

			authority = self._extract_authority(user, agent)
			app_logger.info(
				"Authorization context resolved: user_id=%s agent_id=%s user_email=%s authority=%s",
				user.id if user else None,
				agent.id,
				user.email if user else None,
				authority,
			)
			return AuthorizationContext(
				user=user,
				agent=agent,
				authority=authority,
				operation=operation_override,
			)

		return dependency

	def create_api_authorization_dependency(
		self,
		operation: AuthorizationOperation = AuthorizationOperation.CHAT,
	) -> AuthorizationDependency:
		authentication_dependency = (
			self._get_authentication_factory().create_api_token_authentication_dependency()
		)
		authorization_service = self._get_authorization_service()
		agent_service = self._get_agent_service()

		async def dependency(
			request: Request,
			agent_id: str,
			operation_override: AuthorizationOperation = operation,
		) -> AuthorizationContext:
			user = await authentication_dependency(request)
			return self._resolve_api_authorization_context(
				request=request,
				user=user,
				agent_id=agent_id,
				operation_override=operation_override,
				authorization_service=authorization_service,
				agent_service=agent_service,
			)

		return dependency

	def _get_authorization_service(self) -> AuthorizationService:
		if not self._authorization_service:
			self._authorization_service = AuthorizationService(self._get_agent_service())
		return self._authorization_service

	def _get_agent_service(self) -> AgentService:
		if not self._agent_service:
			from analogai.foundation.extensions.authorization import authorization_factory as authz_factory

			repository = authz_factory.create_repository_strategy().get_agent_repository()
			self._agent_service = AgentService(repository)
		return self._agent_service

	def _get_authentication_factory(self) -> AuthenticationFactoryInterface:
		if not self._authentication_factory:
			from analogai.foundation.extensions.authorization import authorization_factory as authz_factory

			self._authentication_factory = authz_factory.AuthenticationFactory()
		return self._authentication_factory

	def _get_authority_policy(self) -> AuthorityPolicy:
		if not self._authority_policy:
			self._authority_policy = DefaultAuthorityPolicy()
		return self._authority_policy

	def _get_error_mapper(self) -> AuthorizationHttpErrorMapper:
		if not self._error_mapper:
			self._error_mapper = AuthorizationHttpErrorMapper.default()
		return self._error_mapper

	def _get_api_resolvers(self) -> dict[str, ApiAuthorizationContextResolver]:
		if not self._api_resolvers:
			resolvers = (
				UserApiAuthorizationResolver(),
				SystemApiAuthorizationResolver(),
				GuestApiAuthorizationResolver(),
			)
			self._api_resolvers = {resolver.auth_type: resolver for resolver in resolvers}
		return self._api_resolvers

	def _extract_authority(self, user: User, agent: Agent) -> float:
		return self._get_authority_policy().resolve_role(user, agent)

	def _resolve_api_authorization_context(
		self,
		request: Request,
		user: User,
		agent_id: str,
		operation_override: AuthorizationOperation,
		authorization_service: AuthorizationService,
		agent_service: AgentService,
	) -> AuthorizationContext:
		auth_type = getattr(request.state, "api_auth_type", "system")
		resolver = self._get_api_resolvers().get(auth_type)
		if not resolver:
			raise HTTPException(
				status_code=status.HTTP_401_UNAUTHORIZED,
				detail="Invalid auth_type; expected 'user', 'system', or 'guest'",
			)

		data = ApiAuthorizationInput(
			request=request,
			user=user,
			agent_id=agent_id,
			operation_override=operation_override,
			authorization_service=authorization_service,
			agent_service=agent_service,
			authority_policy=self._get_authority_policy(),
			error_mapper=self._get_error_mapper(),
		)
		return resolver.resolve(data)


__all__ = ["AuthorizationFactory", "AuthenticationFactory", "create_repository_strategy"]

