from __future__ import annotations

import json
from typing import Optional
from fastapi import HTTPException, Request, status
from analogai.foundation.modules.user.user import User
from analogai.foundation.modules.user.user_service import UserService
from analogai.foundation.extensions.authentication.interfaces.authentication_factory_interface import (
	AuthenticationDependency,
	AuthenticationFactoryInterface,
	TokenExtractionDependency,
	ApiTokenAuthenticationDependency,
)
from analogai.foundation.extensions.authentication.core import IJWTService
from analogai.foundation.extensions.authentication.models import TokenPayload
from analogai.foundation.common.logging.app_logger import app_logger
from analogai.foundation.extensions.authentication.api_token.api_token_authentication_service import (
	ApiTokenAuthenticationService,
)
from analogai.foundation.extensions.authentication.api_token.auth_type_handlers import (
	ApiAuthTypeHandler,
	GuestApiAuthHandler,
	SystemApiAuthHandler,
	UserApiAuthHandler,
)
from analogai.foundation.extensions.authentication.providers.auth0 import Auth0JWTService
from analogai.foundation.setup.factories.repository_selector import create_repository_strategy


class AuthenticationFactory(AuthenticationFactoryInterface):
	def __init__(self):
		self._jwt_service: Optional[IJWTService] = None
		self._user_service: Optional[UserService] = None
		self._api_token_service: Optional[ApiTokenAuthenticationService] = None
		self._api_auth_handlers: Optional[dict[str, ApiAuthTypeHandler]] = None

	def create_authentication_dependency(self) -> AuthenticationDependency:
		async def dependency(request: Request) -> User:
			token = self._extract_token(request)
			try:
				payload = self._get_jwt_service().decode_payload(token)
			except ValueError as error:
				raise HTTPException(
					status_code=status.HTTP_401_UNAUTHORIZED,
					detail=str(error),
				) from error

			if payload.get("email"):
				try:
					user = self._get_jwt_service().authenticate_user(token)
				except ValueError as error:
					detail = str(error)
					if "user not found" in detail.lower():
						detail = "User not found for token"
					raise HTTPException(
						status_code=status.HTTP_401_UNAUTHORIZED,
						detail=detail,
					) from error
			else:
				user = await self._authenticate_api_token(request, token)

			if not user:
				raise HTTPException(
					status_code=status.HTTP_401_UNAUTHORIZED,
					detail="User not found for token",
				)

			return user

		return dependency

	def create_token_extraction_dependency(self) -> TokenExtractionDependency:
		async def dependency(request: Request) -> TokenPayload:
			token = self._extract_token(request)
			try:
				return self._get_jwt_service().extract_payload(token)
			except ValueError as error:
				raise HTTPException(
					status_code=status.HTTP_401_UNAUTHORIZED,
					detail=str(error),
				) from error

		return dependency

	def create_api_token_authentication_dependency(self) -> ApiTokenAuthenticationDependency:
		async def dependency(request: Request) -> User:
			token = self._extract_token(request)
			return await self._authenticate_api_token(request, token)

		return dependency

	async def _authenticate_api_token(self, request: Request, token: str) -> User:
		try:
			api_payload = self._get_api_token_service().extract_payload(token)
		except ValueError as error:
			raise HTTPException(
				status_code=status.HTTP_401_UNAUTHORIZED,
				detail=str(error),
			) from error

		auth_payload = await self._extract_api_auth_payload(request)
		auth_type = str(auth_payload.get("auth_type", "system")).lower()

		handler = self._get_api_auth_handlers().get(auth_type)
		if not handler:
			raise HTTPException(
				status_code=status.HTTP_401_UNAUTHORIZED,
				detail="Invalid auth_type; expected 'user', 'system', or 'guest'",
			)

		try:
			user = handler.authenticate(
				auth_payload=auth_payload,
				api_payload=api_payload,
				user_service=self._get_user_service(),
			)
		except ValueError as error:
			raise HTTPException(
				status_code=status.HTTP_401_UNAUTHORIZED,
				detail=str(error),
			) from error

		request.state.api_auth_type = auth_type
		request.state.api_agent_id = api_payload.agent_id
		request.state.api_user_email = user.email if user else None
		return user

	def _get_jwt_service(self) -> IJWTService:
		if not self._jwt_service:
			from analogai.foundation.extensions.authentication import authentication_factory as auth_factory

			self._jwt_service = auth_factory.Auth0JWTService(self._get_user_service())
		return self._jwt_service

	def _get_user_service(self) -> UserService:
		if not self._user_service:
			from analogai.foundation.extensions.authentication import authentication_factory as auth_factory

			repository = auth_factory.create_repository_strategy().get_user_repository()
			self._user_service = UserService(repository)
		return self._user_service

	def _get_api_token_service(self) -> ApiTokenAuthenticationService:
		if not self._api_token_service:
			self._api_token_service = ApiTokenAuthenticationService()
		return self._api_token_service

	def _get_api_auth_handlers(self) -> dict[str, ApiAuthTypeHandler]:
		if not self._api_auth_handlers:
			handlers = (
				UserApiAuthHandler(),
				SystemApiAuthHandler(),
				GuestApiAuthHandler(),
			)
			self._api_auth_handlers = {handler.auth_type: handler for handler in handlers}
		return self._api_auth_handlers

	async def _extract_api_auth_payload(self, request: Request) -> dict:
		body = await request.body()
		if not body:
			return {}
		payload = json.loads(body)
		if isinstance(payload, dict):
			return payload
		return {}

	def _extract_token(self, request: Request) -> str:
		authorization = request.headers.get("Authorization", "")
		if not authorization.startswith("Bearer "):
			app_logger.debug(
				"Invalid Authorization header format: '%s'",
				authorization,
			)
			raise HTTPException(
				status_code=status.HTTP_401_UNAUTHORIZED,
				detail="Invalid authentication token format",
			)
		return authorization.replace("Bearer ", "", 1)


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