"""
FastAPI integration implementation.
"""

from http import HTTPStatus
from logging import Logger
from typing import override
import uuid

from fastapi import Request
from fastapi.applications import FastAPI
from fastapi.encoders import jsonable_encoder
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from temporalio.client import Client

from phederation.actions.workflows import (
    ActivityDeliveryWorkflow,
    DeliveryWorkerTask,
    KeyRotationTask,
    UpdateNodeInfoTask,
)
from phederation.api import API_LATEST, create_api
from phederation.api.base import BaseAPI
from phederation.federation import DeliveryResult
from phederation.federation.middleware import ActivityPubMiddleware
from phederation.federation.server import ActivityPubServer
from phederation.federation.tasks import BackgroundTask, create_lifespan
from phederation.integration.base import BaseIntegration
from phederation.statistics.statistics import Statistics
from phederation.storage.tasks import StorageBackgroundTask
from phederation.utils.exceptions import (
    APIError,
    ActivityPubException,
    DeliveryError,
    IntegrationError,
    catch_exceptions,
)
from phederation.utils.logging import configure_logger
from phederation.utils.settings import PhedSettings
from phederation.utils.version import PHEDERATION_HEADERS, PHEDERATION_VERSION


class FastAPIIntegration(BaseIntegration):
    """FastAPI integration."""

    def __init__(self, settings: PhedSettings):
        super().__init__(settings)
        self.logger: Logger = configure_logger(__name__, self.settings.federation.logging_prefix)

        # Create the server with all components from the settings
        self.server: ActivityPubServer = ActivityPubServer(
            settings=self.settings, delivery_workflow_from_integration=FastAPIIntegration.delivery_workflow
        )
        self.background_tasks: list[BackgroundTask] = self._create_background_tasks()
        self.app: FastAPI = FastAPI(
            title="Phederation API",
            summary="A FastAPI implementation of ActivityPub for server2server and client2server communication.",
            description="The API allows all common routes in the ActivityPub protocol (user inbox/outbox, node info, webfinger, ...), and offers additional possibilities like user deletion/creation, media endpoints, and data integrity proofs.",
            version=PHEDERATION_VERSION,
            lifespan=create_lifespan(logger_prefix=self.settings.federation.logging_prefix, background_tasks=self.background_tasks),
        )
        self.app.add_middleware(
            middleware_class=CORSMiddleware,
            allow_origins=["*"],
            allow_credentials=True,
            allow_methods=["GET", "POST", "PUT", "DELETE"],
            allow_headers=["*"],
        )

        self.apis: dict[str, BaseAPI] = {}

    def _create_background_tasks(self) -> list[BackgroundTask]:
        if self.settings.federation.admin_mode:
            return []  # no background tasks for admin mode

        upgrade_node_info_task = UpdateNodeInfoTask(update_period=self.settings.federation.update_nodeinfo_period)
        delivery_task = DeliveryWorkerTask(update_period=self.settings.federation.delivery_queue_polling_period)
        key_rotation_task = KeyRotationTask(
            enabled=self.settings.security.key_rotation_enabled, update_period=self.settings.security.key_rotation_period
        )
        # worker_task = WorkerTask(
        #     workflows=[UpdateNodeInfoWorkflow, ActivityDeliveryWorkflow, KeyRotationWorkflow],
        #     activities=[
        #         upgrade_node_info_task.update_node_info,
        #         delivery_task.prepare_inboxes_for_delivery,
        #         delivery_task.deliver_activity,
        #         key_rotation_task.rotate_keys,
        #         key_rotation_task.announce_key_rotation
        #     ],
        # )
        return [
            upgrade_node_info_task,
            delivery_task,
            key_rotation_task,
            StorageBackgroundTask(),
        ]

    @override
    async def api(self, api_version: str | None = None) -> BaseAPI:
        if not api_version:
            api_version = API_LATEST

        api_version_major = f"{api_version.split('.')[0]}.X.X"

        if not api_version_major in self.apis:
            api = await create_api(api_version)
            await api.initialize(app=self.app, settings=self.settings, middleware=self.middleware, server=self.server)
            self.logger.info(f"Initialized API version {api.version()}.")

            api_version_major = api.version(major=True, minor=False, patch=False)
            self.apis[api_version_major] = api
        result = self.apis.get(api_version_major)
        if not result:
            raise APIError(f"Could not resolve api with version {api_version}")
        return result

    async def _setup_exception_handlers(self) -> None:

        @self.app.exception_handler(Exception)
        def handle_exception_error(request: Request, error: Exception):  # pyright: ignore[reportUnusedFunction, reportUnusedParameter]
            error_type = str(type(error).__name__)
            status_code = HTTPStatus.INTERNAL_SERVER_ERROR
            message = f"Not activitypub related"
            log_message = message

            if isinstance(error, ActivityPubException):
                ap_error: ActivityPubException = error
                message = ap_error.user_message or ap_error.default_message
                log_message = ap_error.message
                status_code = ap_error.status_code
            self.logger.error(f"FastAPIIntegration Exception ({error_type}; {log_message}). User-facing error: {error}.")

            headers = PHEDERATION_HEADERS
            headers["Host"] = self.settings.domain.netloc
            return JSONResponse(
                status_code=status_code,
                headers=headers,
                content=jsonable_encoder({"status": "error", "status_code": status_code, "type": str(type(error).__name__), "message": message}),
            )

    @staticmethod
    async def delivery_workflow(
        worker_client: Client | None,
        settings: PhedSettings,
    ):
        # TODO: refactoring. This method is just a crux to avoid importing the server in a cycle in Tasks.
        if not worker_client:
            raise DeliveryError("Worker client is not set")
        _ = await worker_client.start_workflow(
            ActivityDeliveryWorkflow.run,
            settings.federation,
            id=f"deliver-activity-{uuid.uuid4()}",
            task_queue=settings.worker.tasks_queue_name,
        )

        return DeliveryResult(status_code=HTTPStatus.CONTINUE)

    @catch_exceptions(IntegrationError, "Failed to initialize integration")
    @override
    async def initialize(self) -> None:
        """Initialize integration."""
        # this initializes all parameters of the ActivityPubServer.
        # It also clears the database, if specified in settings (e.g. for testing)
        await self.server.initialize()

        # after the server is initialized, we can create the instance actor(s)
        if not self.settings.federation.admin_mode:
            await self.server.create_instance_actors()

        # setup general exception handlers
        await self._setup_exception_handlers()

        # Create and initialize the statistics module logging middleware information
        self.statistics: Statistics = Statistics(
            app=self.app,
            settings=self.settings,
            storage=self.server.storage
        )
        await self.statistics.initialize()

        # Add the activitypub middleware
        self.middleware: ActivityPubMiddleware = ActivityPubMiddleware(
            settings=self.settings,
            statistics=self.statistics,
            server=self.server,
            app=self.app,
            signature_verifier=self.server.delivery.signature_verifier,
            rate_limiter=self.server.rate_limiter,
        )
        await self.middleware.initialize()

        # setup latest version of api
        _ = await self.api(api_version=API_LATEST)

        for task in self.background_tasks:
            self.logger.debug(f"FastAPI: initializing {type(task).__name__}")
            await task.initialize(server=self.server)

        if self.settings.federation.admin_mode:
            self.logger.info(f"FastAPI integration initialized, running in admin mode at {self.settings.domain.admin_url}")
        else:
            self.logger.info(f"FastAPI integration initialized, running at {self.settings.domain.base_url}")
